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/* ------------------------------------------------------------------ */
1617/* Admin (milestone 7) */
1618/* ------------------------------------------------------------------ */
1619
1620.admin-view {
1621 display: flex;
1622 flex-direction: column;
1623 gap: 16px;
1624 width: 100%;
1625 max-width: 960px;
1626 margin: 0 auto;
1627}
1628
1629.admin-section-title {
1630 margin: 0 0 14px;
1631 font-size: 15px;
1632}
1633
1634.users-header {
1635 display: flex;
1636 align-items: center;
1637 justify-content: space-between;
1638 margin-bottom: 4px;
1639}
1640
1641.users-header .admin-section-title {
1642 margin-bottom: 0;
1643}
1644
1645.users-header .btn-primary {
1646 width: auto;
1647}
1648
1649.user-list {
1650 display: flex;
1651 flex-direction: column;
1652 gap: 10px;
1653}
1654
1655.user-row {
1656 display: flex;
1657 align-items: center;
1658 justify-content: space-between;
1659 gap: 12px;
1660 padding: 12px 14px;
1661 border: 1px solid var(--border);
1662}
1663
1664.user-row-main {
1665 min-width: 0;
1666}
1667
1668.user-row-name {
1669 display: flex;
1670 align-items: center;
1671 gap: 6px;
1672 font-weight: 600;
1673 font-size: 14px;
1674 flex-wrap: wrap;
1675}
1676
1677.ic-admin {
1678 width: 16px;
1679 height: 16px;
1680 flex: none;
1681}
1682
1683.user-row-actions {
1684 display: flex;
1685 gap: 6px;
1686 flex-shrink: 0;
1687}
1688
1689.badge-off {
1690 background: color-mix(in srgb, var(--danger) 15%, var(--panel));
1691 border-color: var(--danger);
1692 color: var(--danger);
1693}
1694
1695.setting-row {
1696 display: flex;
1697 align-items: center;
1698 gap: 10px;
1699 padding: 14px;
1700 border: 1px solid var(--border);
1701 cursor: pointer;
1702}
1703
1704.setting-row input {
1705 margin-left: auto;
1706}
1707
1708.setting-label {
1709 display: block;
1710 font-weight: 600;
1711 font-size: 14px;
1712}
1713
1714.setting-desc {
1715 display: block;
1716 font-size: 12px;
1717 color: var(--muted);
1718 margin-top: 2px;
1719}
1720
1721/* Setting row whose control is a select, pushed to the right. */
1722.setting-row-select {
1723 cursor: default;
1724}
1725
1726.setting-row-select > span {
1727 flex: 1;
1728}
1729
1730.setting-row-select select {
1731 appearance: none;
1732 margin-left: auto;
1733 padding: 10px 42px 10px 12px;
1734 border: 1px solid var(--border);
1735 background-color: var(--panel);
1736 background-image: var(--select-arrow);
1737 background-repeat: no-repeat;
1738 background-position: right 12px center;
1739 background-size: 14px 14px;
1740 color: var(--text);
1741 font: inherit;
1742 max-width: 190px;
1743}
1744
1745/* User create/edit form */
1746
1747.user-form {
1748 width: 520px;
1749}
1750
1751.field-input {
1752 width: 100%;
1753 box-sizing: border-box;
1754}
1755
1756/* Search exclusions: a bordered block matching the setting row above it. */
1757.excludes-field {
1758 display: flex;
1759 flex-direction: column;
1760 gap: 10px;
1761 padding: 14px;
1762 border: 1px solid var(--border);
1763}
1764
1765.excludes-list {
1766 display: flex;
1767 flex-direction: column;
1768 gap: 2px;
1769}
1770
1771.exclude-row {
1772 display: flex;
1773 align-items: center;
1774 gap: 8px;
1775 padding: 4px 0;
1776}
1777
1778.exclude-path {
1779 flex: 1;
1780 font-size: 13px;
1781 overflow: hidden;
1782 text-overflow: ellipsis;
1783 white-space: nowrap;
1784}
1785
1786.exclude-remove {
1787 flex: none;
1788 padding: 4px 6px;
1789}
1790
1791.exclude-add {
1792 align-self: flex-start;
1793}
1794
1795.excludes-empty {
1796 font-size: 13px;
1797 margin: 0;
1798}
1799
1800.field-hint {
1801 font-size: 12px;
1802 margin: -2px 0 8px;
1803}
1804
1805.check-row {
1806 display: flex;
1807 align-items: center;
1808 gap: 8px;
1809 font-size: 14px;
1810 margin-bottom: 12px;
1811 cursor: pointer;
1812}
1813
1814.form-error {
1815 color: var(--danger);
1816 font-size: 13px;
1817 margin: 4px 0 8px;
1818}
1819
1820.roots-editor {
1821 display: flex;
1822 flex-direction: column;
1823 gap: 6px;
1824}
1825
1826.root-draft {
1827 display: flex;
1828 align-items: center;
1829 gap: 8px;
1830}
1831
1832.root-draft-path {
1833 flex: 1;
1834 font-size: 13px;
1835 overflow: hidden;
1836 text-overflow: ellipsis;
1837 white-space: nowrap;
1838}
1839
1840.root-draft-mode {
1841 width: auto;
1842 padding: 6px 8px;
1843}
1844
1845.root-add {
1846 align-self: flex-start;
1847 margin-top: 4px;
1848}
1849
1850/* ------------------------------------------------------------------ */
1851/* Empty state + keyboard focus (milestone 8) */
1852/* ------------------------------------------------------------------ */
1853
1854.empty-state {
1855 display: flex;
1856 flex-direction: column;
1857 align-items: center;
1858 justify-content: center;
1859 gap: 8px;
1860 padding: 64px 20px;
1861 text-align: center;
1862}
1863
1864.empty-glyph {
1865 width: 52px;
1866 height: 52px;
1867}
1868
1869.empty-state h3 {
1870 margin: 0;
1871 font-size: 16px;
1872}
1873
1874/* Visible keyboard focus on selectable file/folder entries. The ring is
1875 inset (negative offset) so the scroll container never clips it. */
1876.tile:focus-visible,
1877.row:focus-visible {
1878 outline: 2px solid var(--accent);
1879 outline-offset: -2px;
1880}
1881
1882/* ------------------------------------------------------------------ */
1883/* Search view (#/search) */
1884/* ------------------------------------------------------------------ */
1885
1886.search-view {
1887 display: flex;
1888 flex-direction: column;
1889 gap: 14px;
1890}
1891
1892/* Query bar ------------------------------------------------------ */
1893
1894.query-bar {
1895 display: flex;
1896 align-items: stretch;
1897 gap: 8px;
1898}
1899
1900.query-input {
1901 flex: 1;
1902 display: flex;
1903 align-items: center;
1904 gap: 8px;
1905 background: var(--panel);
1906 border: 1px solid var(--border);
1907 padding: 0 10px;
1908 min-width: 0;
1909}
1910
1911.query-input .qi-icon {
1912 width: 18px;
1913 height: 18px;
1914 color: var(--muted);
1915 flex: none;
1916}
1917
1918.query-input input {
1919 flex: 1;
1920 min-width: 0;
1921 border: none;
1922 outline: none;
1923 background: transparent;
1924 color: var(--text);
1925 font: inherit;
1926 font-size: 14px;
1927 height: 38px;
1928 padding: 0;
1929}
1930
1931.query-input input::placeholder {
1932 color: var(--muted);
1933}
1934
1935/* Segmented Name / Content / Both control */
1936
1937.seg {
1938 display: flex;
1939 border: 1px solid var(--border);
1940 background: var(--panel);
1941}
1942
1943.seg button {
1944 appearance: none;
1945 border: none;
1946 border-right: 1px solid var(--border);
1947 background: transparent;
1948 color: var(--muted);
1949 font: inherit;
1950 font-size: 13px;
1951 padding: 0 14px;
1952 cursor: pointer;
1953}
1954
1955.seg button:last-child {
1956 border-right: none;
1957}
1958
1959.seg button:hover {
1960 background: var(--bg);
1961}
1962
1963.seg button.on {
1964 background: var(--accent);
1965 color: #fff;
1966}
1967
1968/* Start folder --------------------------------------------------- */
1969
1970/* The folder a search starts in; opens the folder picker. */
1971.folder-pick {
1972 flex: none;
1973 display: flex;
1974 align-items: center;
1975 gap: 8px;
1976 max-width: 260px;
1977 border: 1px solid var(--border);
1978 background: var(--panel);
1979 color: var(--text);
1980 font: inherit;
1981 font-size: 13px;
1982 padding: 0 10px;
1983 cursor: pointer;
1984}
1985
1986.folder-pick:hover {
1987 border-color: var(--muted);
1988}
1989
1990.folder-pick .fp-icon {
1991 width: 16px;
1992 height: 16px;
1993 color: var(--muted);
1994 flex: none;
1995}
1996
1997.folder-pick .fp-path {
1998 overflow: hidden;
1999 text-overflow: ellipsis;
2000 white-space: nowrap;
2001}
2002
2003.ms {
2004 position: relative;
2005 display: flex;
2006}
2007
2008.ms-btn {
2009 display: flex;
2010 align-items: center;
2011 gap: 6px;
2012 border: 1px solid var(--border);
2013 background: var(--panel);
2014 color: var(--text);
2015 font: inherit;
2016 font-size: 13px;
2017 padding: 0 10px;
2018 cursor: pointer;
2019 max-width: 180px;
2020}
2021
2022.ms-btn:hover {
2023 background: var(--bg);
2024}
2025
2026.ms-btn > span {
2027 overflow: hidden;
2028 text-overflow: ellipsis;
2029 white-space: nowrap;
2030}
2031
2032/* Square caret drawn with borders (the icon set has no chevron). */
2033.ms-caret {
2034 flex: none;
2035 width: 8px;
2036 height: 8px;
2037 border-right: 2px solid var(--muted);
2038 border-bottom: 2px solid var(--muted);
2039 transform: rotate(45deg) translate(-1px, -1px);
2040}
2041
2042.ms-backdrop {
2043 position: fixed;
2044 inset: 0;
2045 z-index: 40;
2046}
2047
2048.ms-drop {
2049 position: absolute;
2050 right: 0;
2051 top: calc(100% + 4px);
2052 z-index: 41;
2053 min-width: 200px;
2054 max-height: 280px;
2055 overflow: auto;
2056 background: var(--panel);
2057 border: 1px solid var(--border);
2058 padding: 4px 0;
2059}
2060
2061.ms-opt {
2062 display: flex;
2063 align-items: center;
2064 gap: 8px;
2065 padding: 7px 12px;
2066 font-size: 14px;
2067 cursor: pointer;
2068 user-select: none;
2069}
2070
2071.ms-opt:hover {
2072 background: var(--bg);
2073}
2074
2075.ms-opt span {
2076 overflow: hidden;
2077 text-overflow: ellipsis;
2078 white-space: nowrap;
2079}
2080
2081/* Run / stop ------------------------------------------------------- */
2082
2083.btn-run {
2084 border: 1px solid var(--accent-border);
2085 background: var(--accent);
2086 color: #fff;
2087 font: inherit;
2088 font-size: 13px;
2089 font-weight: 600;
2090 padding: 0 18px;
2091 cursor: pointer;
2092}
2093
2094.btn-run:hover {
2095 background: var(--accent-hover);
2096}
2097
2098.btn-stop {
2099 display: flex;
2100 align-items: center;
2101 gap: 6px;
2102 border: 1px solid var(--danger-border);
2103 background: var(--danger);
2104 color: #fff;
2105 font: inherit;
2106 font-size: 13px;
2107 font-weight: 600;
2108 padding: 0 14px;
2109 cursor: pointer;
2110}
2111
2112.btn-stop:hover {
2113 opacity: 0.9;
2114}
2115
2116.btn-stop .ic-stop {
2117 width: 14px;
2118 height: 14px;
2119}
2120
2121/* Status line ------------------------------------------------------ */
2122
2123.status-line {
2124 display: flex;
2125 align-items: center;
2126 gap: 8px;
2127 font-size: 13px;
2128 color: var(--muted);
2129}
2130
2131.status-line .dot {
2132 width: 8px;
2133 height: 8px;
2134 background: var(--accent);
2135 animation: search-pulse 1s ease-in-out infinite;
2136}
2137
2138@keyframes search-pulse {
2139 0%,
2140 100% {
2141 opacity: 1;
2142 }
2143 50% {
2144 opacity: 0.3;
2145 }
2146}
2147
2148.status-line .skip {
2149 opacity: 0.75;
2150}
2151
2152/* Group labels ----------------------------------------------------- */
2153
2154.group-label {
2155 display: flex;
2156 align-items: center;
2157 gap: 8px;
2158 font-size: 12px;
2159 font-weight: 600;
2160 letter-spacing: 0.04em;
2161 text-transform: uppercase;
2162 color: var(--muted);
2163}
2164
2165/* Result group: a `display:flex` (or `none`, from the inline style) flex
2166 item of `.search-view` that re-establishes the column layout for its
2167 label/list/more-row children. */
2168.result-group {
2169 flex-direction: column;
2170 gap: 14px;
2171}
2172
2173.group-label .gl-icon {
2174 width: 15px;
2175 height: 15px;
2176}
2177
2178.group-label .n {
2179 font-weight: 400;
2180 text-transform: none;
2181 letter-spacing: 0;
2182}
2183
2184.group-label .grow {
2185 flex: 1;
2186}
2187
2188.collapse-all {
2189 appearance: none;
2190 border: 1px solid var(--border);
2191 background: transparent;
2192 color: var(--muted);
2193 font: inherit;
2194 font-size: 12px;
2195 text-transform: none;
2196 letter-spacing: 0;
2197 padding: 3px 10px;
2198 cursor: pointer;
2199}
2200
2201.collapse-all:hover {
2202 background: var(--bg);
2203 color: var(--text);
2204}
2205
2206/* Name-result rows --------------------------------------------------- */
2207
2208.srow {
2209 display: grid;
2210 /* icon | name+dir | size | goto */
2211 grid-template-columns: 28px minmax(0, 1fr) 90px 30px;
2212 gap: 12px;
2213 align-items: center;
2214 padding: 9px 14px;
2215 border-bottom: 1px solid var(--border);
2216 cursor: pointer;
2217 font-size: 14px;
2218 user-select: none;
2219}
2220
2221.srow:last-child {
2222 border-bottom: none;
2223}
2224
2225.srow:hover {
2226 background: var(--bg);
2227}
2228
2229.srow-icon {
2230 width: 20px;
2231 height: 20px;
2232}
2233
2234.srow-main {
2235 min-width: 0;
2236 display: flex;
2237 flex-direction: column;
2238 line-height: 1.3;
2239}
2240
2241.srow-name {
2242 overflow: hidden;
2243 text-overflow: ellipsis;
2244 white-space: nowrap;
2245}
2246
2247/* Highlighted query hits (plain fragments render as bare spans). */
2248mark.hl-hit {
2249 background: color-mix(in srgb, var(--accent) 25%, transparent);
2250 color: inherit;
2251}
2252
2253/* The directory-path line (the name line lives inside .srow-name). */
2254.srow-dir {
2255 font-size: 12px;
2256 color: var(--muted);
2257 overflow: hidden;
2258 text-overflow: ellipsis;
2259 white-space: nowrap;
2260}
2261
2262.srow-dir mark.hl-hit {
2263 color: inherit;
2264}
2265
2266.ssize {
2267 font-size: 13px;
2268 color: var(--muted);
2269 text-align: right;
2270}
2271
2272/* Paging footer: position in the results on the left, the action on the
2273 right. Separated from the list by its own border so the button never reads
2274 as another result row. */
2275.page-foot {
2276 display: flex;
2277 align-items: center;
2278 justify-content: space-between;
2279 gap: 12px;
2280 padding: 10px 14px;
2281 border-top: 1px solid var(--border);
2282 font-size: 12px;
2283 color: var(--muted);
2284}
2285
2286.page-foot .pf-count {
2287 font-variant-numeric: tabular-nums;
2288}
2289
2290.page-foot .pf-more {
2291 appearance: none;
2292 padding: 5px 12px;
2293 border: 1px solid var(--border);
2294 background: var(--panel);
2295 font: inherit;
2296 font-weight: 500;
2297 color: var(--text);
2298 cursor: pointer;
2299}
2300
2301.page-foot .pf-more:hover {
2302 border-color: var(--accent);
2303 color: var(--accent);
2304}
2305
2306/* Content matches ----------------------------------------------------- */
2307
2308.match-card {
2309 background: var(--panel);
2310 border: 1px solid var(--border);
2311 border-top: none;
2312}
2313
2314/* The first card is the one right after the group label; `:first-of-type`
2315 cannot be used because the label is an earlier `div` sibling. */
2316.group-label + .match-card {
2317 border-top: 1px solid var(--border);
2318}
2319
2320/* The header opens the file; the matched lines below are text to read. */
2321.match-head {
2322 display: flex;
2323 align-items: center;
2324 gap: 10px;
2325 padding: 9px 14px;
2326 font-size: 14px;
2327 cursor: pointer;
2328}
2329
2330.match-head:hover {
2331 background: var(--bg);
2332}
2333
2334.match-head .mi {
2335 width: 18px;
2336 height: 18px;
2337 color: var(--muted);
2338 flex: none;
2339}
2340
2341/* Takes the slack, so the count and the two buttons sit at the right edge. */
2342.mpath {
2343 flex: 1;
2344 min-width: 0;
2345 overflow: hidden;
2346 text-overflow: ellipsis;
2347 white-space: nowrap;
2348}
2349
2350.mpath b {
2351 font-weight: 600;
2352}
2353
2354.mcount {
2355 flex: none;
2356 font-size: 12px;
2357 color: var(--muted);
2358}
2359
2360/* "Show in folder", in both the name rows and the match-card headers. */
2361.goto-btn {
2362 appearance: none;
2363 flex: none;
2364 justify-self: end;
2365 display: flex;
2366 align-items: center;
2367 justify-content: center;
2368 width: 26px;
2369 height: 26px;
2370 padding: 0;
2371 border: 1px solid transparent;
2372 background: transparent;
2373 cursor: pointer;
2374}
2375
2376.goto-btn:hover {
2377 border-color: var(--border);
2378}
2379
2380.goto-btn .goto-ic {
2381 width: 16px;
2382 height: 16px;
2383 color: var(--muted);
2384}
2385
2386.goto-btn:hover .goto-ic {
2387 color: var(--accent);
2388}
2389
2390.chev {
2391 appearance: none;
2392 border: 1px solid var(--border);
2393 background: transparent;
2394 width: 26px;
2395 height: 26px;
2396 display: flex;
2397 align-items: center;
2398 justify-content: center;
2399 cursor: pointer;
2400 flex: none;
2401}
2402
2403.chev:hover {
2404 background: var(--bg);
2405}
2406
2407.chev .chev-down,
2408.chev .chev-up {
2409 display: block;
2410 width: 10px;
2411 height: 10px;
2412 border-right: 2px solid var(--muted);
2413 border-bottom: 2px solid var(--muted);
2414}
2415
2416.chev .chev-down {
2417 transform: rotate(45deg) translate(-1px, -1px);
2418}
2419
2420.chev .chev-up {
2421 transform: rotate(-135deg) translate(-1px, -1px);
2422}
2423
2424.match-lines {
2425 border-top: 1px solid var(--border);
2426 background: var(--bg);
2427 font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
2428 font-size: 12.5px;
2429}
2430
2431/* Matched lines are text to read, not a control: the card header is what
2432 opens the file, so no pointer cursor and no hover here. */
2433.mline {
2434 display: grid;
2435 grid-template-columns: 52px minmax(0, 1fr);
2436 gap: 12px;
2437 padding: 5px 14px;
2438 border-bottom: 1px solid var(--border);
2439 white-space: pre;
2440 user-select: text;
2441}
2442
2443.mline:last-child {
2444 border-bottom: none;
2445}
2446
2447.mline .ln {
2448 color: var(--muted);
2449 text-align: right;
2450 user-select: none;
2451}
2452
2453.mline .tx {
2454 overflow: hidden;
2455 text-overflow: ellipsis;
2456}
2457
2458.search-empty {
2459 padding: 48px 20px;
2460 text-align: center;
2461 color: var(--muted);
2462 font-size: 14px;
2463}
2464