app.css
⎇
Raw
1/* Theme palettes. The base is light; dark is applied either by the OS
2 preference (theme = auto, the default) or forced by the theme toggle,
3 which puts `light`/`dark` on <html> (see web/src/theme.rs). Keep the two
4 dark blocks in sync. */
5:root {
6 color-scheme: light;
7 --bg: #f6f7f9;
8 --panel: #ffffff;
9 --text: #1a1d21;
10 --muted: #6b7280;
11 --accent: #3b82f6;
12 --accent-hover: #2563eb;
13 --accent-border: #1d4ed8;
14 --danger: #ef4444;
15 --danger-border: #dc2626;
16 --border: #e5e7eb;
17 --topbar-h: 48px;
18 --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.5' stroke-linecap='square'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
19}
20
21:root.dark {
22 color-scheme: dark;
23 --bg: #0f1115;
24 --panel: #171a20;
25 --text: #e5e7eb;
26 --muted: #9ca3af;
27 --accent: #60a5fa;
28 --accent-hover: #3b82f6;
29 --accent-border: #79b5fc;
30 --danger: #f87171;
31 --danger-border: #fa8b8b;
32 --border: #2a2f3a;
33 --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2.5' stroke-linecap='square'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
34}
35
36@media (prefers-color-scheme: dark) {
37 :root:not(.light) {
38 color-scheme: dark;
39 --bg: #0f1115;
40 --panel: #171a20;
41 --text: #e5e7eb;
42 --muted: #9ca3af;
43 --accent: #60a5fa;
44 --accent-hover: #3b82f6;
45 --accent-border: #79b5fc;
46 --danger: #f87171;
47 --danger-border: #fa8b8b;
48 --border: #2a2f3a;
49 --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2.5' stroke-linecap='square'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
50 }
51}
52
53* {
54 box-sizing: border-box;
55 /* The design language is sharp: everything stays square. */
56 border-radius: 0;
57}
58
59body {
60 margin: 0;
61 font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
62 background: var(--bg);
63 color: var(--text);
64 font-size: 15px;
65}
66
67.muted {
68 color: var(--muted);
69}
70
71.card {
72 background: var(--panel);
73 border: 1px solid var(--border);
74 padding: 24px;
75}
76
77.center-screen {
78 min-height: 100vh;
79 display: flex;
80 flex-direction: column;
81 align-items: center;
82 justify-content: center;
83 padding: 16px;
84 text-align: center;
85}
86
87/* Inside the app frame the topbar already took part of the viewport, so a
88 full 100vh here overflows and shows a scrollbar with nothing to scroll to.
89 Login and setup render .center-screen as the whole page, where the 100vh
90 above is right. */
91.content > .center-screen {
92 min-height: 0;
93 flex: 1;
94}
95
96.auth-card {
97 width: 100%;
98 max-width: 380px;
99}
100
101/* The auth forms have a single action, so its button spans the form. */
102.auth-card .btn-primary {
103 width: 100%;
104}
105
106.auth-card h1 {
107 margin: 0 0 8px;
108 font-size: 22px;
109}
110
111.auth-logo {
112 width: 44px;
113 height: 44px;
114 margin-bottom: 12px;
115}
116
117.auth-card p {
118 margin: 0 0 20px;
119 font-size: 13px;
120}
121
122.field {
123 display: block;
124 margin-bottom: 14px;
125}
126
127.field span {
128 display: block;
129 font-size: 13px;
130 color: var(--muted);
131 margin-bottom: 4px;
132}
133
134input {
135 width: 100%;
136 padding: 10px 12px;
137 border: 1px solid var(--border);
138 background: var(--bg);
139 color: var(--text);
140 font-size: 14px;
141}
142
143input:focus {
144 outline: 2px solid var(--accent);
145 outline-offset: -1px;
146}
147
148/* Square checkboxes — native controls are rounded, the theme is sharp. */
149input[type="checkbox"] {
150 appearance: none;
151 -webkit-appearance: none;
152 width: 16px;
153 height: 16px;
154 margin: 0;
155 padding: 0;
156 flex: none;
157 border: 1px solid var(--border);
158 background: var(--panel);
159 cursor: pointer;
160 position: relative;
161 vertical-align: -3px;
162}
163
164input[type="checkbox"]:checked {
165 background: var(--accent);
166 border-color: var(--accent-border);
167}
168
169input[type="checkbox"]:checked::after {
170 content: "";
171 position: absolute;
172 left: 4px;
173 top: 1px;
174 width: 4px;
175 height: 8px;
176 border: solid #fff;
177 border-width: 0 2px 2px 0;
178 transform: rotate(45deg);
179}
180
181button {
182 font: inherit;
183 cursor: pointer;
184 display: inline-flex;
185 align-items: center;
186 justify-content: center;
187 gap: 6px;
188 border: 1px solid var(--border);
189 background: var(--panel);
190 color: var(--text);
191 padding: 9px 14px;
192}
193
194button:hover {
195 border-color: var(--muted);
196}
197
198button:disabled {
199 opacity: 0.6;
200 cursor: default;
201}
202
203/* No width here on purpose. Full width belongs to the few places that ask
204 for it (.auth-card, .unlock-btn), not to every dialog's action row. */
205.btn-primary {
206 background: var(--accent);
207 /* One step away from the fill, per theme (see palette above), so the
208 button's edge reads the same as on neutral buttons. */
209 border-color: var(--accent-border);
210 color: #fff;
211 font-weight: 600;
212}
213
214.btn-primary:hover {
215 background: var(--accent-hover);
216 border-color: var(--accent-border);
217}
218
219.error {
220 color: var(--danger);
221 font-size: 13px;
222 min-height: 18px;
223 margin: 0 0 10px;
224}
225
226/* layout (signed-in): full-width topbar, then sidebar + content below */
227
228.layout {
229 min-height: 100vh;
230 display: flex;
231 flex-direction: column;
232}
233
234.topbar {
235 flex: none;
236 position: sticky;
237 top: 0;
238 z-index: 130;
239 height: var(--topbar-h);
240 display: flex;
241 align-items: center;
242 justify-content: space-between;
243 gap: 12px;
244 padding: 0 16px;
245 background: var(--panel);
246 border-bottom: 1px solid var(--border);
247}
248
249.topbar-left {
250 display: flex;
251 align-items: center;
252 gap: 10px;
253 min-width: 0;
254}
255
256.icon-btn.nav-toggle {
257 display: none;
258}
259
260.body-row {
261 flex: 1;
262 display: flex;
263 align-items: stretch;
264}
265
266/* ---- sidebar ---- */
267
268.sidebar {
269 flex: none;
270 width: 224px;
271 display: flex;
272 flex-direction: column;
273 background: var(--panel);
274 border-right: 1px solid var(--border);
275 position: sticky;
276 top: var(--topbar-h);
277 height: calc(100vh - var(--topbar-h));
278}
279
280.brand {
281 display: flex;
282 align-items: center;
283 gap: 10px;
284 font-weight: 700;
285 white-space: nowrap;
286 color: inherit;
287 text-decoration: none;
288 cursor: pointer;
289}
290
291.brand:hover {
292 opacity: 0.8;
293}
294
295.brand-name {
296 overflow: hidden;
297 text-overflow: ellipsis;
298}
299
300.logo {
301 width: 26px;
302 height: 26px;
303 flex: none;
304 display: block;
305}
306
307.nav {
308 flex: 1;
309 overflow-y: auto;
310 padding: 14px 8px 16px;
311 display: flex;
312 flex-direction: column;
313 gap: 20px;
314}
315
316.sidebar-user {
317 padding: 0 10px 2px;
318 font-size: 14px;
319 font-weight: 600;
320 overflow: hidden;
321 text-overflow: ellipsis;
322 white-space: nowrap;
323}
324
325.nav-section-label {
326 font-size: 11px;
327 font-weight: 700;
328 text-transform: uppercase;
329 letter-spacing: 0.08em;
330 color: var(--muted);
331 padding: 0 10px 6px;
332}
333
334/* Section header with a right-aligned action (the Files refresh). */
335.nav-section-head {
336 display: flex;
337 align-items: center;
338 justify-content: space-between;
339 gap: 6px;
340 margin-bottom: 4px;
341}
342
343.nav-section-head .nav-section-label {
344 flex: 1;
345 padding-bottom: 2px;
346}
347
348.nav-section-refresh {
349 color: var(--muted);
350 background: none;
351 border-color: transparent;
352 padding: 3px 5px;
353}
354
355.nav-section-refresh:hover {
356 color: var(--text);
357 border-color: var(--border);
358}
359
360.nav-tab {
361 display: flex;
362 align-items: center;
363 justify-content: flex-start;
364 gap: 10px;
365 width: 100%;
366 margin-bottom: 2px;
367 padding: 8px 10px;
368 text-align: left;
369 background: none;
370 border: none;
371 color: var(--muted);
372 font-size: 14px;
373}
374
375.nav-tab:hover {
376 background: var(--bg);
377 color: var(--text);
378}
379
380.nav-tab.active {
381 background: var(--accent);
382 border-color: var(--accent);
383 color: #fff;
384}
385
386.ic-tab {
387 width: 18px;
388 height: 18px;
389 flex: none;
390}
391
392.nav-tab.active .icon {
393 color: #fff;
394}
395
396.nav-tab-name {
397 flex: 1;
398 min-width: 0;
399 overflow: hidden;
400 text-overflow: ellipsis;
401 white-space: nowrap;
402}
403
404.nav-tab-note {
405 flex: none;
406 font-size: 10px;
407 font-weight: 700;
408 padding: 1px 5px;
409 background: var(--bg);
410 border: 1px solid var(--border);
411 color: var(--muted);
412}
413
414.nav-tab.active .nav-tab-note {
415 background: rgb(255 255 255 / 18%);
416 border-color: rgb(255 255 255 / 30%);
417 color: #fff;
418}
419
420.nav-backdrop {
421 display: none;
422}
423
424/* Sidebar footer: theme toggle, log out, and the project version.
425 Sits at the bottom of the sidebar, below the manage section. */
426.sidebar-footer {
427 flex: none;
428 border-top: 1px solid var(--border);
429 padding: 8px;
430}
431
432.sidebar-footer .nav-tab {
433 margin-bottom: 0;
434}
435
436.sidebar-action.logout {
437 color: var(--danger);
438}
439
440.sidebar-action.logout:hover {
441 color: var(--danger);
442}
443
444.sidebar-version {
445 margin-top: 8px;
446 padding: 0 10px;
447 font-size: 11.5px;
448 color: var(--muted);
449 white-space: nowrap;
450 overflow: hidden;
451 text-overflow: ellipsis;
452}
453
454.badge {
455 font-size: 11px;
456 padding: 2px 8px;
457 background: var(--bg);
458 border: 1px solid var(--border);
459 color: var(--muted);
460}
461
462.content {
463 flex: 1;
464 min-width: 0;
465 min-height: 0;
466 display: flex;
467 flex-direction: column;
468 padding: 20px;
469}
470
471/* While a file is open the app is a fixed-height frame: the file view
472 * (editor, image, …) scrolls inside, the page itself never grows. */
473.layout.file-mode {
474 height: 100vh;
475 overflow: hidden;
476}
477
478.layout.file-mode .body-row {
479 min-height: 0;
480}
481
482/* Narrow screens: the sidebar becomes an off-canvas drawer that only
483 covers the content, below the (always visible) topbar. */
484@media (max-width: 860px) {
485 .sidebar {
486 position: fixed;
487 top: var(--topbar-h);
488 bottom: 0;
489 left: 0;
490 right: auto;
491 height: auto;
492 z-index: 120;
493 transform: translateX(-100%);
494 transition: transform 0.15s ease;
495 }
496
497 .sidebar.open {
498 transform: translateX(0);
499 box-shadow: 0 0 48px rgb(0 0 0 / 35%);
500 }
501
502 .nav-backdrop {
503 display: block;
504 position: fixed;
505 top: var(--topbar-h);
506 bottom: 0;
507 left: 0;
508 right: 0;
509 z-index: 110;
510 background: rgb(0 0 0 / 40%);
511 }
512
513 .icon-btn.nav-toggle {
514 display: inline-flex;
515 }
516
517 .content {
518 padding: 14px;
519 }
520}
521
522/* ---- file browser ---- */
523
524.browser {
525 width: 100%;
526 max-width: 1200px;
527 margin: 0 auto;
528 flex: 1;
529 display: flex;
530 flex-direction: column;
531 min-height: 0;
532}
533
534.icon {
535 width: 20px;
536 height: 20px;
537 display: inline-block;
538 vertical-align: middle;
539}
540
541.ic-folder {
542 color: var(--accent);
543}
544
545.ic-muted {
546 color: var(--muted);
547}
548
549.crumbs {
550 display: flex;
551 align-items: center;
552 gap: 2px;
553 padding: 6px 0 12px;
554 font-size: 15px;
555 flex-wrap: wrap;
556}
557
558.crumb {
559 cursor: pointer;
560 color: var(--muted);
561 padding: 3px 6px;
562}
563
564.crumb:hover {
565 color: var(--text);
566 background: var(--panel);
567}
568
569.crumb.current {
570 color: var(--text);
571 font-weight: 600;
572 cursor: default;
573 background: none;
574}
575
576.crumb-sep {
577 color: var(--muted);
578 padding: 0 1px;
579}
580
581.toolbar {
582 display: flex;
583 align-items: center;
584 justify-content: space-between;
585 gap: 6px;
586 padding-bottom: 12px;
587}
588
589.toolbar-right {
590 display: flex;
591 gap: 6px;
592}
593
594/* Sort control in the toolbar. The select mirrors the app's other selects:
595 no native chrome, own arrow. */
596.sort-label {
597 font-size: 13px;
598 color: var(--muted);
599 align-self: center;
600 margin-right: 2px;
601}
602
603.sort-select {
604 appearance: none;
605 width: auto;
606 padding: 7px 32px 7px 10px;
607 border: 1px solid var(--border);
608 background-color: var(--panel);
609 background-image: var(--select-arrow);
610 background-repeat: no-repeat;
611 background-position: right 9px center;
612 background-size: 12px 12px;
613 color: var(--text);
614 font: inherit;
615 font-size: 13px;
616}
617
618.sort-select:focus {
619 outline: 2px solid var(--accent);
620 outline-offset: -1px;
621}
622
623.icon-btn {
624 padding: 7px 9px;
625 border: 1px solid var(--border);
626 background: var(--panel);
627 color: var(--muted);
628 display: inline-flex;
629 align-items: center;
630}
631
632.icon-btn:hover {
633 border-color: var(--muted);
634}
635
636.icon-btn.active {
637 background: var(--accent);
638 border-color: var(--accent);
639 color: #fff;
640}
641
642.ic-btn {
643 width: 18px;
644 height: 18px;
645 color: inherit;
646}
647
648.entries-area {
649 position: relative;
650 flex: 1;
651 min-height: 240px;
652}
653
654.entries-area.drop-target {
655 outline: 2px dashed var(--accent);
656 outline-offset: -2px;
657}
658
659.drop-hint {
660 position: absolute;
661 inset: 0;
662 z-index: 2;
663 display: flex;
664 align-items: center;
665 justify-content: center;
666 gap: 8px;
667 font-weight: 500;
668 color: var(--accent);
669 background: color-mix(in srgb, var(--accent) 10%, var(--panel) 60%);
670 /* The hint must not steal the drag from the area beneath it. */
671 pointer-events: none;
672}
673
674.center-note {
675 text-align: center;
676 padding: 48px 0;
677}
678
679.error-card {
680 display: flex;
681 align-items: center;
682 justify-content: space-between;
683 gap: 16px;
684 color: var(--danger);
685 font-size: 14px;
686}
687
688/* root tiles were replaced by the sidebar's folder tabs */
689
690/* grid view */
691
692.entries-grid {
693 display: grid;
694 grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
695 gap: 10px;
696}
697
698.tile {
699 background: var(--panel);
700 border: 1px solid var(--border);
701 padding: 16px 10px 12px;
702 text-align: center;
703 cursor: pointer;
704 user-select: none;
705}
706
707/* Touch: a long press opens the context menu (see util::LongPress). The
708 iOS callout and text selection would otherwise appear first. Vertical
709 panning stays with the browser. */
710.entries-area,
711.tile,
712.row {
713 -webkit-touch-callout: none;
714 -webkit-user-select: none;
715 touch-action: pan-y;
716}
717
718.tile:hover {
719 border-color: var(--muted);
720}
721
722.tile-icon {
723 width: 42px;
724 height: 42px;
725 margin: 0 auto 10px;
726 display: block;
727}
728
729/* Holds the icon with the thumbnail layered over it. The icon is the
730 fallback when no thumbnail loads. */
731.tile-media {
732 position: relative;
733 display: flex;
734 align-items: center;
735 justify-content: center;
736 margin-bottom: 10px;
737}
738
739.tile-media .tile-icon {
740 margin: 0;
741}
742
743/* One tile height per folder: the taller media area applies to every tile,
744 not only the ones that end up with a thumbnail. */
745.has-thumbs .tile-media {
746 height: 92px;
747}
748
749/* No background: the element must stay transparent until the bytes arrive,
750 or it hides the icon underneath as a dark rectangle. */
751.tile-thumb {
752 position: absolute;
753 inset: 0;
754 width: 100%;
755 height: 100%;
756 object-fit: cover;
757}
758
759.tile-name {
760 font-size: 13px;
761 line-height: 1.3;
762 word-break: break-word;
763 /* Uniform tile height: the name area always takes two lines and
764 never overflows (the full name is in the title tooltip). */
765 min-height: calc(2 * 1.3em);
766 display: -webkit-box;
767 -webkit-line-clamp: 2;
768 -webkit-box-orient: vertical;
769 overflow: hidden;
770}
771
772/* list view */
773
774.entries-list {
775 background: var(--panel);
776 border: 1px solid var(--border);
777 overflow: hidden;
778}
779
780.row {
781 display: grid;
782 grid-template-columns: 28px minmax(0, 1fr) 90px 150px;
783 gap: 12px;
784 align-items: center;
785 padding: 9px 14px;
786 border-bottom: 1px solid var(--border);
787 cursor: pointer;
788 font-size: 14px;
789 user-select: none;
790}
791
792.row:last-child {
793 border-bottom: none;
794}
795
796.row:hover {
797 background: var(--bg);
798}
799
800.row-icon {
801 width: 20px;
802 height: 20px;
803}
804
805.row-name {
806 overflow: hidden;
807 text-overflow: ellipsis;
808 white-space: nowrap;
809}
810
811.row-size,
812.row-date {
813 font-size: 13px;
814 text-align: right;
815}
816
817@media (max-width: 640px) {
818 .row {
819 grid-template-columns: 28px minmax(0, 1fr) 90px;
820 }
821
822 .row-date {
823 display: none;
824 }
825}
826
827/* context menu */
828
829.ctx-menu {
830 position: fixed;
831 z-index: 100;
832 min-width: 190px;
833 background: var(--panel);
834 border: 1px solid var(--border);
835 padding: 5px;
836 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
837}
838
839.ctx-item {
840 display: flex;
841 align-items: center;
842 gap: 10px;
843 padding: 8px 12px;
844 font-size: 14px;
845 cursor: pointer;
846}
847
848.ic-ctx {
849 width: 18px;
850 height: 18px;
851 flex: none;
852}
853
854.ctx-item:hover {
855 background: var(--bg);
856}
857
858.ctx-item.disabled {
859 opacity: 0.45;
860 cursor: default;
861}
862
863.ctx-item.disabled:hover {
864 background: none;
865}
866
867/* toolbar selection actions (top right) */
868
869.sel-actions {
870 display: flex;
871 align-items: center;
872 gap: 6px;
873 position: relative;
874}
875
876.sel-actions .icon-btn:disabled {
877 opacity: 0.45;
878 cursor: default;
879}
880
881.sel-actions .icon-btn:disabled:hover {
882 border-color: var(--border);
883 color: var(--muted);
884}
885
886.sel-more-menu {
887 position: fixed;
888 inset: auto;
889 margin: 0;
890 min-width: 190px;
891 color: inherit;
892 background: var(--panel);
893 border: 1px solid var(--border);
894 padding: 5px;
895 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
896}
897
898/* selection actions in the top bar */
899
900.topbar .sel-actions {
901 min-height: 0;
902}
903
904.topbar .sel-actions .icon-btn {
905 padding: 5px 7px;
906}
907
908/* selection highlight in the grid and list */
909
910.tile.selected {
911 border-color: var(--accent);
912 background: color-mix(in srgb, var(--accent) 12%, var(--panel));
913}
914
915.row.selected {
916 background: color-mix(in srgb, var(--accent) 12%, var(--panel));
917 box-shadow: inset 2px 0 0 var(--accent);
918}
919
920/* settings tabs (profile / server) */
921
922.settings-tabs {
923 display: flex;
924 gap: 8px;
925}
926
927.settings-tab-btn {
928 padding: 7px 14px;
929 border: 1px solid var(--border);
930 background: var(--panel);
931 color: var(--muted);
932 font-size: 13px;
933 font-weight: 600;
934}
935
936.settings-tab-btn:hover {
937 border-color: var(--muted);
938 color: var(--text);
939}
940
941.settings-tab-btn.active {
942 border-color: var(--accent);
943 color: var(--accent);
944 background: color-mix(in srgb, var(--accent) 10%, var(--panel));
945}
946
947/* toast */
948
949.toast {
950 position: fixed;
951 bottom: 20px;
952 right: 20px;
953 z-index: 200;
954 background: var(--panel);
955 color: var(--text);
956 border: 1px solid var(--border);
957 padding: 11px 16px;
958 font-size: 14px;
959 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
960 max-width: 320px;
961}
962
963.toast-error {
964 background: color-mix(in srgb, var(--danger) 12%, var(--panel));
965 border-color: var(--danger-border);
966 color: var(--danger);
967}
968
969
970/* ---- modals ---- */
971
972/* The shell is a native <dialog> opened with showModal(): the browser puts
973 it in the top layer and paints ::backdrop, so there is no overlay element
974 to position and no z-index to keep in order. The reset below strips the
975 UA's own box so the .modal card inside is the only thing that shows. */
976.modal-host {
977 padding: 0;
978 border: none;
979 background: none;
980 /* The UA caps an open dialog's size; the card sets its own. */
981 max-width: none;
982 max-height: none;
983 /* Centre it in the inset-0 box, as the old flex overlay did. */
984 margin: auto;
985 overflow: visible;
986}
987
988.modal-host::backdrop {
989 background: rgb(0 0 0 / 45%);
990}
991
992.modal {
993 width: 440px;
994 max-width: calc(100vw - 32px);
995 padding: 20px;
996 background: var(--panel);
997 border: 1px solid var(--border);
998 box-shadow: 0 12px 44px rgb(0 0 0 / 35%);
999}
1000
1001.modal.picker {
1002 width: 640px;
1003}
1004
1005.modal-title {
1006 margin: 0 0 12px;
1007 font-size: 16px;
1008}
1009
1010.modal-label {
1011 display: block;
1012 font-size: 13px;
1013 color: var(--muted);
1014 margin-bottom: 6px;
1015}
1016
1017.modal .input {
1018 width: 100%;
1019 box-sizing: border-box;
1020 margin-bottom: 16px;
1021}
1022
1023.modal-label.error,
1024.dialog-error {
1025 color: var(--danger);
1026 font-size: 13px;
1027}
1028
1029.dialog-error {
1030 margin-top: 8px;
1031}
1032
1033.modal-message {
1034 white-space: pre-line;
1035 font-size: 14px;
1036 margin: 0 0 8px;
1037}
1038
1039.modal-actions {
1040 display: flex;
1041 justify-content: flex-end;
1042 gap: 8px;
1043 margin-top: 16px;
1044}
1045
1046.btn-danger {
1047 background: var(--danger);
1048 /* A visible border (one step away from the fill, per theme) so the
1049 button's edge reads the same as on neutral buttons. */
1050 border-color: var(--danger-border);
1051 color: #fff;
1052}
1053
1054.btn-danger:hover {
1055 background: color-mix(in srgb, var(--danger) 85%, black);
1056}
1057
1058.btn:disabled {
1059 opacity: 0.5;
1060 cursor: not-allowed;
1061}
1062
1063.conflict-list {
1064 max-height: 200px;
1065 overflow-y: auto;
1066 margin: 8px 0;
1067 padding-left: 20px;
1068 font-size: 13px;
1069}
1070
1071/* Folder picker: roots on the left, the selected root's tree on the right. */
1072
1073.picker-body {
1074 display: grid;
1075 grid-template-columns: 180px minmax(0, 1fr);
1076 border: 1px solid var(--border);
1077 height: 320px;
1078}
1079
1080.picker-roots {
1081 display: flex;
1082 flex-direction: column;
1083 gap: 2px;
1084 padding: 6px;
1085 border-right: 1px solid var(--border);
1086 background: var(--bg);
1087 min-height: 0;
1088 overflow-y: auto;
1089}
1090
1091.picker-root {
1092 display: flex;
1093 align-items: center;
1094 justify-content: flex-start;
1095 gap: 8px;
1096 padding: 7px 8px;
1097 border: none;
1098 background: transparent;
1099 color: var(--text);
1100 font: inherit;
1101 font-size: 13px;
1102 text-align: left;
1103 cursor: pointer;
1104 min-width: 0;
1105}
1106
1107.picker-root:hover {
1108 background: var(--panel);
1109}
1110
1111.picker-root.active {
1112 background: var(--accent);
1113 color: #fff;
1114}
1115
1116.picker-root.ro .picker-root-name {
1117 opacity: 0.6;
1118 text-decoration: line-through dotted;
1119}
1120
1121.picker-root-ic {
1122 width: 16px;
1123 height: 16px;
1124 flex: none;
1125}
1126
1127.picker-root-name {
1128 overflow: hidden;
1129 text-overflow: ellipsis;
1130 white-space: nowrap;
1131}
1132
1133.picker-main {
1134 display: flex;
1135 flex-direction: column;
1136 min-width: 0;
1137 /* Grid items default to min-height:auto, which would let a long
1138 folder list stretch the dialog instead of scrolling. */
1139 min-height: 0;
1140}
1141
1142.picker-crumbs {
1143 padding: 8px 10px;
1144 font-size: 13px;
1145 border-bottom: 1px solid var(--border);
1146 flex: none;
1147}
1148
1149.picker-entries {
1150 flex: 1;
1151 min-height: 0;
1152 overflow-y: auto;
1153}
1154
1155.picker-row {
1156 display: flex;
1157 align-items: center;
1158 gap: 10px;
1159 padding: 8px 12px;
1160 cursor: pointer;
1161 font-size: 14px;
1162 user-select: none;
1163}
1164
1165.picker-row:hover {
1166 background: var(--bg);
1167}
1168
1169.picker-row .row-name {
1170 flex: 1;
1171}
1172
1173/* A right-pointing chevron drawn with borders (no icon needed). */
1174.picker-chev {
1175 width: 7px;
1176 height: 7px;
1177 border-right: 1.5px solid var(--muted);
1178 border-bottom: 1.5px solid var(--muted);
1179 transform: rotate(-45deg);
1180 flex: none;
1181}
1182
1183.picker-empty {
1184 height: 100%;
1185 display: flex;
1186 flex-direction: column;
1187 align-items: center;
1188 justify-content: center;
1189 gap: 8px;
1190 font-size: 13px;
1191}
1192
1193.picker-empty-ic {
1194 width: 32px;
1195 height: 32px;
1196 opacity: 0.5;
1197}
1198
1199.picker-root.active .picker-root-ic {
1200 color: #fff;
1201}
1202
1203@media (max-width: 640px) {
1204 .picker-body {
1205 grid-template-columns: 1fr;
1206 grid-template-rows: auto minmax(0, 1fr);
1207 height: 380px;
1208 }
1209
1210 .picker-roots {
1211 flex-direction: row;
1212 flex-wrap: wrap;
1213 border-right: none;
1214 border-bottom: 1px solid var(--border);
1215 }
1216}
1217
1218/* ---------------------------------------------------------------------------
1219 * Full-page file view (preview / editor / "no preview"). The top bar carries
1220 * the title + actions; the content area below is edge to edge.
1221 * ------------------------------------------------------------------------- */
1222.content.file-open {
1223 padding: 0;
1224 overflow: hidden;
1225}
1226
1227/* The browser stays mounted (state, fetch callback) but hidden. */
1228.content.file-open .browser {
1229 display: none;
1230}
1231
1232.file-view {
1233 flex: 1;
1234 min-height: 0;
1235 display: flex;
1236 flex-direction: column;
1237}
1238
1239.file-title {
1240 display: flex;
1241 align-items: center;
1242 gap: 8px;
1243 min-width: 0;
1244}
1245
1246.file-title-icon {
1247 width: 18px;
1248 height: 18px;
1249 flex: none;
1250 color: var(--muted);
1251}
1252
1253.file-title-name {
1254 font-weight: 600;
1255 font-size: 14px;
1256 white-space: nowrap;
1257 overflow: hidden;
1258 text-overflow: ellipsis;
1259}
1260
1261.file-actions {
1262 display: flex;
1263 align-items: center;
1264 gap: 10px;
1265 flex: none;
1266}
1267
1268.file-close,
1269.file-download {
1270 border: none;
1271 background: transparent;
1272 color: var(--muted);
1273 padding: 6px 8px;
1274}
1275
1276.file-close:hover,
1277.file-download:hover {
1278 color: var(--text);
1279 background: var(--bg);
1280}
1281.preview-body {
1282 flex: 1;
1283 min-height: 0;
1284 overflow: auto;
1285 display: flex;
1286 background: var(--bg);
1287}
1288.preview-img {
1289 max-width: 100%;
1290 max-height: 100%;
1291 display: block;
1292 margin: auto;
1293}
1294.preview-pdf {
1295 width: 100%;
1296 height: 100%;
1297 border: none;
1298 background: #fff;
1299}
1300.preview-video {
1301 width: 100%;
1302 max-height: 100%;
1303 background: #000;
1304}
1305.preview-audio {
1306 display: flex;
1307 align-items: center;
1308 justify-content: center;
1309 width: 100%;
1310 padding: 24px;
1311}
1312.preview-audio audio {
1313 width: 100%;
1314 max-width: 480px;
1315}
1316
1317/* CodeMirror (text editor) */
1318.cm-container {
1319 height: 100%;
1320}
1321.cm-container .cm-editor {
1322 height: 100%;
1323}
1324/* Load/save failure: dims the editor and shows the message in a card
1325 * (the top-bar Save button stays reachable to retry). */
1326.cm-error {
1327 position: absolute;
1328 inset: 0;
1329 z-index: 5;
1330 display: flex;
1331 align-items: center;
1332 justify-content: center;
1333 padding: 24px;
1334 background: rgb(0 0 0 / 45%);
1335}
1336
1337.cm-error-card {
1338 position: relative;
1339 max-width: 440px;
1340 min-width: 200px;
1341 background: var(--panel);
1342 border: 1px solid var(--border);
1343 box-shadow: 0 12px 44px rgb(0 0 0 / 35%);
1344}
1345
1346.cm-error-close {
1347 position: absolute;
1348 top: 7px;
1349 right: 7px;
1350 display: inline-flex;
1351 align-items: center;
1352 justify-content: center;
1353 border: none;
1354 background: transparent;
1355 color: var(--muted);
1356 padding: 3px;
1357}
1358
1359.cm-error-close:hover {
1360 color: var(--text);
1361 background: var(--bg);
1362}
1363
1364.cm-error-close .ic-btn {
1365 width: 16px;
1366 height: 16px;
1367}
1368
1369.cm-error-text {
1370 padding: 16px 40px 16px 20px;
1371 font-size: 14px;
1372 text-align: center;
1373 overflow-wrap: anywhere;
1374}
1375
1376/* ---------------------------------------------------------------------------
1377 * Text editor (full page; title/save/close live in the top bar)
1378 * ------------------------------------------------------------------------- */
1379.file-editor {
1380 flex: 1;
1381 min-height: 0;
1382 display: flex;
1383 position: relative;
1384 background: var(--bg);
1385}
1386.file-editor .cm-container {
1387 width: 100%;
1388 height: 100%;
1389}
1390/* "No preview" fallback (binaries, archives). */
1391.file-nopreview {
1392 flex: 1;
1393 display: flex;
1394 align-items: center;
1395 justify-content: center;
1396}
1397.file-nopreview .btn-primary {
1398 margin-top: 14px;
1399}
1400/* ---------------------------------------------------------------------------
1401 * Download format dialog
1402 * ------------------------------------------------------------------------- */
1403.dl-formats {
1404 display: flex;
1405 flex-direction: column;
1406 gap: 8px;
1407 margin-bottom: 8px;
1408}
1409.dl-fmt {
1410 justify-content: flex-start;
1411}
1412
1413/* ---------------------------------------------------------------------------
1414 * Item info dialog
1415 * ------------------------------------------------------------------------- */
1416.info-list {
1417 margin: 8px 0 16px;
1418 display: grid;
1419 grid-template-columns: 90px 1fr;
1420 row-gap: 8px;
1421 column-gap: 12px;
1422}
1423.info-list dt {
1424 color: var(--muted);
1425}
1426.info-list dd {
1427 margin: 0;
1428 word-break: break-word;
1429}
1430
1431/* ---------------------------------------------------------------------------
1432 Shares (milestone 6)
1433 --------------------------------------------------------------------------- */
1434
1435.share-modal {
1436 width: 480px;
1437}
1438
1439.share-link-row {
1440 display: flex;
1441 gap: 8px;
1442 margin: 12px 0;
1443}
1444
1445.share-link-input {
1446 flex: 1;
1447 font-family: ui-monospace, monospace;
1448 font-size: 12.5px;
1449 padding: 8px 10px;
1450 border: 1px solid var(--border);
1451 background: var(--bg);
1452 color: var(--text);
1453 min-width: 0;
1454}
1455
1456.share-check {
1457 display: flex;
1458 align-items: center;
1459 gap: 8px;
1460 margin: 4px 0 14px;
1461 font-size: 14px;
1462}
1463
1464.share-check input {
1465 width: 16px;
1466 height: 16px;
1467}
1468
1469.share-expiry-row {
1470 display: flex;
1471 align-items: center;
1472 gap: 10px;
1473 margin: 0 0 10px;
1474}
1475
1476.share-expiry-row label {
1477 font-size: 13px;
1478 color: var(--muted);
1479}
1480
1481.share-expiry-row select,
1482.share-expiry-row input {
1483 flex: 1;
1484 padding: 8px 10px;
1485 border: 1px solid var(--border);
1486 background: var(--panel);
1487 color: var(--text);
1488 font: inherit;
1489}
1490
1491/* Optional password on the create-share form. Laid out like the expiry
1492 row above it, so the form reads as one column of labelled fields. */
1493.share-pw-row {
1494 display: flex;
1495 align-items: center;
1496 gap: 10px;
1497 margin: 0 0 6px;
1498}
1499
1500.share-pw-row label {
1501 font-size: 13px;
1502 color: var(--muted);
1503}
1504
1505/* Expiry and password sit in one column, so their inputs line up. */
1506.share-expiry-row label,
1507.share-pw-row label {
1508 min-width: 64px;
1509}
1510
1511.share-pw-row input {
1512 flex: 1;
1513 padding: 8px 10px;
1514 border: 1px solid var(--border);
1515 background: var(--panel);
1516 color: var(--text);
1517 font: inherit;
1518}
1519
1520.share-pw-hint {
1521 font-size: 12px;
1522 margin: 0 0 10px;
1523}
1524
1525/* The password gate of a protected share: the whole page is this card. */
1526.unlock-card {
1527 width: 320px;
1528 max-width: calc(100vw - 32px);
1529 padding: 20px;
1530 text-align: left;
1531}
1532
1533.unlock-card h2 {
1534 margin: 0 0 6px;
1535 font-size: 18px;
1536}
1537
1538.unlock-card input {
1539 margin-top: 14px;
1540}
1541
1542.unlock-btn {
1543 width: 100%;
1544 margin-top: 12px;
1545}
1546
1547.share-expiry-note {
1548 margin: 4px 0 0;
1549 font-size: 12.5px;
1550}
1551
1552.btn-sm {
1553 padding: 5px 10px;
1554 font-size: 12.5px;
1555}
1556
1557.icon-btn-sm {
1558 padding: 4px 6px;
1559}
1560
1561/* "Your shares" manage view */
1562
1563.shares-view {
1564 width: 100%;
1565 max-width: 960px;
1566 margin: 0 auto;
1567}
1568
1569.view-header {
1570 margin-bottom: 16px;
1571}
1572
1573.view-header .admin-section-title {
1574 margin-bottom: 0;
1575}
1576
1577.view-hint {
1578 margin: 4px 0 0;
1579 font-size: 13px;
1580 max-width: 64ch;
1581}
1582
1583.share-list {
1584 display: flex;
1585 flex-direction: column;
1586 gap: 10px;
1587}
1588
1589.share-item {
1590 display: flex;
1591 align-items: center;
1592 justify-content: space-between;
1593 gap: 12px;
1594 padding: 10px 12px;
1595 border: 1px solid var(--border);
1596 background: var(--panel);
1597}
1598
1599.share-item-main {
1600 min-width: 0;
1601}
1602
1603.share-item-name {
1604 display: flex;
1605 align-items: center;
1606 gap: 8px;
1607 font-weight: 600;
1608 font-size: 14px;
1609 overflow: hidden;
1610 text-overflow: ellipsis;
1611 white-space: nowrap;
1612}
1613
1614.ic-row {
1615 width: 18px;
1616 height: 18px;
1617 flex: none;
1618}
1619
1620.share-item-meta {
1621 font-size: 12px;
1622 margin-top: 2px;
1623 overflow: hidden;
1624 text-overflow: ellipsis;
1625 white-space: nowrap;
1626}
1627
1628.share-item-actions {
1629 display: flex;
1630 gap: 6px;
1631 flex-shrink: 0;
1632}
1633
1634/* Sections of the share management view: the viewer's own links and, for an
1635 admin, one section per account. Native <details>/<summary>. */
1636
1637.share-groups {
1638 display: flex;
1639 flex-direction: column;
1640 gap: 12px;
1641}
1642
1643.share-group {
1644 border: 1px solid var(--border);
1645 background: var(--panel);
1646}
1647
1648.share-group-head {
1649 display: flex;
1650 align-items: center;
1651 gap: 8px;
1652 padding: 10px 12px;
1653 font-size: 14px;
1654 font-weight: 600;
1655 cursor: pointer;
1656 /* The caret below stands in for the disclosure triangle. */
1657 list-style: none;
1658}
1659
1660.share-group-head::-webkit-details-marker {
1661 display: none;
1662}
1663
1664.share-group-head:hover {
1665 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
1666}
1667
1668.share-group-head:focus-visible {
1669 outline: 2px solid var(--accent);
1670 outline-offset: -2px;
1671}
1672
1673.share-caret {
1674 width: 16px;
1675 height: 16px;
1676 flex: none;
1677 transition: rotate 0.15s ease;
1678}
1679
1680.share-group[open] .share-caret {
1681 rotate: 90deg;
1682}
1683
1684.share-group-count {
1685 font-size: 12px;
1686 font-weight: 400;
1687}
1688
1689/* A live link whose account cannot sign in any more. */
1690.share-group-warn {
1691 font-size: 12px;
1692 font-weight: 400;
1693 color: var(--danger);
1694}
1695
1696.share-group-del {
1697 margin-left: auto;
1698}
1699
1700/* Rows inside a section read as one block: the border moves from each row to
1701 the section. */
1702.share-group .share-list {
1703 gap: 0;
1704 border-top: 1px solid var(--border);
1705}
1706
1707.share-group .share-item {
1708 border: 0;
1709 border-top: 1px solid var(--border);
1710}
1711
1712.share-group .share-item:first-child {
1713 border-top: 0;
1714}
1715
1716/* ------------------------------------------------------------------ */
1717/* Admin (milestone 7) */
1718/* ------------------------------------------------------------------ */
1719
1720.admin-view {
1721 display: flex;
1722 flex-direction: column;
1723 gap: 16px;
1724 width: 100%;
1725 max-width: 960px;
1726 margin: 0 auto;
1727}
1728
1729.admin-section-title {
1730 margin: 0 0 14px;
1731 font-size: 15px;
1732}
1733
1734.users-header {
1735 display: flex;
1736 align-items: center;
1737 justify-content: space-between;
1738 margin-bottom: 4px;
1739}
1740
1741.users-header .admin-section-title {
1742 margin-bottom: 0;
1743}
1744
1745.users-header .btn-primary {
1746 width: auto;
1747}
1748
1749.user-list {
1750 display: flex;
1751 flex-direction: column;
1752 gap: 10px;
1753}
1754
1755.user-row {
1756 display: flex;
1757 align-items: center;
1758 justify-content: space-between;
1759 gap: 12px;
1760 padding: 12px 14px;
1761 border: 1px solid var(--border);
1762}
1763
1764.user-row-main {
1765 min-width: 0;
1766}
1767
1768.user-row-name {
1769 display: flex;
1770 align-items: center;
1771 gap: 6px;
1772 font-weight: 600;
1773 font-size: 14px;
1774 flex-wrap: wrap;
1775}
1776
1777.ic-admin {
1778 width: 16px;
1779 height: 16px;
1780 flex: none;
1781}
1782
1783.user-row-actions {
1784 display: flex;
1785 gap: 6px;
1786 flex-shrink: 0;
1787}
1788
1789.badge-off {
1790 background: color-mix(in srgb, var(--danger) 15%, var(--panel));
1791 border-color: var(--danger);
1792 color: var(--danger);
1793}
1794
1795.setting-row {
1796 display: flex;
1797 align-items: center;
1798 gap: 10px;
1799 padding: 14px;
1800 border: 1px solid var(--border);
1801 cursor: pointer;
1802}
1803
1804.setting-row input {
1805 margin-left: auto;
1806}
1807
1808.setting-label {
1809 display: block;
1810 font-weight: 600;
1811 font-size: 14px;
1812}
1813
1814.setting-desc {
1815 display: block;
1816 font-size: 12px;
1817 color: var(--muted);
1818 margin-top: 2px;
1819}
1820
1821/* Setting row whose control is a select, pushed to the right. */
1822.setting-row-select {
1823 cursor: default;
1824}
1825
1826.setting-row-select > span {
1827 flex: 1;
1828}
1829
1830.setting-row-select select {
1831 appearance: none;
1832 margin-left: auto;
1833 padding: 10px 42px 10px 12px;
1834 border: 1px solid var(--border);
1835 background-color: var(--panel);
1836 background-image: var(--select-arrow);
1837 background-repeat: no-repeat;
1838 background-position: right 12px center;
1839 background-size: 14px 14px;
1840 color: var(--text);
1841 font: inherit;
1842 max-width: 190px;
1843}
1844
1845/* User create/edit form */
1846
1847.user-form {
1848 width: 520px;
1849}
1850
1851.field-input {
1852 width: 100%;
1853 box-sizing: border-box;
1854}
1855
1856/* Search exclusions: a bordered block matching the setting row above it. */
1857.excludes-field {
1858 display: flex;
1859 flex-direction: column;
1860 gap: 10px;
1861 padding: 14px;
1862 border: 1px solid var(--border);
1863}
1864
1865.excludes-list {
1866 display: flex;
1867 flex-direction: column;
1868 gap: 2px;
1869}
1870
1871.exclude-row {
1872 display: flex;
1873 align-items: center;
1874 gap: 8px;
1875 padding: 4px 0;
1876}
1877
1878.exclude-path {
1879 flex: 1;
1880 font-size: 13px;
1881 overflow: hidden;
1882 text-overflow: ellipsis;
1883 white-space: nowrap;
1884}
1885
1886.exclude-remove {
1887 flex: none;
1888 padding: 4px 6px;
1889}
1890
1891.exclude-add {
1892 align-self: flex-start;
1893}
1894
1895.excludes-empty {
1896 font-size: 13px;
1897 margin: 0;
1898}
1899
1900.field-hint {
1901 font-size: 12px;
1902 margin: -2px 0 8px;
1903}
1904
1905.check-row {
1906 display: flex;
1907 align-items: center;
1908 gap: 8px;
1909 font-size: 14px;
1910 margin-bottom: 12px;
1911 cursor: pointer;
1912}
1913
1914.form-error {
1915 color: var(--danger);
1916 font-size: 13px;
1917 margin: 4px 0 8px;
1918}
1919
1920.roots-editor {
1921 display: flex;
1922 flex-direction: column;
1923 gap: 6px;
1924}
1925
1926.root-draft {
1927 display: flex;
1928 align-items: center;
1929 gap: 8px;
1930}
1931
1932.root-draft-path {
1933 flex: 1;
1934 font-size: 13px;
1935 overflow: hidden;
1936 text-overflow: ellipsis;
1937 white-space: nowrap;
1938}
1939
1940.root-draft-mode {
1941 width: auto;
1942 padding: 6px 8px;
1943}
1944
1945.root-add {
1946 align-self: flex-start;
1947 margin-top: 4px;
1948}
1949
1950/* ------------------------------------------------------------------ */
1951/* Empty state + keyboard focus (milestone 8) */
1952/* ------------------------------------------------------------------ */
1953
1954.empty-state {
1955 display: flex;
1956 flex-direction: column;
1957 align-items: center;
1958 justify-content: center;
1959 gap: 8px;
1960 padding: 64px 20px;
1961 text-align: center;
1962}
1963
1964.empty-glyph {
1965 width: 52px;
1966 height: 52px;
1967}
1968
1969.empty-state h3 {
1970 margin: 0;
1971 font-size: 16px;
1972}
1973
1974/* Visible keyboard focus on selectable file/folder entries. The ring is
1975 inset (negative offset) so the scroll container never clips it. */
1976.tile:focus-visible,
1977.row:focus-visible {
1978 outline: 2px solid var(--accent);
1979 outline-offset: -2px;
1980}
1981
1982/* ------------------------------------------------------------------ */
1983/* Search view (#/search) */
1984/* ------------------------------------------------------------------ */
1985
1986.search-view {
1987 display: flex;
1988 flex-direction: column;
1989 gap: 14px;
1990}
1991
1992/* Query bar ------------------------------------------------------ */
1993
1994.query-bar {
1995 display: flex;
1996 align-items: stretch;
1997 gap: 8px;
1998}
1999
2000.query-input {
2001 flex: 1;
2002 display: flex;
2003 align-items: center;
2004 gap: 8px;
2005 background: var(--panel);
2006 border: 1px solid var(--border);
2007 padding: 0 10px;
2008 min-width: 0;
2009}
2010
2011.query-input .qi-icon {
2012 width: 18px;
2013 height: 18px;
2014 color: var(--muted);
2015 flex: none;
2016}
2017
2018.query-input input {
2019 flex: 1;
2020 min-width: 0;
2021 border: none;
2022 outline: none;
2023 background: transparent;
2024 color: var(--text);
2025 font: inherit;
2026 font-size: 14px;
2027 height: 38px;
2028 padding: 0;
2029}
2030
2031.query-input input::placeholder {
2032 color: var(--muted);
2033}
2034
2035/* Segmented Name / Content / Both control */
2036
2037.seg {
2038 display: flex;
2039 border: 1px solid var(--border);
2040 background: var(--panel);
2041}
2042
2043.seg button {
2044 appearance: none;
2045 border: none;
2046 border-right: 1px solid var(--border);
2047 background: transparent;
2048 color: var(--muted);
2049 font: inherit;
2050 font-size: 13px;
2051 padding: 0 14px;
2052 cursor: pointer;
2053}
2054
2055.seg button:last-child {
2056 border-right: none;
2057}
2058
2059.seg button:hover {
2060 background: var(--bg);
2061}
2062
2063.seg button.on {
2064 background: var(--accent);
2065 color: #fff;
2066}
2067
2068/* Start folder --------------------------------------------------- */
2069
2070/* The folder a search starts in; opens the folder picker. */
2071.folder-pick {
2072 flex: none;
2073 display: flex;
2074 align-items: center;
2075 gap: 8px;
2076 max-width: 260px;
2077 border: 1px solid var(--border);
2078 background: var(--panel);
2079 color: var(--text);
2080 font: inherit;
2081 font-size: 13px;
2082 padding: 0 10px;
2083 cursor: pointer;
2084}
2085
2086.folder-pick:hover {
2087 border-color: var(--muted);
2088}
2089
2090.folder-pick .fp-icon {
2091 width: 16px;
2092 height: 16px;
2093 color: var(--muted);
2094 flex: none;
2095}
2096
2097.folder-pick .fp-path {
2098 overflow: hidden;
2099 text-overflow: ellipsis;
2100 white-space: nowrap;
2101}
2102
2103/* Run / stop ------------------------------------------------------- */
2104
2105.btn-run {
2106 border: 1px solid var(--accent-border);
2107 background: var(--accent);
2108 color: #fff;
2109 font: inherit;
2110 font-size: 13px;
2111 font-weight: 600;
2112 padding: 0 18px;
2113 cursor: pointer;
2114}
2115
2116.btn-run:hover {
2117 background: var(--accent-hover);
2118}
2119
2120.btn-stop {
2121 display: flex;
2122 align-items: center;
2123 gap: 6px;
2124 border: 1px solid var(--danger-border);
2125 background: var(--danger);
2126 color: #fff;
2127 font: inherit;
2128 font-size: 13px;
2129 font-weight: 600;
2130 padding: 0 14px;
2131 cursor: pointer;
2132}
2133
2134.btn-stop:hover {
2135 opacity: 0.9;
2136}
2137
2138.btn-stop .ic-stop {
2139 width: 14px;
2140 height: 14px;
2141}
2142
2143/* Status line ------------------------------------------------------ */
2144
2145.status-line {
2146 display: flex;
2147 align-items: center;
2148 gap: 8px;
2149 font-size: 13px;
2150 color: var(--muted);
2151}
2152
2153.status-line .dot {
2154 width: 8px;
2155 height: 8px;
2156 background: var(--accent);
2157 animation: search-pulse 1s ease-in-out infinite;
2158}
2159
2160@keyframes search-pulse {
2161 0%,
2162 100% {
2163 opacity: 1;
2164 }
2165 50% {
2166 opacity: 0.3;
2167 }
2168}
2169
2170.status-line .skip {
2171 opacity: 0.75;
2172}
2173
2174/* Group labels ----------------------------------------------------- */
2175
2176.group-label {
2177 display: flex;
2178 align-items: center;
2179 gap: 8px;
2180 font-size: 12px;
2181 font-weight: 600;
2182 letter-spacing: 0.04em;
2183 text-transform: uppercase;
2184 color: var(--muted);
2185}
2186
2187/* Result group: a `display:flex` (or `none`, from the inline style) flex
2188 item of `.search-view` that re-establishes the column layout for its
2189 label/list/more-row children. */
2190.result-group {
2191 flex-direction: column;
2192 gap: 14px;
2193}
2194
2195.group-label .gl-icon {
2196 width: 15px;
2197 height: 15px;
2198}
2199
2200.group-label .n {
2201 font-weight: 400;
2202 text-transform: none;
2203 letter-spacing: 0;
2204}
2205
2206.group-label .grow {
2207 flex: 1;
2208}
2209
2210.collapse-all {
2211 appearance: none;
2212 border: 1px solid var(--border);
2213 background: transparent;
2214 color: var(--muted);
2215 font: inherit;
2216 font-size: 12px;
2217 text-transform: none;
2218 letter-spacing: 0;
2219 padding: 3px 10px;
2220 cursor: pointer;
2221}
2222
2223.collapse-all:hover {
2224 background: var(--bg);
2225 color: var(--text);
2226}
2227
2228/* Name-result rows --------------------------------------------------- */
2229
2230.srow {
2231 display: grid;
2232 /* icon | name+dir | size | goto */
2233 grid-template-columns: 28px minmax(0, 1fr) 90px 30px;
2234 gap: 12px;
2235 align-items: center;
2236 padding: 9px 14px;
2237 border-bottom: 1px solid var(--border);
2238 cursor: pointer;
2239 font-size: 14px;
2240 user-select: none;
2241}
2242
2243.srow:last-child {
2244 border-bottom: none;
2245}
2246
2247.srow:hover {
2248 background: var(--bg);
2249}
2250
2251.srow-icon {
2252 width: 20px;
2253 height: 20px;
2254}
2255
2256.srow-main {
2257 min-width: 0;
2258 display: flex;
2259 flex-direction: column;
2260 line-height: 1.3;
2261}
2262
2263.srow-name {
2264 overflow: hidden;
2265 text-overflow: ellipsis;
2266 white-space: nowrap;
2267}
2268
2269/* Highlighted query hits (plain fragments render as bare spans). */
2270mark.hl-hit {
2271 background: color-mix(in srgb, var(--accent) 25%, transparent);
2272 color: inherit;
2273}
2274
2275/* The directory-path line (the name line lives inside .srow-name). */
2276.srow-dir {
2277 font-size: 12px;
2278 color: var(--muted);
2279 overflow: hidden;
2280 text-overflow: ellipsis;
2281 white-space: nowrap;
2282}
2283
2284.srow-dir mark.hl-hit {
2285 color: inherit;
2286}
2287
2288.ssize {
2289 font-size: 13px;
2290 color: var(--muted);
2291 text-align: right;
2292}
2293
2294/* Paging footer: position in the results on the left, the action on the
2295 right. Separated from the list by its own border so the button never reads
2296 as another result row. */
2297.page-foot {
2298 display: flex;
2299 flex-wrap: wrap;
2300 align-items: center;
2301 justify-content: space-between;
2302 gap: 12px;
2303 padding: 10px 14px;
2304 border-top: 1px solid var(--border);
2305 font-size: 12px;
2306 color: var(--muted);
2307}
2308
2309.page-foot .pf-count {
2310 font-variant-numeric: tabular-nums;
2311}
2312
2313.page-foot .pf-more,
2314.pf-btn {
2315 appearance: none;
2316 padding: 5px 12px;
2317 border: 1px solid var(--border);
2318 background: var(--panel);
2319 font: inherit;
2320 font-weight: 500;
2321 color: var(--text);
2322 cursor: pointer;
2323}
2324
2325.page-foot .pf-more:hover,
2326.pf-btn:hover:not(:disabled) {
2327 border-color: var(--accent);
2328 color: var(--accent);
2329}
2330
2331.pf-pages {
2332 display: flex;
2333 align-items: center;
2334 gap: 4px;
2335 margin-right: auto;
2336}
2337
2338.pf-btn {
2339 min-width: 30px;
2340 padding: 5px 8px;
2341 font-variant-numeric: tabular-nums;
2342}
2343
2344.pf-btn.current {
2345 border-color: var(--accent);
2346 background: var(--accent);
2347 color: #fff;
2348}
2349
2350.pf-btn:disabled {
2351 opacity: 0.4;
2352 cursor: default;
2353}
2354
2355.pf-goto {
2356 display: flex;
2357 align-items: center;
2358 gap: 6px;
2359 margin: 0;
2360}
2361
2362.pf-goto input {
2363 width: 72px;
2364 padding: 5px 8px;
2365}
2366
2367/* An arrow-key page turn is loading. Fixed at the top, so it also shows over
2368 the preview. The delay keeps fast turns from flashing. */
2369.page-busy {
2370 position: fixed;
2371 top: 0;
2372 left: 0;
2373 right: 0;
2374 height: 3px;
2375 overflow: hidden;
2376 z-index: 250;
2377 pointer-events: none;
2378 animation: page-busy-in 0s 200ms both;
2379}
2380
2381.page-busy::after {
2382 content: "";
2383 display: block;
2384 width: 30%;
2385 height: 100%;
2386 background: var(--accent);
2387 animation: upload-indeterminate 1.2s ease-in-out infinite;
2388}
2389
2390body:has(.page-busy) {
2391 cursor: progress;
2392}
2393
2394@keyframes page-busy-in {
2395 from {
2396 opacity: 0;
2397 }
2398 to {
2399 opacity: 1;
2400 }
2401}
2402
2403/* Content matches ----------------------------------------------------- */
2404
2405.match-card {
2406 background: var(--panel);
2407 border: 1px solid var(--border);
2408 border-top: none;
2409}
2410
2411/* The first card is the one right after the group label; `:first-of-type`
2412 cannot be used because the label is an earlier `div` sibling. */
2413.group-label + .match-card {
2414 border-top: 1px solid var(--border);
2415}
2416
2417/* The header opens the file; the matched lines below are text to read. */
2418.match-head {
2419 display: flex;
2420 align-items: center;
2421 gap: 10px;
2422 padding: 9px 14px;
2423 font-size: 14px;
2424 cursor: pointer;
2425}
2426
2427.match-head:hover {
2428 background: var(--bg);
2429}
2430
2431.match-head .mi {
2432 width: 18px;
2433 height: 18px;
2434 color: var(--muted);
2435 flex: none;
2436}
2437
2438/* Takes the slack, so the count and the two buttons sit at the right edge. */
2439.mpath {
2440 flex: 1;
2441 min-width: 0;
2442 overflow: hidden;
2443 text-overflow: ellipsis;
2444 white-space: nowrap;
2445}
2446
2447.mpath b {
2448 font-weight: 600;
2449}
2450
2451.mcount {
2452 flex: none;
2453 font-size: 12px;
2454 color: var(--muted);
2455}
2456
2457/* "Show in folder", in both the name rows and the match-card headers. */
2458.goto-btn {
2459 appearance: none;
2460 flex: none;
2461 justify-self: end;
2462 display: flex;
2463 align-items: center;
2464 justify-content: center;
2465 width: 26px;
2466 height: 26px;
2467 padding: 0;
2468 border: 1px solid transparent;
2469 background: transparent;
2470 cursor: pointer;
2471}
2472
2473.goto-btn:hover {
2474 border-color: var(--border);
2475}
2476
2477.goto-btn .goto-ic {
2478 width: 16px;
2479 height: 16px;
2480 color: var(--muted);
2481}
2482
2483.goto-btn:hover .goto-ic {
2484 color: var(--accent);
2485}
2486
2487.chev {
2488 appearance: none;
2489 border: 1px solid var(--border);
2490 background: transparent;
2491 width: 26px;
2492 height: 26px;
2493 display: flex;
2494 align-items: center;
2495 justify-content: center;
2496 cursor: pointer;
2497 flex: none;
2498}
2499
2500.chev:hover {
2501 background: var(--bg);
2502}
2503
2504.chev .chev-down,
2505.chev .chev-up {
2506 display: block;
2507 width: 10px;
2508 height: 10px;
2509 border-right: 2px solid var(--muted);
2510 border-bottom: 2px solid var(--muted);
2511}
2512
2513.chev .chev-down {
2514 transform: rotate(45deg) translate(-1px, -1px);
2515}
2516
2517.chev .chev-up {
2518 transform: rotate(-135deg) translate(-1px, -1px);
2519}
2520
2521.match-lines {
2522 border-top: 1px solid var(--border);
2523 background: var(--bg);
2524 font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
2525 font-size: 12.5px;
2526}
2527
2528/* Matched lines are text to read, not a control: the card header is what
2529 opens the file, so no pointer cursor and no hover here. */
2530.mline {
2531 display: grid;
2532 grid-template-columns: 52px minmax(0, 1fr);
2533 gap: 12px;
2534 padding: 5px 14px;
2535 border-bottom: 1px solid var(--border);
2536 white-space: pre;
2537 user-select: text;
2538}
2539
2540.mline:last-child {
2541 border-bottom: none;
2542}
2543
2544.mline .ln {
2545 color: var(--muted);
2546 text-align: right;
2547 user-select: none;
2548}
2549
2550.mline .tx {
2551 overflow: hidden;
2552 text-overflow: ellipsis;
2553}
2554
2555.search-empty {
2556 padding: 48px 20px;
2557 text-align: center;
2558 color: var(--muted);
2559 font-size: 14px;
2560}
2561
2562/* ------------------------------------------------------------------ */
2563/* Uploads: overlay panel + log view */
2564/* ------------------------------------------------------------------ */
2565
2566.upload-panel {
2567 position: fixed;
2568 right: 20px;
2569 bottom: 20px;
2570 z-index: 190;
2571 width: min(380px, calc(100vw - 40px));
2572 background: var(--panel);
2573 color: var(--text);
2574 border: 1px solid var(--border);
2575 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
2576 font-size: 13px;
2577}
2578
2579/* The toast sits above the panel when both show. */
2580.upload-panel ~ .toast,
2581.toast:has(~ .upload-panel) {
2582 bottom: 80px;
2583}
2584
2585.upload-panel-head {
2586 display: flex;
2587 align-items: center;
2588 gap: 8px;
2589 padding: 8px 10px;
2590 border-bottom: 1px solid var(--border);
2591}
2592
2593.upload-panel.collapsed .upload-panel-head {
2594 border-bottom: none;
2595}
2596
2597.upload-panel-title {
2598 font-weight: 600;
2599}
2600
2601.upload-panel-summary {
2602 margin-left: auto;
2603 font-size: 12px;
2604}
2605
2606.upload-panel-list {
2607 max-height: 50vh;
2608 overflow-y: auto;
2609}
2610
2611.upload-job {
2612 padding: 8px 10px;
2613 border-bottom: 1px solid var(--border);
2614}
2615
2616.upload-job:last-child {
2617 border-bottom: none;
2618}
2619
2620.upload-job-head {
2621 display: flex;
2622 align-items: center;
2623 gap: 4px;
2624}
2625
2626.upload-job-label {
2627 flex: 1;
2628 min-width: 0;
2629 overflow: hidden;
2630 text-overflow: ellipsis;
2631 white-space: nowrap;
2632 font-weight: 500;
2633}
2634
2635.upload-job-pct {
2636 font-variant-numeric: tabular-nums;
2637 margin-right: 4px;
2638}
2639
2640.upload-bar {
2641 height: 4px;
2642 margin: 6px 0 4px;
2643 /* Clip the indeterminate block: it slides past both ends. */
2644 overflow: hidden;
2645 background: color-mix(in srgb, var(--accent) 15%, var(--panel));
2646}
2647
2648.upload-bar-fill {
2649 height: 100%;
2650 background: var(--accent);
2651 transition: width 0.2s linear;
2652}
2653
2654/* Preparing: nothing to measure yet, so a sliding block instead of a fill. */
2655.upload-bar.indeterminate .upload-bar-fill {
2656 width: 30% !important;
2657 animation: upload-indeterminate 1.2s ease-in-out infinite;
2658}
2659
2660@keyframes upload-indeterminate {
2661 0% {
2662 transform: translateX(-100%);
2663 }
2664 100% {
2665 transform: translateX(333%);
2666 }
2667}
2668
2669.upload-job.failed .upload-bar-fill {
2670 background: var(--danger);
2671}
2672
2673.upload-job.done .upload-bar-fill {
2674 background: var(--muted);
2675}
2676
2677.upload-job-state {
2678 font-size: 12px;
2679 overflow: hidden;
2680 text-overflow: ellipsis;
2681 white-space: nowrap;
2682}
2683
2684.upload-job.failed .upload-job-state {
2685 color: var(--danger);
2686 white-space: normal;
2687}
2688
2689.upload-conflict {
2690 display: flex;
2691 flex-direction: column;
2692 gap: 6px;
2693 margin-top: 8px;
2694 padding: 8px;
2695 border: 1px solid var(--danger-border);
2696 background: color-mix(in srgb, var(--danger) 8%, var(--panel));
2697}
2698
2699.upload-conflict-all {
2700 display: flex;
2701 /* The label wraps on a narrow dialog; keep the box on the first line. */
2702 align-items: flex-start;
2703 gap: 0.5rem;
2704 font-size: 12px;
2705 cursor: pointer;
2706}
2707
2708.upload-conflict-all input {
2709 flex: none;
2710 margin-top: 2px;
2711}
2712
2713.modal .upload-conflict-all {
2714 margin: 8px 0 0;
2715 font-size: 13px;
2716}
2717
2718.upload-conflict-actions {
2719 display: flex;
2720 justify-content: flex-end;
2721 gap: 6px;
2722}
2723
2724.upload-graph {
2725 margin-top: 8px;
2726 color: var(--accent);
2727 border: 1px solid var(--border);
2728 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
2729}
2730
2731/* Peak label above the plot, so the line can never run through it. */
2732.upload-graph-max {
2733 display: block;
2734 text-align: right;
2735 padding: 2px 6px 0;
2736 font-size: 11px;
2737 line-height: 14px;
2738}
2739
2740.upload-graph svg {
2741 display: block;
2742 width: 100%;
2743 height: 46px;
2744}
2745
2746.upload-errors {
2747 margin: 6px 0 0;
2748 padding-left: 18px;
2749 font-size: 12px;
2750 color: var(--danger);
2751}
2752
2753.upload-err-file {
2754 font-weight: 500;
2755}
2756
2757/* Log view: the same rows, full width, roomier. */
2758.uploads-view .upload-log {
2759 border: 1px solid var(--border);
2760 background: var(--panel);
2761 font-size: 14px;
2762}
2763
2764.uploads-view .upload-job {
2765 padding: 12px 14px;
2766}
2767
2768.uploads-view .upload-graph svg {
2769 height: 76px;
2770}
2771
2772/* Security settings tab: password, passkeys, sign-in requirement. */
2773
2774.security-tab {
2775 display: flex;
2776 flex-direction: column;
2777 gap: 10px;
2778}
2779
2780.security-tab h3 {
2781 margin: 10px 0 0;
2782}
2783
2784/* Matches .excludes-field, so the three blocks of this tab read as one list. */
2785.security-form {
2786 display: flex;
2787 flex-direction: column;
2788 gap: 10px;
2789 padding: 14px;
2790 border: 1px solid var(--border);
2791}
2792
2793.security-form .field input {
2794 width: 100%;
2795 box-sizing: border-box;
2796}
2797
2798.passkey-list {
2799 display: flex;
2800 flex-direction: column;
2801 gap: 2px;
2802 padding: 0 14px;
2803}
2804
2805.passkey-row {
2806 display: flex;
2807 align-items: center;
2808 gap: 8px;
2809 padding: 6px 0;
2810}
2811
2812.passkey-main {
2813 flex: 1;
2814 overflow: hidden;
2815}
2816
2817.passkey-name {
2818 display: block;
2819 font-size: 13px;
2820 overflow: hidden;
2821 text-overflow: ellipsis;
2822 white-space: nowrap;
2823}
2824
2825/* "Needs your user name": a note, not an error. This passkey works, it just
2826 cannot be found without the name typed in first. */
2827.passkey-warning {
2828 display: inline-block;
2829 margin-top: 2px;
2830 padding: 1px 6px;
2831 border: 1px solid var(--border);
2832 font-size: 11px;
2833 color: var(--muted);
2834}
2835
2836/* The sign-in requirement's labels are whole phrases, so the shared 190px
2837 cap for setting selects would clip them. */
2838.security-tab .setting-row-select select {
2839 max-width: none;
2840 width: auto;
2841}
2842
2843/* Separator between the password form and the passkey button. `.auth-card p`
2844 sets a bottom-only margin on every paragraph in the card, and wins on
2845 specificity, so both of these have to name the card too. */
2846.auth-card .login-or {
2847 margin: 16px 0 8px;
2848 font-size: 12px;
2849 color: var(--muted);
2850 text-transform: lowercase;
2851}
2852
2853.auth-card .setting-desc {
2854 margin-top: 8px;
2855}
2856
2857.auth-card .btn {
2858 width: 100%;
2859}
2860