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