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