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-wrap {
887 position: relative;
888}
889
890.sel-more-menu {
891 position: absolute;
892 right: 0;
893 top: calc(100% + 6px);
894 z-index: 100;
895 min-width: 190px;
896 background: var(--panel);
897 border: 1px solid var(--border);
898 padding: 5px;
899 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
900}
901
902.sel-more-menu.hidden {
903 display: none;
904}
905
906/* selection actions in the top bar */
907
908.topbar .sel-actions {
909 min-height: 0;
910}
911
912.topbar .sel-actions .icon-btn {
913 padding: 5px 7px;
914}
915
916/* selection highlight in the grid and list */
917
918.tile.selected {
919 border-color: var(--accent);
920 background: color-mix(in srgb, var(--accent) 12%, var(--panel));
921}
922
923.row.selected {
924 background: color-mix(in srgb, var(--accent) 12%, var(--panel));
925 box-shadow: inset 2px 0 0 var(--accent);
926}
927
928/* settings tabs (profile / server) */
929
930.settings-tabs {
931 display: flex;
932 gap: 8px;
933}
934
935.settings-tab-btn {
936 padding: 7px 14px;
937 border: 1px solid var(--border);
938 background: var(--panel);
939 color: var(--muted);
940 font-size: 13px;
941 font-weight: 600;
942}
943
944.settings-tab-btn:hover {
945 border-color: var(--muted);
946 color: var(--text);
947}
948
949.settings-tab-btn.active {
950 border-color: var(--accent);
951 color: var(--accent);
952 background: color-mix(in srgb, var(--accent) 10%, var(--panel));
953}
954
955/* toast */
956
957.toast {
958 position: fixed;
959 bottom: 20px;
960 right: 20px;
961 z-index: 200;
962 background: var(--panel);
963 color: var(--text);
964 border: 1px solid var(--border);
965 padding: 11px 16px;
966 font-size: 14px;
967 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
968 max-width: 320px;
969}
970
971.toast-error {
972 background: color-mix(in srgb, var(--danger) 12%, var(--panel));
973 border-color: var(--danger-border);
974 color: var(--danger);
975}
976
977
978/* ---- modals ---- */
979
980/* The shell is a native <dialog> opened with showModal(): the browser puts
981 it in the top layer and paints ::backdrop, so there is no overlay element
982 to position and no z-index to keep in order. The reset below strips the
983 UA's own box so the .modal card inside is the only thing that shows. */
984.modal-host {
985 padding: 0;
986 border: none;
987 background: none;
988 /* The UA caps an open dialog's size; the card sets its own. */
989 max-width: none;
990 max-height: none;
991 /* Centre it in the inset-0 box, as the old flex overlay did. */
992 margin: auto;
993 overflow: visible;
994}
995
996.modal-host::backdrop {
997 background: rgb(0 0 0 / 45%);
998}
999
1000.modal {
1001 width: 440px;
1002 max-width: calc(100vw - 32px);
1003 padding: 20px;
1004 background: var(--panel);
1005 border: 1px solid var(--border);
1006 box-shadow: 0 12px 44px rgb(0 0 0 / 35%);
1007}
1008
1009.modal.picker {
1010 width: 640px;
1011}
1012
1013.modal-title {
1014 margin: 0 0 12px;
1015 font-size: 16px;
1016}
1017
1018.modal-label {
1019 display: block;
1020 font-size: 13px;
1021 color: var(--muted);
1022 margin-bottom: 6px;
1023}
1024
1025.modal .input {
1026 width: 100%;
1027 box-sizing: border-box;
1028 margin-bottom: 16px;
1029}
1030
1031.modal-label.error,
1032.dialog-error {
1033 color: var(--danger);
1034 font-size: 13px;
1035}
1036
1037.dialog-error {
1038 margin-top: 8px;
1039}
1040
1041.modal-message {
1042 white-space: pre-line;
1043 font-size: 14px;
1044 margin: 0 0 8px;
1045}
1046
1047.modal-actions {
1048 display: flex;
1049 justify-content: flex-end;
1050 gap: 8px;
1051 margin-top: 16px;
1052}
1053
1054.btn-danger {
1055 background: var(--danger);
1056 /* A visible border (one step away from the fill, per theme) so the
1057 button's edge reads the same as on neutral buttons. */
1058 border-color: var(--danger-border);
1059 color: #fff;
1060}
1061
1062.btn-danger:hover {
1063 background: color-mix(in srgb, var(--danger) 85%, black);
1064}
1065
1066.btn:disabled {
1067 opacity: 0.5;
1068 cursor: not-allowed;
1069}
1070
1071.conflict-list {
1072 max-height: 200px;
1073 overflow-y: auto;
1074 margin: 8px 0;
1075 padding-left: 20px;
1076 font-size: 13px;
1077}
1078
1079/* Folder picker: roots on the left, the selected root's tree on the right. */
1080
1081.picker-body {
1082 display: grid;
1083 grid-template-columns: 180px minmax(0, 1fr);
1084 border: 1px solid var(--border);
1085 height: 320px;
1086}
1087
1088.picker-roots {
1089 display: flex;
1090 flex-direction: column;
1091 gap: 2px;
1092 padding: 6px;
1093 border-right: 1px solid var(--border);
1094 background: var(--bg);
1095 min-height: 0;
1096 overflow-y: auto;
1097}
1098
1099.picker-root {
1100 display: flex;
1101 align-items: center;
1102 justify-content: flex-start;
1103 gap: 8px;
1104 padding: 7px 8px;
1105 border: none;
1106 background: transparent;
1107 color: var(--text);
1108 font: inherit;
1109 font-size: 13px;
1110 text-align: left;
1111 cursor: pointer;
1112 min-width: 0;
1113}
1114
1115.picker-root:hover {
1116 background: var(--panel);
1117}
1118
1119.picker-root.active {
1120 background: var(--accent);
1121 color: #fff;
1122}
1123
1124.picker-root.ro .picker-root-name {
1125 opacity: 0.6;
1126 text-decoration: line-through dotted;
1127}
1128
1129.picker-root-ic {
1130 width: 16px;
1131 height: 16px;
1132 flex: none;
1133}
1134
1135.picker-root-name {
1136 overflow: hidden;
1137 text-overflow: ellipsis;
1138 white-space: nowrap;
1139}
1140
1141.picker-main {
1142 display: flex;
1143 flex-direction: column;
1144 min-width: 0;
1145 /* Grid items default to min-height:auto, which would let a long
1146 folder list stretch the dialog instead of scrolling. */
1147 min-height: 0;
1148}
1149
1150.picker-crumbs {
1151 padding: 8px 10px;
1152 font-size: 13px;
1153 border-bottom: 1px solid var(--border);
1154 flex: none;
1155}
1156
1157.picker-entries {
1158 flex: 1;
1159 min-height: 0;
1160 overflow-y: auto;
1161}
1162
1163.picker-row {
1164 display: flex;
1165 align-items: center;
1166 gap: 10px;
1167 padding: 8px 12px;
1168 cursor: pointer;
1169 font-size: 14px;
1170 user-select: none;
1171}
1172
1173.picker-row:hover {
1174 background: var(--bg);
1175}
1176
1177.picker-row .row-name {
1178 flex: 1;
1179}
1180
1181/* A right-pointing chevron drawn with borders (no icon needed). */
1182.picker-chev {
1183 width: 7px;
1184 height: 7px;
1185 border-right: 1.5px solid var(--muted);
1186 border-bottom: 1.5px solid var(--muted);
1187 transform: rotate(-45deg);
1188 flex: none;
1189}
1190
1191.picker-empty {
1192 height: 100%;
1193 display: flex;
1194 flex-direction: column;
1195 align-items: center;
1196 justify-content: center;
1197 gap: 8px;
1198 font-size: 13px;
1199}
1200
1201.picker-empty-ic {
1202 width: 32px;
1203 height: 32px;
1204 opacity: 0.5;
1205}
1206
1207.picker-root.active .picker-root-ic {
1208 color: #fff;
1209}
1210
1211@media (max-width: 640px) {
1212 .picker-body {
1213 grid-template-columns: 1fr;
1214 grid-template-rows: auto minmax(0, 1fr);
1215 height: 380px;
1216 }
1217
1218 .picker-roots {
1219 flex-direction: row;
1220 flex-wrap: wrap;
1221 border-right: none;
1222 border-bottom: 1px solid var(--border);
1223 }
1224}
1225
1226/* ---------------------------------------------------------------------------
1227 * Full-page file view (preview / editor / "no preview"). The top bar carries
1228 * the title + actions; the content area below is edge to edge.
1229 * ------------------------------------------------------------------------- */
1230.content.file-open {
1231 padding: 0;
1232 overflow: hidden;
1233}
1234
1235/* The browser stays mounted (state, fetch callback) but hidden. */
1236.content.file-open .browser {
1237 display: none;
1238}
1239
1240.file-view {
1241 flex: 1;
1242 min-height: 0;
1243 display: flex;
1244 flex-direction: column;
1245}
1246
1247.file-title {
1248 display: flex;
1249 align-items: center;
1250 gap: 8px;
1251 min-width: 0;
1252}
1253
1254.file-title-icon {
1255 width: 18px;
1256 height: 18px;
1257 flex: none;
1258 color: var(--muted);
1259}
1260
1261.file-title-name {
1262 font-weight: 600;
1263 font-size: 14px;
1264 white-space: nowrap;
1265 overflow: hidden;
1266 text-overflow: ellipsis;
1267}
1268
1269.file-actions {
1270 display: flex;
1271 align-items: center;
1272 gap: 10px;
1273 flex: none;
1274}
1275
1276.file-close,
1277.file-download {
1278 border: none;
1279 background: transparent;
1280 color: var(--muted);
1281 padding: 6px 8px;
1282}
1283
1284.file-close:hover,
1285.file-download:hover {
1286 color: var(--text);
1287 background: var(--bg);
1288}
1289.preview-body {
1290 flex: 1;
1291 min-height: 0;
1292 overflow: auto;
1293 display: flex;
1294 background: var(--bg);
1295}
1296.preview-img {
1297 max-width: 100%;
1298 max-height: 100%;
1299 display: block;
1300 margin: auto;
1301}
1302.preview-pdf {
1303 width: 100%;
1304 height: 100%;
1305 border: none;
1306 background: #fff;
1307}
1308.preview-video {
1309 width: 100%;
1310 max-height: 100%;
1311 background: #000;
1312}
1313.preview-audio {
1314 display: flex;
1315 align-items: center;
1316 justify-content: center;
1317 width: 100%;
1318 padding: 24px;
1319}
1320.preview-audio audio {
1321 width: 100%;
1322 max-width: 480px;
1323}
1324
1325/* CodeMirror (text editor) */
1326.cm-container {
1327 height: 100%;
1328}
1329.cm-container .cm-editor {
1330 height: 100%;
1331}
1332/* Load/save failure: dims the editor and shows the message in a card
1333 * (the top-bar Save button stays reachable to retry). */
1334.cm-error {
1335 position: absolute;
1336 inset: 0;
1337 z-index: 5;
1338 display: flex;
1339 align-items: center;
1340 justify-content: center;
1341 padding: 24px;
1342 background: rgb(0 0 0 / 45%);
1343}
1344
1345.cm-error-card {
1346 position: relative;
1347 max-width: 440px;
1348 min-width: 200px;
1349 background: var(--panel);
1350 border: 1px solid var(--border);
1351 box-shadow: 0 12px 44px rgb(0 0 0 / 35%);
1352}
1353
1354.cm-error-close {
1355 position: absolute;
1356 top: 7px;
1357 right: 7px;
1358 display: inline-flex;
1359 align-items: center;
1360 justify-content: center;
1361 border: none;
1362 background: transparent;
1363 color: var(--muted);
1364 padding: 3px;
1365}
1366
1367.cm-error-close:hover {
1368 color: var(--text);
1369 background: var(--bg);
1370}
1371
1372.cm-error-close .ic-btn {
1373 width: 16px;
1374 height: 16px;
1375}
1376
1377.cm-error-text {
1378 padding: 16px 40px 16px 20px;
1379 font-size: 14px;
1380 text-align: center;
1381 overflow-wrap: anywhere;
1382}
1383
1384/* ---------------------------------------------------------------------------
1385 * Text editor (full page; title/save/close live in the top bar)
1386 * ------------------------------------------------------------------------- */
1387.file-editor {
1388 flex: 1;
1389 min-height: 0;
1390 display: flex;
1391 position: relative;
1392 background: var(--bg);
1393}
1394.file-editor .cm-container {
1395 width: 100%;
1396 height: 100%;
1397}
1398/* "No preview" fallback (binaries, archives). */
1399.file-nopreview {
1400 flex: 1;
1401 display: flex;
1402 align-items: center;
1403 justify-content: center;
1404}
1405.file-nopreview .btn-primary {
1406 margin-top: 14px;
1407}
1408/* ---------------------------------------------------------------------------
1409 * Download format dialog
1410 * ------------------------------------------------------------------------- */
1411.dl-formats {
1412 display: flex;
1413 flex-direction: column;
1414 gap: 8px;
1415 margin-bottom: 8px;
1416}
1417.dl-fmt {
1418 justify-content: flex-start;
1419}
1420
1421/* ---------------------------------------------------------------------------
1422 * Item info dialog
1423 * ------------------------------------------------------------------------- */
1424.info-list {
1425 margin: 8px 0 16px;
1426 display: grid;
1427 grid-template-columns: 90px 1fr;
1428 row-gap: 8px;
1429 column-gap: 12px;
1430}
1431.info-list dt {
1432 color: var(--muted);
1433}
1434.info-list dd {
1435 margin: 0;
1436 word-break: break-word;
1437}
1438
1439/* ---------------------------------------------------------------------------
1440 Shares (milestone 6)
1441 --------------------------------------------------------------------------- */
1442
1443.share-modal {
1444 width: 480px;
1445}
1446
1447.share-link-row {
1448 display: flex;
1449 gap: 8px;
1450 margin: 12px 0;
1451}
1452
1453.share-link-input {
1454 flex: 1;
1455 font-family: ui-monospace, monospace;
1456 font-size: 12.5px;
1457 padding: 8px 10px;
1458 border: 1px solid var(--border);
1459 background: var(--bg);
1460 color: var(--text);
1461 min-width: 0;
1462}
1463
1464.share-check {
1465 display: flex;
1466 align-items: center;
1467 gap: 8px;
1468 margin: 4px 0 14px;
1469 font-size: 14px;
1470}
1471
1472.share-check input {
1473 width: 16px;
1474 height: 16px;
1475}
1476
1477.share-expiry-row {
1478 display: flex;
1479 align-items: center;
1480 gap: 10px;
1481 margin: 0 0 10px;
1482}
1483
1484.share-expiry-row label {
1485 font-size: 13px;
1486 color: var(--muted);
1487}
1488
1489.share-expiry-row select,
1490.share-expiry-row input {
1491 flex: 1;
1492 padding: 8px 10px;
1493 border: 1px solid var(--border);
1494 background: var(--panel);
1495 color: var(--text);
1496 font: inherit;
1497}
1498
1499/* Optional password on the create-share form. Laid out like the expiry
1500 row above it, so the form reads as one column of labelled fields. */
1501.share-pw-row {
1502 display: flex;
1503 align-items: center;
1504 gap: 10px;
1505 margin: 0 0 6px;
1506}
1507
1508.share-pw-row label {
1509 font-size: 13px;
1510 color: var(--muted);
1511}
1512
1513/* Expiry and password sit in one column, so their inputs line up. */
1514.share-expiry-row label,
1515.share-pw-row label {
1516 min-width: 64px;
1517}
1518
1519.share-pw-row input {
1520 flex: 1;
1521 padding: 8px 10px;
1522 border: 1px solid var(--border);
1523 background: var(--panel);
1524 color: var(--text);
1525 font: inherit;
1526}
1527
1528.share-pw-hint {
1529 font-size: 12px;
1530 margin: 0 0 10px;
1531}
1532
1533/* The password gate of a protected share: the whole page is this card. */
1534.unlock-card {
1535 width: 320px;
1536 max-width: calc(100vw - 32px);
1537 padding: 20px;
1538 text-align: left;
1539}
1540
1541.unlock-card h2 {
1542 margin: 0 0 6px;
1543 font-size: 18px;
1544}
1545
1546.unlock-card input {
1547 margin-top: 14px;
1548}
1549
1550.unlock-btn {
1551 width: 100%;
1552 margin-top: 12px;
1553}
1554
1555.share-expiry-note {
1556 margin: 4px 0 0;
1557 font-size: 12.5px;
1558}
1559
1560.btn-sm {
1561 padding: 5px 10px;
1562 font-size: 12.5px;
1563}
1564
1565.icon-btn-sm {
1566 padding: 4px 6px;
1567}
1568
1569/* "Your shares" manage view */
1570
1571.shares-view {
1572 width: 100%;
1573 max-width: 960px;
1574 margin: 0 auto;
1575}
1576
1577.view-header {
1578 margin-bottom: 16px;
1579}
1580
1581.view-header .admin-section-title {
1582 margin-bottom: 0;
1583}
1584
1585.view-hint {
1586 margin: 4px 0 0;
1587 font-size: 13px;
1588 max-width: 64ch;
1589}
1590
1591.share-list {
1592 display: flex;
1593 flex-direction: column;
1594 gap: 10px;
1595}
1596
1597.share-item {
1598 display: flex;
1599 align-items: center;
1600 justify-content: space-between;
1601 gap: 12px;
1602 padding: 10px 12px;
1603 border: 1px solid var(--border);
1604 background: var(--panel);
1605}
1606
1607.share-item-main {
1608 min-width: 0;
1609}
1610
1611.share-item-name {
1612 display: flex;
1613 align-items: center;
1614 gap: 8px;
1615 font-weight: 600;
1616 font-size: 14px;
1617 overflow: hidden;
1618 text-overflow: ellipsis;
1619 white-space: nowrap;
1620}
1621
1622.ic-row {
1623 width: 18px;
1624 height: 18px;
1625 flex: none;
1626}
1627
1628.share-item-meta {
1629 font-size: 12px;
1630 margin-top: 2px;
1631 overflow: hidden;
1632 text-overflow: ellipsis;
1633 white-space: nowrap;
1634}
1635
1636.share-item-actions {
1637 display: flex;
1638 gap: 6px;
1639 flex-shrink: 0;
1640}
1641
1642/* Sections of the share management view: the viewer's own links and, for an
1643 admin, one section per account. Native <details>/<summary>. */
1644
1645.share-groups {
1646 display: flex;
1647 flex-direction: column;
1648 gap: 12px;
1649}
1650
1651.share-group {
1652 border: 1px solid var(--border);
1653 background: var(--panel);
1654}
1655
1656.share-group-head {
1657 display: flex;
1658 align-items: center;
1659 gap: 8px;
1660 padding: 10px 12px;
1661 font-size: 14px;
1662 font-weight: 600;
1663 cursor: pointer;
1664 /* The caret below stands in for the disclosure triangle. */
1665 list-style: none;
1666}
1667
1668.share-group-head::-webkit-details-marker {
1669 display: none;
1670}
1671
1672.share-group-head:hover {
1673 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
1674}
1675
1676.share-group-head:focus-visible {
1677 outline: 2px solid var(--accent);
1678 outline-offset: -2px;
1679}
1680
1681.share-caret {
1682 width: 16px;
1683 height: 16px;
1684 flex: none;
1685 transition: rotate 0.15s ease;
1686}
1687
1688.share-group[open] .share-caret {
1689 rotate: 90deg;
1690}
1691
1692.share-group-count {
1693 font-size: 12px;
1694 font-weight: 400;
1695}
1696
1697/* A live link whose account cannot sign in any more. */
1698.share-group-warn {
1699 font-size: 12px;
1700 font-weight: 400;
1701 color: var(--danger);
1702}
1703
1704.share-group-del {
1705 margin-left: auto;
1706}
1707
1708/* Rows inside a section read as one block: the border moves from each row to
1709 the section. */
1710.share-group .share-list {
1711 gap: 0;
1712 border-top: 1px solid var(--border);
1713}
1714
1715.share-group .share-item {
1716 border: 0;
1717 border-top: 1px solid var(--border);
1718}
1719
1720.share-group .share-item:first-child {
1721 border-top: 0;
1722}
1723
1724/* ------------------------------------------------------------------ */
1725/* Admin (milestone 7) */
1726/* ------------------------------------------------------------------ */
1727
1728.admin-view {
1729 display: flex;
1730 flex-direction: column;
1731 gap: 16px;
1732 width: 100%;
1733 max-width: 960px;
1734 margin: 0 auto;
1735}
1736
1737.admin-section-title {
1738 margin: 0 0 14px;
1739 font-size: 15px;
1740}
1741
1742.users-header {
1743 display: flex;
1744 align-items: center;
1745 justify-content: space-between;
1746 margin-bottom: 4px;
1747}
1748
1749.users-header .admin-section-title {
1750 margin-bottom: 0;
1751}
1752
1753.users-header .btn-primary {
1754 width: auto;
1755}
1756
1757.user-list {
1758 display: flex;
1759 flex-direction: column;
1760 gap: 10px;
1761}
1762
1763.user-row {
1764 display: flex;
1765 align-items: center;
1766 justify-content: space-between;
1767 gap: 12px;
1768 padding: 12px 14px;
1769 border: 1px solid var(--border);
1770}
1771
1772.user-row-main {
1773 min-width: 0;
1774}
1775
1776.user-row-name {
1777 display: flex;
1778 align-items: center;
1779 gap: 6px;
1780 font-weight: 600;
1781 font-size: 14px;
1782 flex-wrap: wrap;
1783}
1784
1785.ic-admin {
1786 width: 16px;
1787 height: 16px;
1788 flex: none;
1789}
1790
1791.user-row-actions {
1792 display: flex;
1793 gap: 6px;
1794 flex-shrink: 0;
1795}
1796
1797.badge-off {
1798 background: color-mix(in srgb, var(--danger) 15%, var(--panel));
1799 border-color: var(--danger);
1800 color: var(--danger);
1801}
1802
1803.setting-row {
1804 display: flex;
1805 align-items: center;
1806 gap: 10px;
1807 padding: 14px;
1808 border: 1px solid var(--border);
1809 cursor: pointer;
1810}
1811
1812.setting-row input {
1813 margin-left: auto;
1814}
1815
1816.setting-label {
1817 display: block;
1818 font-weight: 600;
1819 font-size: 14px;
1820}
1821
1822.setting-desc {
1823 display: block;
1824 font-size: 12px;
1825 color: var(--muted);
1826 margin-top: 2px;
1827}
1828
1829/* Setting row whose control is a select, pushed to the right. */
1830.setting-row-select {
1831 cursor: default;
1832}
1833
1834.setting-row-select > span {
1835 flex: 1;
1836}
1837
1838.setting-row-select select {
1839 appearance: none;
1840 margin-left: auto;
1841 padding: 10px 42px 10px 12px;
1842 border: 1px solid var(--border);
1843 background-color: var(--panel);
1844 background-image: var(--select-arrow);
1845 background-repeat: no-repeat;
1846 background-position: right 12px center;
1847 background-size: 14px 14px;
1848 color: var(--text);
1849 font: inherit;
1850 max-width: 190px;
1851}
1852
1853/* User create/edit form */
1854
1855.user-form {
1856 width: 520px;
1857}
1858
1859.field-input {
1860 width: 100%;
1861 box-sizing: border-box;
1862}
1863
1864/* Search exclusions: a bordered block matching the setting row above it. */
1865.excludes-field {
1866 display: flex;
1867 flex-direction: column;
1868 gap: 10px;
1869 padding: 14px;
1870 border: 1px solid var(--border);
1871}
1872
1873.excludes-list {
1874 display: flex;
1875 flex-direction: column;
1876 gap: 2px;
1877}
1878
1879.exclude-row {
1880 display: flex;
1881 align-items: center;
1882 gap: 8px;
1883 padding: 4px 0;
1884}
1885
1886.exclude-path {
1887 flex: 1;
1888 font-size: 13px;
1889 overflow: hidden;
1890 text-overflow: ellipsis;
1891 white-space: nowrap;
1892}
1893
1894.exclude-remove {
1895 flex: none;
1896 padding: 4px 6px;
1897}
1898
1899.exclude-add {
1900 align-self: flex-start;
1901}
1902
1903.excludes-empty {
1904 font-size: 13px;
1905 margin: 0;
1906}
1907
1908.field-hint {
1909 font-size: 12px;
1910 margin: -2px 0 8px;
1911}
1912
1913.check-row {
1914 display: flex;
1915 align-items: center;
1916 gap: 8px;
1917 font-size: 14px;
1918 margin-bottom: 12px;
1919 cursor: pointer;
1920}
1921
1922.form-error {
1923 color: var(--danger);
1924 font-size: 13px;
1925 margin: 4px 0 8px;
1926}
1927
1928.roots-editor {
1929 display: flex;
1930 flex-direction: column;
1931 gap: 6px;
1932}
1933
1934.root-draft {
1935 display: flex;
1936 align-items: center;
1937 gap: 8px;
1938}
1939
1940.root-draft-path {
1941 flex: 1;
1942 font-size: 13px;
1943 overflow: hidden;
1944 text-overflow: ellipsis;
1945 white-space: nowrap;
1946}
1947
1948.root-draft-mode {
1949 width: auto;
1950 padding: 6px 8px;
1951}
1952
1953.root-add {
1954 align-self: flex-start;
1955 margin-top: 4px;
1956}
1957
1958/* ------------------------------------------------------------------ */
1959/* Empty state + keyboard focus (milestone 8) */
1960/* ------------------------------------------------------------------ */
1961
1962.empty-state {
1963 display: flex;
1964 flex-direction: column;
1965 align-items: center;
1966 justify-content: center;
1967 gap: 8px;
1968 padding: 64px 20px;
1969 text-align: center;
1970}
1971
1972.empty-glyph {
1973 width: 52px;
1974 height: 52px;
1975}
1976
1977.empty-state h3 {
1978 margin: 0;
1979 font-size: 16px;
1980}
1981
1982/* Visible keyboard focus on selectable file/folder entries. The ring is
1983 inset (negative offset) so the scroll container never clips it. */
1984.tile:focus-visible,
1985.row:focus-visible {
1986 outline: 2px solid var(--accent);
1987 outline-offset: -2px;
1988}
1989
1990/* ------------------------------------------------------------------ */
1991/* Search view (#/search) */
1992/* ------------------------------------------------------------------ */
1993
1994.search-view {
1995 display: flex;
1996 flex-direction: column;
1997 gap: 14px;
1998}
1999
2000/* Query bar ------------------------------------------------------ */
2001
2002.query-bar {
2003 display: flex;
2004 align-items: stretch;
2005 gap: 8px;
2006}
2007
2008.query-input {
2009 flex: 1;
2010 display: flex;
2011 align-items: center;
2012 gap: 8px;
2013 background: var(--panel);
2014 border: 1px solid var(--border);
2015 padding: 0 10px;
2016 min-width: 0;
2017}
2018
2019.query-input .qi-icon {
2020 width: 18px;
2021 height: 18px;
2022 color: var(--muted);
2023 flex: none;
2024}
2025
2026.query-input input {
2027 flex: 1;
2028 min-width: 0;
2029 border: none;
2030 outline: none;
2031 background: transparent;
2032 color: var(--text);
2033 font: inherit;
2034 font-size: 14px;
2035 height: 38px;
2036 padding: 0;
2037}
2038
2039.query-input input::placeholder {
2040 color: var(--muted);
2041}
2042
2043/* Segmented Name / Content / Both control */
2044
2045.seg {
2046 display: flex;
2047 border: 1px solid var(--border);
2048 background: var(--panel);
2049}
2050
2051.seg button {
2052 appearance: none;
2053 border: none;
2054 border-right: 1px solid var(--border);
2055 background: transparent;
2056 color: var(--muted);
2057 font: inherit;
2058 font-size: 13px;
2059 padding: 0 14px;
2060 cursor: pointer;
2061}
2062
2063.seg button:last-child {
2064 border-right: none;
2065}
2066
2067.seg button:hover {
2068 background: var(--bg);
2069}
2070
2071.seg button.on {
2072 background: var(--accent);
2073 color: #fff;
2074}
2075
2076/* Start folder --------------------------------------------------- */
2077
2078/* The folder a search starts in; opens the folder picker. */
2079.folder-pick {
2080 flex: none;
2081 display: flex;
2082 align-items: center;
2083 gap: 8px;
2084 max-width: 260px;
2085 border: 1px solid var(--border);
2086 background: var(--panel);
2087 color: var(--text);
2088 font: inherit;
2089 font-size: 13px;
2090 padding: 0 10px;
2091 cursor: pointer;
2092}
2093
2094.folder-pick:hover {
2095 border-color: var(--muted);
2096}
2097
2098.folder-pick .fp-icon {
2099 width: 16px;
2100 height: 16px;
2101 color: var(--muted);
2102 flex: none;
2103}
2104
2105.folder-pick .fp-path {
2106 overflow: hidden;
2107 text-overflow: ellipsis;
2108 white-space: nowrap;
2109}
2110
2111.ms {
2112 position: relative;
2113 display: flex;
2114}
2115
2116.ms-btn {
2117 display: flex;
2118 align-items: center;
2119 gap: 6px;
2120 border: 1px solid var(--border);
2121 background: var(--panel);
2122 color: var(--text);
2123 font: inherit;
2124 font-size: 13px;
2125 padding: 0 10px;
2126 cursor: pointer;
2127 max-width: 180px;
2128}
2129
2130.ms-btn:hover {
2131 background: var(--bg);
2132}
2133
2134.ms-btn > span {
2135 overflow: hidden;
2136 text-overflow: ellipsis;
2137 white-space: nowrap;
2138}
2139
2140/* Square caret drawn with borders (the icon set has no chevron). */
2141.ms-caret {
2142 flex: none;
2143 width: 8px;
2144 height: 8px;
2145 border-right: 2px solid var(--muted);
2146 border-bottom: 2px solid var(--muted);
2147 transform: rotate(45deg) translate(-1px, -1px);
2148}
2149
2150.ms-backdrop {
2151 position: fixed;
2152 inset: 0;
2153 z-index: 40;
2154}
2155
2156.ms-drop {
2157 position: absolute;
2158 right: 0;
2159 top: calc(100% + 4px);
2160 z-index: 41;
2161 min-width: 200px;
2162 max-height: 280px;
2163 overflow: auto;
2164 background: var(--panel);
2165 border: 1px solid var(--border);
2166 padding: 4px 0;
2167}
2168
2169.ms-opt {
2170 display: flex;
2171 align-items: center;
2172 gap: 8px;
2173 padding: 7px 12px;
2174 font-size: 14px;
2175 cursor: pointer;
2176 user-select: none;
2177}
2178
2179.ms-opt:hover {
2180 background: var(--bg);
2181}
2182
2183.ms-opt span {
2184 overflow: hidden;
2185 text-overflow: ellipsis;
2186 white-space: nowrap;
2187}
2188
2189/* Run / stop ------------------------------------------------------- */
2190
2191.btn-run {
2192 border: 1px solid var(--accent-border);
2193 background: var(--accent);
2194 color: #fff;
2195 font: inherit;
2196 font-size: 13px;
2197 font-weight: 600;
2198 padding: 0 18px;
2199 cursor: pointer;
2200}
2201
2202.btn-run:hover {
2203 background: var(--accent-hover);
2204}
2205
2206.btn-stop {
2207 display: flex;
2208 align-items: center;
2209 gap: 6px;
2210 border: 1px solid var(--danger-border);
2211 background: var(--danger);
2212 color: #fff;
2213 font: inherit;
2214 font-size: 13px;
2215 font-weight: 600;
2216 padding: 0 14px;
2217 cursor: pointer;
2218}
2219
2220.btn-stop:hover {
2221 opacity: 0.9;
2222}
2223
2224.btn-stop .ic-stop {
2225 width: 14px;
2226 height: 14px;
2227}
2228
2229/* Status line ------------------------------------------------------ */
2230
2231.status-line {
2232 display: flex;
2233 align-items: center;
2234 gap: 8px;
2235 font-size: 13px;
2236 color: var(--muted);
2237}
2238
2239.status-line .dot {
2240 width: 8px;
2241 height: 8px;
2242 background: var(--accent);
2243 animation: search-pulse 1s ease-in-out infinite;
2244}
2245
2246@keyframes search-pulse {
2247 0%,
2248 100% {
2249 opacity: 1;
2250 }
2251 50% {
2252 opacity: 0.3;
2253 }
2254}
2255
2256.status-line .skip {
2257 opacity: 0.75;
2258}
2259
2260/* Group labels ----------------------------------------------------- */
2261
2262.group-label {
2263 display: flex;
2264 align-items: center;
2265 gap: 8px;
2266 font-size: 12px;
2267 font-weight: 600;
2268 letter-spacing: 0.04em;
2269 text-transform: uppercase;
2270 color: var(--muted);
2271}
2272
2273/* Result group: a `display:flex` (or `none`, from the inline style) flex
2274 item of `.search-view` that re-establishes the column layout for its
2275 label/list/more-row children. */
2276.result-group {
2277 flex-direction: column;
2278 gap: 14px;
2279}
2280
2281.group-label .gl-icon {
2282 width: 15px;
2283 height: 15px;
2284}
2285
2286.group-label .n {
2287 font-weight: 400;
2288 text-transform: none;
2289 letter-spacing: 0;
2290}
2291
2292.group-label .grow {
2293 flex: 1;
2294}
2295
2296.collapse-all {
2297 appearance: none;
2298 border: 1px solid var(--border);
2299 background: transparent;
2300 color: var(--muted);
2301 font: inherit;
2302 font-size: 12px;
2303 text-transform: none;
2304 letter-spacing: 0;
2305 padding: 3px 10px;
2306 cursor: pointer;
2307}
2308
2309.collapse-all:hover {
2310 background: var(--bg);
2311 color: var(--text);
2312}
2313
2314/* Name-result rows --------------------------------------------------- */
2315
2316.srow {
2317 display: grid;
2318 /* icon | name+dir | size | goto */
2319 grid-template-columns: 28px minmax(0, 1fr) 90px 30px;
2320 gap: 12px;
2321 align-items: center;
2322 padding: 9px 14px;
2323 border-bottom: 1px solid var(--border);
2324 cursor: pointer;
2325 font-size: 14px;
2326 user-select: none;
2327}
2328
2329.srow:last-child {
2330 border-bottom: none;
2331}
2332
2333.srow:hover {
2334 background: var(--bg);
2335}
2336
2337.srow-icon {
2338 width: 20px;
2339 height: 20px;
2340}
2341
2342.srow-main {
2343 min-width: 0;
2344 display: flex;
2345 flex-direction: column;
2346 line-height: 1.3;
2347}
2348
2349.srow-name {
2350 overflow: hidden;
2351 text-overflow: ellipsis;
2352 white-space: nowrap;
2353}
2354
2355/* Highlighted query hits (plain fragments render as bare spans). */
2356mark.hl-hit {
2357 background: color-mix(in srgb, var(--accent) 25%, transparent);
2358 color: inherit;
2359}
2360
2361/* The directory-path line (the name line lives inside .srow-name). */
2362.srow-dir {
2363 font-size: 12px;
2364 color: var(--muted);
2365 overflow: hidden;
2366 text-overflow: ellipsis;
2367 white-space: nowrap;
2368}
2369
2370.srow-dir mark.hl-hit {
2371 color: inherit;
2372}
2373
2374.ssize {
2375 font-size: 13px;
2376 color: var(--muted);
2377 text-align: right;
2378}
2379
2380/* Paging footer: position in the results on the left, the action on the
2381 right. Separated from the list by its own border so the button never reads
2382 as another result row. */
2383.page-foot {
2384 display: flex;
2385 flex-wrap: wrap;
2386 align-items: center;
2387 justify-content: space-between;
2388 gap: 12px;
2389 padding: 10px 14px;
2390 border-top: 1px solid var(--border);
2391 font-size: 12px;
2392 color: var(--muted);
2393}
2394
2395.page-foot .pf-count {
2396 font-variant-numeric: tabular-nums;
2397}
2398
2399.page-foot .pf-more,
2400.pf-btn {
2401 appearance: none;
2402 padding: 5px 12px;
2403 border: 1px solid var(--border);
2404 background: var(--panel);
2405 font: inherit;
2406 font-weight: 500;
2407 color: var(--text);
2408 cursor: pointer;
2409}
2410
2411.page-foot .pf-more:hover,
2412.pf-btn:hover:not(:disabled) {
2413 border-color: var(--accent);
2414 color: var(--accent);
2415}
2416
2417.pf-pages {
2418 display: flex;
2419 align-items: center;
2420 gap: 4px;
2421 margin-right: auto;
2422}
2423
2424.pf-btn {
2425 min-width: 30px;
2426 padding: 5px 8px;
2427 font-variant-numeric: tabular-nums;
2428}
2429
2430.pf-btn.current {
2431 border-color: var(--accent);
2432 background: var(--accent);
2433 color: #fff;
2434}
2435
2436.pf-btn:disabled {
2437 opacity: 0.4;
2438 cursor: default;
2439}
2440
2441.pf-goto {
2442 display: flex;
2443 align-items: center;
2444 gap: 6px;
2445 margin: 0;
2446}
2447
2448.pf-goto input {
2449 width: 72px;
2450 padding: 5px 8px;
2451}
2452
2453/* An arrow-key page turn is loading. Fixed at the top, so it also shows over
2454 the preview. The delay keeps fast turns from flashing. */
2455.page-busy {
2456 position: fixed;
2457 top: 0;
2458 left: 0;
2459 right: 0;
2460 height: 3px;
2461 overflow: hidden;
2462 z-index: 250;
2463 pointer-events: none;
2464 animation: page-busy-in 0s 200ms both;
2465}
2466
2467.page-busy::after {
2468 content: "";
2469 display: block;
2470 width: 30%;
2471 height: 100%;
2472 background: var(--accent);
2473 animation: upload-indeterminate 1.2s ease-in-out infinite;
2474}
2475
2476body:has(.page-busy) {
2477 cursor: progress;
2478}
2479
2480@keyframes page-busy-in {
2481 from {
2482 opacity: 0;
2483 }
2484 to {
2485 opacity: 1;
2486 }
2487}
2488
2489/* Content matches ----------------------------------------------------- */
2490
2491.match-card {
2492 background: var(--panel);
2493 border: 1px solid var(--border);
2494 border-top: none;
2495}
2496
2497/* The first card is the one right after the group label; `:first-of-type`
2498 cannot be used because the label is an earlier `div` sibling. */
2499.group-label + .match-card {
2500 border-top: 1px solid var(--border);
2501}
2502
2503/* The header opens the file; the matched lines below are text to read. */
2504.match-head {
2505 display: flex;
2506 align-items: center;
2507 gap: 10px;
2508 padding: 9px 14px;
2509 font-size: 14px;
2510 cursor: pointer;
2511}
2512
2513.match-head:hover {
2514 background: var(--bg);
2515}
2516
2517.match-head .mi {
2518 width: 18px;
2519 height: 18px;
2520 color: var(--muted);
2521 flex: none;
2522}
2523
2524/* Takes the slack, so the count and the two buttons sit at the right edge. */
2525.mpath {
2526 flex: 1;
2527 min-width: 0;
2528 overflow: hidden;
2529 text-overflow: ellipsis;
2530 white-space: nowrap;
2531}
2532
2533.mpath b {
2534 font-weight: 600;
2535}
2536
2537.mcount {
2538 flex: none;
2539 font-size: 12px;
2540 color: var(--muted);
2541}
2542
2543/* "Show in folder", in both the name rows and the match-card headers. */
2544.goto-btn {
2545 appearance: none;
2546 flex: none;
2547 justify-self: end;
2548 display: flex;
2549 align-items: center;
2550 justify-content: center;
2551 width: 26px;
2552 height: 26px;
2553 padding: 0;
2554 border: 1px solid transparent;
2555 background: transparent;
2556 cursor: pointer;
2557}
2558
2559.goto-btn:hover {
2560 border-color: var(--border);
2561}
2562
2563.goto-btn .goto-ic {
2564 width: 16px;
2565 height: 16px;
2566 color: var(--muted);
2567}
2568
2569.goto-btn:hover .goto-ic {
2570 color: var(--accent);
2571}
2572
2573.chev {
2574 appearance: none;
2575 border: 1px solid var(--border);
2576 background: transparent;
2577 width: 26px;
2578 height: 26px;
2579 display: flex;
2580 align-items: center;
2581 justify-content: center;
2582 cursor: pointer;
2583 flex: none;
2584}
2585
2586.chev:hover {
2587 background: var(--bg);
2588}
2589
2590.chev .chev-down,
2591.chev .chev-up {
2592 display: block;
2593 width: 10px;
2594 height: 10px;
2595 border-right: 2px solid var(--muted);
2596 border-bottom: 2px solid var(--muted);
2597}
2598
2599.chev .chev-down {
2600 transform: rotate(45deg) translate(-1px, -1px);
2601}
2602
2603.chev .chev-up {
2604 transform: rotate(-135deg) translate(-1px, -1px);
2605}
2606
2607.match-lines {
2608 border-top: 1px solid var(--border);
2609 background: var(--bg);
2610 font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
2611 font-size: 12.5px;
2612}
2613
2614/* Matched lines are text to read, not a control: the card header is what
2615 opens the file, so no pointer cursor and no hover here. */
2616.mline {
2617 display: grid;
2618 grid-template-columns: 52px minmax(0, 1fr);
2619 gap: 12px;
2620 padding: 5px 14px;
2621 border-bottom: 1px solid var(--border);
2622 white-space: pre;
2623 user-select: text;
2624}
2625
2626.mline:last-child {
2627 border-bottom: none;
2628}
2629
2630.mline .ln {
2631 color: var(--muted);
2632 text-align: right;
2633 user-select: none;
2634}
2635
2636.mline .tx {
2637 overflow: hidden;
2638 text-overflow: ellipsis;
2639}
2640
2641.search-empty {
2642 padding: 48px 20px;
2643 text-align: center;
2644 color: var(--muted);
2645 font-size: 14px;
2646}
2647
2648/* ------------------------------------------------------------------ */
2649/* Uploads: overlay panel + log view */
2650/* ------------------------------------------------------------------ */
2651
2652.upload-panel {
2653 position: fixed;
2654 right: 20px;
2655 bottom: 20px;
2656 z-index: 190;
2657 width: min(380px, calc(100vw - 40px));
2658 background: var(--panel);
2659 color: var(--text);
2660 border: 1px solid var(--border);
2661 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
2662 font-size: 13px;
2663}
2664
2665/* The toast sits above the panel when both show. */
2666.upload-panel ~ .toast,
2667.toast:has(~ .upload-panel) {
2668 bottom: 80px;
2669}
2670
2671.upload-panel-head {
2672 display: flex;
2673 align-items: center;
2674 gap: 8px;
2675 padding: 8px 10px;
2676 border-bottom: 1px solid var(--border);
2677}
2678
2679.upload-panel.collapsed .upload-panel-head {
2680 border-bottom: none;
2681}
2682
2683.upload-panel-title {
2684 font-weight: 600;
2685}
2686
2687.upload-panel-summary {
2688 margin-left: auto;
2689 font-size: 12px;
2690}
2691
2692.upload-panel-list {
2693 max-height: 50vh;
2694 overflow-y: auto;
2695}
2696
2697.upload-job {
2698 padding: 8px 10px;
2699 border-bottom: 1px solid var(--border);
2700}
2701
2702.upload-job:last-child {
2703 border-bottom: none;
2704}
2705
2706.upload-job-head {
2707 display: flex;
2708 align-items: center;
2709 gap: 4px;
2710}
2711
2712.upload-job-label {
2713 flex: 1;
2714 min-width: 0;
2715 overflow: hidden;
2716 text-overflow: ellipsis;
2717 white-space: nowrap;
2718 font-weight: 500;
2719}
2720
2721.upload-job-pct {
2722 font-variant-numeric: tabular-nums;
2723 margin-right: 4px;
2724}
2725
2726.upload-bar {
2727 height: 4px;
2728 margin: 6px 0 4px;
2729 /* Clip the indeterminate block: it slides past both ends. */
2730 overflow: hidden;
2731 background: color-mix(in srgb, var(--accent) 15%, var(--panel));
2732}
2733
2734.upload-bar-fill {
2735 height: 100%;
2736 background: var(--accent);
2737 transition: width 0.2s linear;
2738}
2739
2740/* Preparing: nothing to measure yet, so a sliding block instead of a fill. */
2741.upload-bar.indeterminate .upload-bar-fill {
2742 width: 30% !important;
2743 animation: upload-indeterminate 1.2s ease-in-out infinite;
2744}
2745
2746@keyframes upload-indeterminate {
2747 0% {
2748 transform: translateX(-100%);
2749 }
2750 100% {
2751 transform: translateX(333%);
2752 }
2753}
2754
2755.upload-job.failed .upload-bar-fill {
2756 background: var(--danger);
2757}
2758
2759.upload-job.done .upload-bar-fill {
2760 background: var(--muted);
2761}
2762
2763.upload-job-state {
2764 font-size: 12px;
2765 overflow: hidden;
2766 text-overflow: ellipsis;
2767 white-space: nowrap;
2768}
2769
2770.upload-job.failed .upload-job-state {
2771 color: var(--danger);
2772 white-space: normal;
2773}
2774
2775.upload-conflict {
2776 display: flex;
2777 flex-direction: column;
2778 gap: 6px;
2779 margin-top: 8px;
2780 padding: 8px;
2781 border: 1px solid var(--danger-border);
2782 background: color-mix(in srgb, var(--danger) 8%, var(--panel));
2783}
2784
2785.upload-conflict-all {
2786 display: flex;
2787 /* The label wraps on a narrow dialog; keep the box on the first line. */
2788 align-items: flex-start;
2789 gap: 0.5rem;
2790 font-size: 12px;
2791 cursor: pointer;
2792}
2793
2794.upload-conflict-all input {
2795 flex: none;
2796 margin-top: 2px;
2797}
2798
2799.modal .upload-conflict-all {
2800 margin: 8px 0 0;
2801 font-size: 13px;
2802}
2803
2804.upload-conflict-actions {
2805 display: flex;
2806 justify-content: flex-end;
2807 gap: 6px;
2808}
2809
2810.upload-graph {
2811 margin-top: 8px;
2812 color: var(--accent);
2813 border: 1px solid var(--border);
2814 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
2815}
2816
2817/* Peak label above the plot, so the line can never run through it. */
2818.upload-graph-max {
2819 display: block;
2820 text-align: right;
2821 padding: 2px 6px 0;
2822 font-size: 11px;
2823 line-height: 14px;
2824}
2825
2826.upload-graph svg {
2827 display: block;
2828 width: 100%;
2829 height: 46px;
2830}
2831
2832.upload-errors {
2833 margin: 6px 0 0;
2834 padding-left: 18px;
2835 font-size: 12px;
2836 color: var(--danger);
2837}
2838
2839.upload-err-file {
2840 font-weight: 500;
2841}
2842
2843/* Log view: the same rows, full width, roomier. */
2844.uploads-view .upload-log {
2845 border: 1px solid var(--border);
2846 background: var(--panel);
2847 font-size: 14px;
2848}
2849
2850.uploads-view .upload-job {
2851 padding: 12px 14px;
2852}
2853
2854.uploads-view .upload-graph svg {
2855 height: 76px;
2856}
2857
2858/* Security settings tab: password, passkeys, sign-in requirement. */
2859
2860.security-tab {
2861 display: flex;
2862 flex-direction: column;
2863 gap: 10px;
2864}
2865
2866.security-tab h3 {
2867 margin: 10px 0 0;
2868}
2869
2870/* Matches .excludes-field, so the three blocks of this tab read as one list. */
2871.security-form {
2872 display: flex;
2873 flex-direction: column;
2874 gap: 10px;
2875 padding: 14px;
2876 border: 1px solid var(--border);
2877}
2878
2879.security-form .field input {
2880 width: 100%;
2881 box-sizing: border-box;
2882}
2883
2884.passkey-list {
2885 display: flex;
2886 flex-direction: column;
2887 gap: 2px;
2888 padding: 0 14px;
2889}
2890
2891.passkey-row {
2892 display: flex;
2893 align-items: center;
2894 gap: 8px;
2895 padding: 6px 0;
2896}
2897
2898.passkey-main {
2899 flex: 1;
2900 overflow: hidden;
2901}
2902
2903.passkey-name {
2904 display: block;
2905 font-size: 13px;
2906 overflow: hidden;
2907 text-overflow: ellipsis;
2908 white-space: nowrap;
2909}
2910
2911/* "Needs your user name": a note, not an error. This passkey works, it just
2912 cannot be found without the name typed in first. */
2913.passkey-warning {
2914 display: inline-block;
2915 margin-top: 2px;
2916 padding: 1px 6px;
2917 border: 1px solid var(--border);
2918 font-size: 11px;
2919 color: var(--muted);
2920}
2921
2922/* The sign-in requirement's labels are whole phrases, so the shared 190px
2923 cap for setting selects would clip them. */
2924.security-tab .setting-row-select select {
2925 max-width: none;
2926 width: auto;
2927}
2928
2929/* Separator between the password form and the passkey button. `.auth-card p`
2930 sets a bottom-only margin on every paragraph in the card, and wins on
2931 specificity, so both of these have to name the card too. */
2932.auth-card .login-or {
2933 margin: 16px 0 8px;
2934 font-size: 12px;
2935 color: var(--muted);
2936 text-transform: lowercase;
2937}
2938
2939.auth-card .setting-desc {
2940 margin-top: 8px;
2941}
2942
2943.auth-card .btn {
2944 width: 100%;
2945}
2946