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/* Class rules set `display`, which would beat the UA rule for `hidden`. */
68[hidden] {
69 display: none !important;
70}
71
72.muted {
73 color: var(--muted);
74}
75
76.card {
77 background: var(--panel);
78 border: 1px solid var(--border);
79 padding: 24px;
80}
81
82.center-screen {
83 min-height: 100vh;
84 display: flex;
85 flex-direction: column;
86 align-items: center;
87 justify-content: center;
88 padding: 16px;
89 text-align: center;
90}
91
92/* Inside the app frame the topbar already took part of the viewport, so a
93 full 100vh here overflows and shows a scrollbar with nothing to scroll to.
94 Login and setup render .center-screen as the whole page, where the 100vh
95 above is right. */
96.content > .center-screen {
97 min-height: 0;
98 flex: 1;
99}
100
101.auth-card {
102 width: 100%;
103 max-width: 380px;
104}
105
106/* The auth forms have a single action, so its button spans the form. */
107.auth-card .btn-primary {
108 width: 100%;
109}
110
111.auth-card h1 {
112 margin: 0 0 8px;
113 font-size: 22px;
114}
115
116.auth-logo {
117 width: 44px;
118 height: 44px;
119 margin-bottom: 12px;
120}
121
122.auth-card p {
123 margin: 0 0 20px;
124 font-size: 13px;
125}
126
127.field {
128 display: block;
129 margin-bottom: 14px;
130}
131
132.field span {
133 display: block;
134 font-size: 13px;
135 color: var(--muted);
136 margin-bottom: 4px;
137}
138
139input {
140 width: 100%;
141 padding: 10px 12px;
142 border: 1px solid var(--border);
143 background: var(--bg);
144 color: var(--text);
145 font-size: 14px;
146}
147
148input:focus,
149textarea:focus {
150 outline: 2px solid var(--accent);
151 outline-offset: -1px;
152}
153
154textarea {
155 width: 100%;
156 padding: 10px 12px;
157 border: 1px solid var(--border);
158 background: var(--bg);
159 color: var(--text);
160 font: inherit;
161 font-size: 14px;
162 resize: vertical;
163}
164
165/* Square checkboxes — native controls are rounded, the theme is sharp. */
166input[type="checkbox"] {
167 appearance: none;
168 width: 16px;
169 height: 16px;
170 margin: 0;
171 padding: 0;
172 flex: none;
173 border: 1px solid var(--border);
174 background: var(--panel);
175 cursor: pointer;
176 position: relative;
177 vertical-align: -3px;
178}
179
180input[type="checkbox"]:checked {
181 background: var(--accent);
182 border-color: var(--accent-border);
183}
184
185input[type="checkbox"]:checked::after {
186 content: "";
187 position: absolute;
188 left: 4px;
189 top: 1px;
190 width: 4px;
191 height: 8px;
192 border: solid #fff;
193 border-width: 0 2px 2px 0;
194 transform: rotate(45deg);
195}
196
197button,
198:where(a.btn) {
199 font: inherit;
200 cursor: pointer;
201 display: inline-flex;
202 align-items: center;
203 justify-content: center;
204 gap: 6px;
205 border: 1px solid var(--border);
206 background: var(--panel);
207 color: var(--text);
208 padding: 9px 14px;
209}
210
211/* Browser link colors are unreadable on the dark panels. */
212a {
213 color: var(--accent);
214}
215
216a.btn {
217 color: var(--text);
218 text-decoration: none;
219}
220
221button:hover,
222:where(a.btn):hover {
223 border-color: var(--muted);
224}
225
226button:disabled {
227 opacity: 0.6;
228 cursor: default;
229}
230
231/* No width here on purpose. Full width belongs to the few places that ask
232 for it (.auth-card, .unlock-btn), not to every dialog's action row. */
233.btn-primary {
234 background: var(--accent);
235 /* One step away from the fill, per theme (see palette above), so the
236 button's edge reads the same as on neutral buttons. */
237 border-color: var(--accent-border);
238 color: #fff;
239 font-weight: 600;
240}
241
242.btn-primary:hover {
243 background: var(--accent-hover);
244 border-color: var(--accent-border);
245}
246
247.error {
248 color: var(--danger);
249 font-size: 13px;
250 min-height: 18px;
251 margin: 0 0 10px;
252}
253
254/* layout (signed-in): full-width topbar, then sidebar + content below */
255
256.layout {
257 min-height: 100vh;
258 display: flex;
259 flex-direction: column;
260}
261
262.topbar {
263 flex: none;
264 position: sticky;
265 top: 0;
266 z-index: 130;
267 height: var(--topbar-h);
268 display: flex;
269 align-items: center;
270 justify-content: space-between;
271 gap: 12px;
272 padding: 0 16px;
273 background: var(--panel);
274 border-bottom: 1px solid var(--border);
275}
276
277.topbar-left {
278 display: flex;
279 align-items: center;
280 gap: 10px;
281 min-width: 0;
282}
283
284.icon-btn.nav-toggle {
285 display: none;
286}
287
288.body-row {
289 flex: 1;
290 display: flex;
291 align-items: stretch;
292}
293
294/* ---- sidebar ---- */
295
296.sidebar {
297 flex: none;
298 width: 224px;
299 display: flex;
300 flex-direction: column;
301 background: var(--panel);
302 border-right: 1px solid var(--border);
303 position: sticky;
304 top: var(--topbar-h);
305 height: calc(100vh - var(--topbar-h));
306}
307
308.brand {
309 display: flex;
310 align-items: center;
311 gap: 10px;
312 font-weight: 700;
313 white-space: nowrap;
314 color: inherit;
315 text-decoration: none;
316 cursor: pointer;
317}
318
319.brand:hover {
320 opacity: 0.8;
321}
322
323.brand-name {
324 overflow: hidden;
325 text-overflow: ellipsis;
326}
327
328.logo {
329 width: 26px;
330 height: 26px;
331 flex: none;
332 display: block;
333}
334
335.nav {
336 flex: 1;
337 overflow-y: auto;
338 padding: 14px 8px 16px;
339 display: flex;
340 flex-direction: column;
341 gap: 20px;
342}
343
344.sidebar-user {
345 padding: 0 10px 2px;
346 font-size: 14px;
347 font-weight: 600;
348 overflow: hidden;
349 text-overflow: ellipsis;
350 white-space: nowrap;
351}
352
353.nav-section-label {
354 font-size: 11px;
355 font-weight: 700;
356 text-transform: uppercase;
357 letter-spacing: 0.08em;
358 color: var(--muted);
359 padding: 0 10px 6px;
360}
361
362/* Section header with a right-aligned action (the Files refresh). */
363.nav-section-head {
364 display: flex;
365 align-items: center;
366 justify-content: space-between;
367 gap: 6px;
368 margin-bottom: 4px;
369}
370
371.nav-section-head .nav-section-label {
372 flex: 1;
373 padding-bottom: 2px;
374}
375
376.nav-section-refresh {
377 color: var(--muted);
378 background: none;
379 border-color: transparent;
380 padding: 3px 5px;
381}
382
383.nav-section-refresh:hover {
384 color: var(--text);
385 border-color: var(--border);
386}
387
388.nav-tab {
389 display: flex;
390 align-items: center;
391 justify-content: flex-start;
392 gap: 10px;
393 width: 100%;
394 margin-bottom: 2px;
395 padding: 8px 10px;
396 text-align: left;
397 background: none;
398 border: none;
399 color: var(--muted);
400 font-size: 14px;
401}
402
403.nav-tab:hover {
404 background: var(--bg);
405 color: var(--text);
406}
407
408.nav-tab.active {
409 background: var(--accent);
410 border-color: var(--accent);
411 color: #fff;
412}
413
414.ic-tab {
415 width: 18px;
416 height: 18px;
417 flex: none;
418}
419
420.nav-tab.active .icon {
421 color: #fff;
422}
423
424.nav-tab-name {
425 flex: 1;
426 min-width: 0;
427 overflow: hidden;
428 text-overflow: ellipsis;
429 white-space: nowrap;
430}
431
432.nav-tab-note {
433 flex: none;
434 font-size: 10px;
435 font-weight: 700;
436 padding: 1px 5px;
437 background: var(--bg);
438 border: 1px solid var(--border);
439 color: var(--muted);
440}
441
442.nav-tab.active .nav-tab-note {
443 background: rgb(255 255 255 / 18%);
444 border-color: rgb(255 255 255 / 30%);
445 color: #fff;
446}
447
448.nav-backdrop {
449 display: none;
450}
451
452/* Sidebar footer: theme toggle, log out, and the project version.
453 Sits at the bottom of the sidebar, below the manage section. */
454.sidebar-footer {
455 flex: none;
456 border-top: 1px solid var(--border);
457 padding: 8px;
458}
459
460.sidebar-footer .nav-tab {
461 margin-bottom: 0;
462}
463
464.sidebar-action.logout {
465 color: var(--danger);
466}
467
468.sidebar-action.logout:hover {
469 color: var(--danger);
470}
471
472.sidebar-version {
473 margin-top: 8px;
474 padding: 0 10px;
475 font-size: 11.5px;
476 color: var(--muted);
477 white-space: nowrap;
478 overflow: hidden;
479 text-overflow: ellipsis;
480}
481
482.badge {
483 font-size: 11px;
484 padding: 2px 8px;
485 background: var(--bg);
486 border: 1px solid var(--border);
487 color: var(--muted);
488}
489
490.content {
491 flex: 1;
492 min-width: 0;
493 min-height: 0;
494 display: flex;
495 flex-direction: column;
496 padding: 20px;
497}
498
499/* While a file is open the app is a fixed-height frame: the file view
500 * (editor, image, …) scrolls inside, the page itself never grows. */
501.layout.file-mode {
502 height: 100vh;
503 overflow: hidden;
504}
505
506.layout.file-mode .body-row {
507 min-height: 0;
508}
509
510/* Narrow screens: the sidebar becomes an off-canvas drawer that only
511 covers the content, below the (always visible) topbar. */
512@media (max-width: 860px) {
513 .sidebar {
514 position: fixed;
515 top: var(--topbar-h);
516 bottom: 0;
517 left: 0;
518 right: auto;
519 height: auto;
520 z-index: 120;
521 transform: translateX(-100%);
522 transition: transform 0.15s ease;
523 }
524
525 .sidebar.open {
526 transform: translateX(0);
527 box-shadow: 0 0 48px rgb(0 0 0 / 35%);
528 }
529
530 .nav-backdrop {
531 display: block;
532 position: fixed;
533 top: var(--topbar-h);
534 bottom: 0;
535 left: 0;
536 right: 0;
537 z-index: 110;
538 background: rgb(0 0 0 / 40%);
539 }
540
541 .icon-btn.nav-toggle {
542 display: inline-flex;
543 }
544
545 .content {
546 padding: 14px;
547 }
548}
549
550/* ---- file browser ---- */
551
552.browser {
553 width: 100%;
554 max-width: 1200px;
555 margin: 0 auto;
556 flex: 1;
557 display: flex;
558 flex-direction: column;
559 min-height: 0;
560}
561
562.icon {
563 width: 20px;
564 height: 20px;
565 display: inline-block;
566 vertical-align: middle;
567}
568
569.ic-folder {
570 color: var(--accent);
571}
572
573.ic-muted {
574 color: var(--muted);
575}
576
577.crumbs {
578 display: flex;
579 align-items: center;
580 gap: 2px;
581 padding: 6px 0 12px;
582 font-size: 15px;
583 flex-wrap: wrap;
584}
585
586.crumb {
587 cursor: pointer;
588 color: var(--muted);
589 padding: 3px 6px;
590 overflow-wrap: anywhere;
591}
592
593.crumb:hover {
594 color: var(--text);
595 background: var(--panel);
596}
597
598.crumb.current {
599 color: var(--text);
600 font-weight: 600;
601 cursor: default;
602 background: none;
603}
604
605.crumb-sep {
606 color: var(--muted);
607 padding: 0 1px;
608}
609
610.toolbar {
611 display: flex;
612 align-items: center;
613 justify-content: space-between;
614 gap: 6px;
615 padding-bottom: 12px;
616}
617
618/* Wider screens fit the sort controls by shrinking the label. Wrapping
619 there would move them to a second line. */
620@media (max-width: 360px) {
621 .toolbar {
622 flex-wrap: wrap;
623 }
624}
625
626.toolbar-right {
627 display: flex;
628 gap: 6px;
629}
630
631/* Sort control in the toolbar. The select mirrors the app's other selects:
632 no native chrome, own arrow. */
633.sort-label {
634 font-size: 13px;
635 color: var(--muted);
636 align-self: center;
637 margin-right: 2px;
638}
639
640.sort-select {
641 appearance: none;
642 width: auto;
643 padding: 7px 32px 7px 10px;
644 border: 1px solid var(--border);
645 background-color: var(--panel);
646 background-image: var(--select-arrow);
647 background-repeat: no-repeat;
648 background-position: right 9px center;
649 background-size: 12px 12px;
650 color: var(--text);
651 font: inherit;
652 font-size: 13px;
653}
654
655.sort-select:focus {
656 outline: 2px solid var(--accent);
657 outline-offset: -1px;
658}
659
660.icon-btn {
661 padding: 7px 9px;
662 border: 1px solid var(--border);
663 background: var(--panel);
664 color: var(--muted);
665 display: inline-flex;
666 align-items: center;
667}
668
669.icon-btn:hover {
670 border-color: var(--muted);
671}
672
673.icon-btn.active {
674 background: var(--accent);
675 border-color: var(--accent);
676 color: #fff;
677}
678
679.ic-btn {
680 width: 18px;
681 height: 18px;
682 color: inherit;
683}
684
685.entries-area {
686 position: relative;
687 flex: 1;
688 min-height: 240px;
689}
690
691.entries-area.drop-target {
692 outline: 2px dashed var(--accent);
693 outline-offset: -2px;
694}
695
696.drop-hint {
697 position: absolute;
698 inset: 0;
699 z-index: 2;
700 display: flex;
701 align-items: center;
702 justify-content: center;
703 gap: 8px;
704 font-weight: 500;
705 color: var(--accent);
706 background: color-mix(in srgb, var(--accent) 10%, var(--panel) 60%);
707 /* The hint must not steal the drag from the area beneath it. */
708 pointer-events: none;
709}
710
711.center-note {
712 text-align: center;
713 padding: 48px 0;
714}
715
716.error-card {
717 display: flex;
718 align-items: center;
719 justify-content: space-between;
720 gap: 16px;
721 color: var(--danger);
722 font-size: 14px;
723}
724
725/* root tiles were replaced by the sidebar's folder tabs */
726
727/* grid view */
728
729.entries-grid {
730 display: grid;
731 grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
732 gap: 10px;
733}
734
735.tile {
736 background: var(--panel);
737 border: 1px solid var(--border);
738 padding: 16px 10px 12px;
739 text-align: center;
740 cursor: pointer;
741 user-select: none;
742}
743
744/* Touch: a long press opens the context menu (see util::LongPress). The
745 iOS callout and text selection would otherwise appear first. Vertical
746 panning stays with the browser. */
747.entries-area,
748.tile,
749.row {
750 -webkit-touch-callout: none;
751 -webkit-user-select: none;
752 touch-action: pan-y;
753}
754
755.tile:hover {
756 border-color: var(--muted);
757}
758
759.tile-icon {
760 width: 42px;
761 height: 42px;
762 margin: 0 auto 10px;
763 display: block;
764}
765
766/* Holds the icon with the thumbnail layered over it. The icon is the
767 fallback when no thumbnail loads. */
768.tile-media {
769 position: relative;
770 display: flex;
771 align-items: center;
772 justify-content: center;
773 margin-bottom: 10px;
774}
775
776.tile-media .tile-icon {
777 margin: 0;
778}
779
780/* One tile height per folder: the taller media area applies to every tile,
781 not only the ones that end up with a thumbnail. */
782.has-thumbs .tile-media {
783 height: 92px;
784}
785
786/* No background: the element must stay transparent until the bytes arrive,
787 or it hides the icon underneath as a dark rectangle. */
788.tile-thumb {
789 position: absolute;
790 inset: 0;
791 width: 100%;
792 height: 100%;
793 object-fit: cover;
794}
795
796.tile-name {
797 font-size: 13px;
798 line-height: 1.3;
799 word-break: break-word;
800 /* Uniform tile height: the name area always takes two lines and
801 never overflows (the full name is in the title tooltip). */
802 min-height: calc(2 * 1.3em);
803 display: -webkit-box;
804 -webkit-line-clamp: 2;
805 -webkit-box-orient: vertical;
806 overflow: hidden;
807}
808
809/* list view */
810
811.entries-list {
812 background: var(--panel);
813 border: 1px solid var(--border);
814 overflow: hidden;
815}
816
817.row {
818 display: grid;
819 grid-template-columns: 28px minmax(0, 1fr) 90px 150px;
820 gap: 12px;
821 align-items: center;
822 padding: 9px 14px;
823 border-bottom: 1px solid var(--border);
824 cursor: pointer;
825 font-size: 14px;
826 user-select: none;
827}
828
829.row:last-child {
830 border-bottom: none;
831}
832
833.row:hover {
834 background: var(--bg);
835}
836
837.row-icon {
838 width: 20px;
839 height: 20px;
840}
841
842.row-name {
843 overflow: hidden;
844 text-overflow: ellipsis;
845 white-space: nowrap;
846}
847
848.row-size,
849.row-date {
850 font-size: 13px;
851 text-align: right;
852}
853
854@media (max-width: 640px) {
855 .row {
856 grid-template-columns: 28px minmax(0, 1fr) 90px;
857 }
858
859 .row-date {
860 display: none;
861 }
862}
863
864/* context menu */
865
866.ctx-menu {
867 position: fixed;
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.ctx-item {
877 display: flex;
878 align-items: center;
879 gap: 10px;
880 padding: 8px 12px;
881 font-size: 14px;
882 cursor: pointer;
883}
884
885.ic-ctx {
886 width: 18px;
887 height: 18px;
888 flex: none;
889}
890
891.ctx-item:hover {
892 background: var(--bg);
893}
894
895.ctx-item.disabled {
896 opacity: 0.45;
897 cursor: default;
898}
899
900.ctx-item.disabled:hover {
901 background: none;
902}
903
904/* toolbar selection actions (top right) */
905
906.sel-actions {
907 display: flex;
908 align-items: center;
909 gap: 6px;
910 position: relative;
911}
912
913.sel-actions .icon-btn:disabled {
914 opacity: 0.45;
915 cursor: default;
916}
917
918.sel-actions .icon-btn:disabled:hover {
919 border-color: var(--border);
920 color: var(--muted);
921}
922
923.sel-more-menu {
924 position: fixed;
925 inset: auto;
926 margin: 0;
927 min-width: 190px;
928 color: inherit;
929 background: var(--panel);
930 border: 1px solid var(--border);
931 padding: 5px;
932 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
933}
934
935/* selection actions in the top bar */
936
937.topbar .sel-actions {
938 min-height: 0;
939}
940
941.topbar .sel-actions .icon-btn {
942 padding: 5px 7px;
943}
944
945/* selection highlight in the grid and list */
946
947.tile.selected {
948 border-color: var(--accent);
949 background: color-mix(in srgb, var(--accent) 12%, var(--panel));
950}
951
952.row.selected {
953 background: color-mix(in srgb, var(--accent) 12%, var(--panel));
954 box-shadow: inset 2px 0 0 var(--accent);
955}
956
957/* settings tabs (profile / server) */
958
959.settings-tabs {
960 display: flex;
961 gap: 8px;
962}
963
964.settings-tab-btn {
965 padding: 7px 14px;
966 border: 1px solid var(--border);
967 background: var(--panel);
968 color: var(--muted);
969 font-size: 13px;
970 font-weight: 600;
971}
972
973.settings-tab-btn:hover {
974 border-color: var(--muted);
975 color: var(--text);
976}
977
978.settings-tab-btn.active {
979 border-color: var(--accent);
980 color: var(--accent);
981 background: color-mix(in srgb, var(--accent) 10%, var(--panel));
982}
983
984/* toast */
985
986.toast {
987 position: fixed;
988 bottom: 20px;
989 right: 20px;
990 z-index: 200;
991 background: var(--panel);
992 color: var(--text);
993 border: 1px solid var(--border);
994 padding: 11px 16px;
995 font-size: 14px;
996 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
997 max-width: 320px;
998}
999
1000.toast-error {
1001 background: color-mix(in srgb, var(--danger) 12%, var(--panel));
1002 border-color: var(--danger-border);
1003 color: var(--danger);
1004}
1005
1006
1007/* ---- modals ---- */
1008
1009/* The shell is a native <dialog> opened with showModal(): the browser puts
1010 it in the top layer and paints ::backdrop, so there is no overlay element
1011 to position and no z-index to keep in order. The reset below strips the
1012 UA's own box so the .modal card inside is the only thing that shows. */
1013.modal-host {
1014 padding: 0;
1015 border: none;
1016 background: none;
1017 /* The UA caps an open dialog's size; the card sets its own. */
1018 max-width: none;
1019 max-height: none;
1020 /* Centre it in the inset-0 box, as the old flex overlay did. */
1021 margin: auto;
1022 overflow: visible;
1023}
1024
1025.modal-host::backdrop {
1026 background: rgb(0 0 0 / 45%);
1027}
1028
1029.modal {
1030 width: 440px;
1031 max-width: calc(100vw - 32px);
1032 padding: 20px;
1033 background: var(--panel);
1034 border: 1px solid var(--border);
1035 box-shadow: 0 12px 44px rgb(0 0 0 / 35%);
1036}
1037
1038.modal.picker {
1039 width: 640px;
1040}
1041
1042.modal-title {
1043 margin: 0 0 12px;
1044 font-size: 16px;
1045}
1046
1047.modal-label {
1048 display: block;
1049 font-size: 13px;
1050 color: var(--muted);
1051 margin-bottom: 6px;
1052}
1053
1054.modal .input {
1055 width: 100%;
1056 box-sizing: border-box;
1057 margin-bottom: 16px;
1058}
1059
1060.dialog-error {
1061 color: var(--danger);
1062 font-size: 13px;
1063}
1064
1065.dialog-error {
1066 margin-top: 8px;
1067}
1068
1069.modal-message {
1070 white-space: pre-line;
1071 font-size: 14px;
1072 margin: 0 0 8px;
1073}
1074
1075.modal-actions {
1076 display: flex;
1077 justify-content: flex-end;
1078 gap: 8px;
1079 margin-top: 16px;
1080}
1081
1082.btn-danger {
1083 background: var(--danger);
1084 /* A visible border (one step away from the fill, per theme) so the
1085 button's edge reads the same as on neutral buttons. */
1086 border-color: var(--danger-border);
1087 color: #fff;
1088}
1089
1090.btn-danger:hover {
1091 background: color-mix(in srgb, var(--danger) 85%, black);
1092}
1093
1094.btn:disabled {
1095 opacity: 0.5;
1096 cursor: not-allowed;
1097}
1098
1099.conflict-list {
1100 max-height: 200px;
1101 overflow-y: auto;
1102 margin: 8px 0;
1103 padding-left: 20px;
1104 font-size: 13px;
1105}
1106
1107/* Folder picker: roots on the left, the selected root's tree on the right. */
1108
1109.picker-body {
1110 display: grid;
1111 grid-template-columns: 180px minmax(0, 1fr);
1112 border: 1px solid var(--border);
1113 height: 320px;
1114}
1115
1116.picker-roots {
1117 display: flex;
1118 flex-direction: column;
1119 gap: 2px;
1120 padding: 6px;
1121 border-right: 1px solid var(--border);
1122 background: var(--bg);
1123 min-height: 0;
1124 overflow-y: auto;
1125}
1126
1127.picker-root {
1128 display: flex;
1129 align-items: center;
1130 justify-content: flex-start;
1131 gap: 8px;
1132 padding: 7px 8px;
1133 border: none;
1134 background: transparent;
1135 color: var(--text);
1136 font: inherit;
1137 font-size: 13px;
1138 text-align: left;
1139 cursor: pointer;
1140 min-width: 0;
1141}
1142
1143.picker-root:hover {
1144 background: var(--panel);
1145}
1146
1147.picker-root.active {
1148 background: var(--accent);
1149 color: #fff;
1150}
1151
1152.picker-root.ro .picker-root-name {
1153 opacity: 0.6;
1154 text-decoration: line-through dotted;
1155}
1156
1157.picker-root-ic {
1158 width: 16px;
1159 height: 16px;
1160 flex: none;
1161}
1162
1163.picker-root-name {
1164 overflow: hidden;
1165 text-overflow: ellipsis;
1166 white-space: nowrap;
1167}
1168
1169.picker-main {
1170 display: flex;
1171 flex-direction: column;
1172 min-width: 0;
1173 /* Grid items default to min-height:auto, which would let a long
1174 folder list stretch the dialog instead of scrolling. */
1175 min-height: 0;
1176}
1177
1178.picker-crumbs {
1179 padding: 8px 10px;
1180 font-size: 13px;
1181 border-bottom: 1px solid var(--border);
1182 flex: none;
1183}
1184
1185.picker-entries {
1186 flex: 1;
1187 min-height: 0;
1188 overflow-y: auto;
1189}
1190
1191.picker-row {
1192 display: flex;
1193 align-items: center;
1194 gap: 10px;
1195 padding: 8px 12px;
1196 cursor: pointer;
1197 font-size: 14px;
1198 user-select: none;
1199}
1200
1201.picker-row:hover {
1202 background: var(--bg);
1203}
1204
1205.picker-row .row-name {
1206 flex: 1;
1207}
1208
1209/* A right-pointing chevron drawn with borders (no icon needed). */
1210.picker-chev {
1211 width: 7px;
1212 height: 7px;
1213 border-right: 1.5px solid var(--muted);
1214 border-bottom: 1.5px solid var(--muted);
1215 transform: rotate(-45deg);
1216 flex: none;
1217}
1218
1219.picker-empty {
1220 height: 100%;
1221 display: flex;
1222 flex-direction: column;
1223 align-items: center;
1224 justify-content: center;
1225 gap: 8px;
1226 font-size: 13px;
1227}
1228
1229.picker-empty-ic {
1230 width: 32px;
1231 height: 32px;
1232 opacity: 0.5;
1233}
1234
1235.picker-root.active .picker-root-ic {
1236 color: #fff;
1237}
1238
1239@media (max-width: 640px) {
1240 .picker-body {
1241 grid-template-columns: 1fr;
1242 grid-template-rows: auto minmax(0, 1fr);
1243 height: 380px;
1244 }
1245
1246 .picker-roots {
1247 flex-direction: row;
1248 flex-wrap: wrap;
1249 border-right: none;
1250 border-bottom: 1px solid var(--border);
1251 }
1252}
1253
1254/* ---------------------------------------------------------------------------
1255 * Full-page file view (preview / editor / "no preview"). The top bar carries
1256 * the title + actions; the content area below is edge to edge.
1257 * ------------------------------------------------------------------------- */
1258.content.file-open {
1259 padding: 0;
1260 overflow: hidden;
1261}
1262
1263/* The browser stays mounted (state, fetch callback) but hidden. */
1264.content.file-open .browser {
1265 display: none;
1266}
1267
1268.file-view {
1269 flex: 1;
1270 min-height: 0;
1271 display: flex;
1272 flex-direction: column;
1273}
1274
1275.file-title {
1276 display: flex;
1277 align-items: center;
1278 gap: 8px;
1279 min-width: 0;
1280}
1281
1282.file-title-icon {
1283 width: 18px;
1284 height: 18px;
1285 flex: none;
1286 color: var(--muted);
1287}
1288
1289.file-title-name {
1290 font-weight: 600;
1291 font-size: 14px;
1292 white-space: nowrap;
1293 overflow: hidden;
1294 text-overflow: ellipsis;
1295}
1296
1297.file-actions {
1298 display: flex;
1299 align-items: center;
1300 gap: 10px;
1301 flex: none;
1302}
1303
1304.file-close,
1305.file-download {
1306 border: none;
1307 background: transparent;
1308 color: var(--muted);
1309 padding: 6px 8px;
1310}
1311
1312.file-close:hover,
1313.file-download:hover {
1314 color: var(--text);
1315 background: var(--bg);
1316}
1317.preview-body {
1318 flex: 1;
1319 min-height: 0;
1320 overflow: auto;
1321 display: flex;
1322 background: var(--bg);
1323}
1324.preview-img {
1325 max-width: 100%;
1326 max-height: 100%;
1327 display: block;
1328 margin: auto;
1329}
1330.preview-pdf {
1331 width: 100%;
1332 height: 100%;
1333 border: none;
1334 background: #fff;
1335}
1336.preview-video {
1337 width: 100%;
1338 max-height: 100%;
1339 background: #000;
1340}
1341.preview-audio {
1342 display: flex;
1343 align-items: center;
1344 justify-content: center;
1345 width: 100%;
1346 padding: 24px;
1347}
1348.preview-audio audio {
1349 width: 100%;
1350 max-width: 480px;
1351}
1352
1353/* CodeMirror (text editor) */
1354.cm-container {
1355 height: 100%;
1356}
1357.cm-container .cm-editor {
1358 height: 100%;
1359}
1360/* Load/save failure: dims the editor and shows the message in a card
1361 * (the top-bar Save button stays reachable to retry). */
1362.cm-error {
1363 position: absolute;
1364 inset: 0;
1365 z-index: 5;
1366 display: flex;
1367 align-items: center;
1368 justify-content: center;
1369 padding: 24px;
1370 background: rgb(0 0 0 / 45%);
1371}
1372
1373.cm-error-card {
1374 position: relative;
1375 max-width: 440px;
1376 min-width: 200px;
1377 background: var(--panel);
1378 border: 1px solid var(--border);
1379 box-shadow: 0 12px 44px rgb(0 0 0 / 35%);
1380}
1381
1382.cm-error-close {
1383 position: absolute;
1384 top: 7px;
1385 right: 7px;
1386 display: inline-flex;
1387 align-items: center;
1388 justify-content: center;
1389 border: none;
1390 background: transparent;
1391 color: var(--muted);
1392 padding: 3px;
1393}
1394
1395.cm-error-close:hover {
1396 color: var(--text);
1397 background: var(--bg);
1398}
1399
1400.cm-error-close .ic-btn {
1401 width: 16px;
1402 height: 16px;
1403}
1404
1405.cm-error-text {
1406 padding: 16px 40px 16px 20px;
1407 font-size: 14px;
1408 text-align: center;
1409 overflow-wrap: anywhere;
1410}
1411
1412/* ---------------------------------------------------------------------------
1413 * Text editor (full page; title/save/close live in the top bar)
1414 * ------------------------------------------------------------------------- */
1415.file-editor {
1416 flex: 1;
1417 min-height: 0;
1418 display: flex;
1419 position: relative;
1420 background: var(--bg);
1421}
1422.file-editor .cm-container {
1423 width: 100%;
1424 height: 100%;
1425}
1426/* "No preview" fallback (binaries, archives). */
1427.file-nopreview {
1428 flex: 1;
1429 display: flex;
1430 align-items: center;
1431 justify-content: center;
1432}
1433.file-nopreview .btn-primary {
1434 margin-top: 14px;
1435}
1436/* ---------------------------------------------------------------------------
1437 * Download format dialog
1438 * ------------------------------------------------------------------------- */
1439.dl-formats {
1440 display: flex;
1441 flex-direction: column;
1442 gap: 8px;
1443 margin-bottom: 8px;
1444}
1445.dl-fmt {
1446 justify-content: flex-start;
1447}
1448
1449/* ---------------------------------------------------------------------------
1450 * Item info dialog
1451 * ------------------------------------------------------------------------- */
1452.info-list {
1453 margin: 8px 0 16px;
1454 display: grid;
1455 grid-template-columns: 90px 1fr;
1456 row-gap: 8px;
1457 column-gap: 12px;
1458}
1459.info-list dt {
1460 color: var(--muted);
1461}
1462.info-list dd {
1463 margin: 0;
1464 word-break: break-word;
1465}
1466
1467/* ---------------------------------------------------------------------------
1468 Shares (milestone 6)
1469 --------------------------------------------------------------------------- */
1470
1471.share-modal {
1472 width: 480px;
1473}
1474
1475.share-link-row {
1476 display: flex;
1477 gap: 8px;
1478 margin: 12px 0;
1479}
1480
1481.share-link-input {
1482 flex: 1;
1483 font-family: ui-monospace, monospace;
1484 font-size: 12.5px;
1485 padding: 8px 10px;
1486 border: 1px solid var(--border);
1487 background: var(--bg);
1488 color: var(--text);
1489 min-width: 0;
1490}
1491
1492.share-check {
1493 display: flex;
1494 align-items: center;
1495 gap: 8px;
1496 margin: 4px 0 14px;
1497 font-size: 14px;
1498}
1499
1500.share-check input {
1501 width: 16px;
1502 height: 16px;
1503}
1504
1505.share-expiry-row {
1506 display: flex;
1507 align-items: center;
1508 gap: 10px;
1509 margin: 0 0 10px;
1510}
1511
1512.share-expiry-row label {
1513 font-size: 13px;
1514 color: var(--muted);
1515}
1516
1517.share-expiry-row select,
1518.share-expiry-row input {
1519 flex: 1;
1520 padding: 8px 10px;
1521 border: 1px solid var(--border);
1522 background: var(--panel);
1523 color: var(--text);
1524 font: inherit;
1525}
1526
1527/* Optional password on the create-share form. Laid out like the expiry
1528 row above it, so the form reads as one column of labelled fields. */
1529.share-pw-row {
1530 display: flex;
1531 align-items: center;
1532 gap: 10px;
1533 margin: 0 0 6px;
1534}
1535
1536.share-pw-row label {
1537 font-size: 13px;
1538 color: var(--muted);
1539}
1540
1541/* Expiry and password sit in one column, so their inputs line up. */
1542.share-expiry-row label,
1543.share-pw-row label {
1544 min-width: 64px;
1545}
1546
1547.share-pw-row input {
1548 flex: 1;
1549 padding: 8px 10px;
1550 border: 1px solid var(--border);
1551 background: var(--panel);
1552 color: var(--text);
1553 font: inherit;
1554}
1555
1556.share-pw-hint {
1557 font-size: 12px;
1558 margin: 0 0 10px;
1559}
1560
1561/* The password gate of a protected share: the whole page is this card. */
1562.unlock-card {
1563 width: 320px;
1564 max-width: calc(100vw - 32px);
1565 padding: 20px;
1566 text-align: left;
1567}
1568
1569.unlock-card h2 {
1570 margin: 0 0 6px;
1571 font-size: 18px;
1572}
1573
1574.unlock-card input {
1575 margin-top: 14px;
1576}
1577
1578.unlock-btn {
1579 width: 100%;
1580 margin-top: 12px;
1581}
1582
1583.share-expiry-note {
1584 margin: 4px 0 0;
1585 font-size: 12.5px;
1586}
1587
1588.btn-sm {
1589 padding: 5px 10px;
1590 font-size: 12.5px;
1591}
1592
1593.icon-btn-sm {
1594 padding: 4px 6px;
1595}
1596
1597/* "Your shares" manage view */
1598
1599.shares-view {
1600 width: 100%;
1601 max-width: 960px;
1602 margin: 0 auto;
1603}
1604
1605.view-header {
1606 margin-bottom: 16px;
1607}
1608
1609.view-header .admin-section-title {
1610 margin-bottom: 0;
1611}
1612
1613.view-hint {
1614 margin: 4px 0 0;
1615 font-size: 13px;
1616 max-width: 64ch;
1617}
1618
1619.share-list {
1620 display: flex;
1621 flex-direction: column;
1622 gap: 10px;
1623}
1624
1625.share-item {
1626 display: flex;
1627 align-items: center;
1628 justify-content: space-between;
1629 gap: 12px;
1630 padding: 10px 12px;
1631 border: 1px solid var(--border);
1632 background: var(--panel);
1633}
1634
1635.share-item-main {
1636 min-width: 0;
1637}
1638
1639.share-item-name {
1640 display: flex;
1641 align-items: center;
1642 gap: 8px;
1643 font-weight: 600;
1644 font-size: 14px;
1645 overflow: hidden;
1646 text-overflow: ellipsis;
1647 white-space: nowrap;
1648}
1649
1650.ic-row {
1651 width: 18px;
1652 height: 18px;
1653 flex: none;
1654}
1655
1656.share-item-meta {
1657 font-size: 12px;
1658 margin-top: 2px;
1659 overflow: hidden;
1660 text-overflow: ellipsis;
1661 white-space: nowrap;
1662}
1663
1664.share-item-actions {
1665 display: flex;
1666 gap: 6px;
1667 flex-shrink: 0;
1668}
1669
1670/* Phones: the name gets the full width, the buttons move below it. */
1671@media (max-width: 640px) {
1672 .share-item {
1673 flex-wrap: wrap;
1674 }
1675
1676 .share-item-main,
1677 .share-item-actions {
1678 flex: 1 1 100%;
1679 min-width: 0;
1680 }
1681
1682 .share-item-actions {
1683 flex-wrap: wrap;
1684 }
1685
1686 .shares-view .share-item-actions select {
1687 flex: 1 1 auto;
1688 min-width: 0;
1689 }
1690
1691 .share-item-actions .btn {
1692 white-space: nowrap;
1693 }
1694}
1695
1696/* A loan's mode select sits beside small buttons: same height. No
1697 percentage cap: the actions box sizes to its content, so a cap would
1698 leave a gap after the select. */
1699.shares-view .share-item-actions select {
1700 max-width: none;
1701 padding-top: 5px;
1702 padding-bottom: 5px;
1703 font-size: 12.5px;
1704}
1705
1706/* Sections of the share management view: the viewer's own links and, for an
1707 admin, one section per account. Native <details>/<summary>. */
1708
1709.share-groups {
1710 display: flex;
1711 flex-direction: column;
1712 gap: 12px;
1713}
1714
1715.share-group {
1716 border: 1px solid var(--border);
1717 background: var(--panel);
1718}
1719
1720.share-group-head {
1721 display: flex;
1722 align-items: center;
1723 gap: 8px;
1724 padding: 10px 12px;
1725 font-size: 14px;
1726 font-weight: 600;
1727 cursor: pointer;
1728 /* The caret below stands in for the disclosure triangle. */
1729 list-style: none;
1730}
1731
1732.share-group-head::-webkit-details-marker {
1733 display: none;
1734}
1735
1736.share-group-head:hover {
1737 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
1738}
1739
1740.share-group-head:focus-visible {
1741 outline: 2px solid var(--accent);
1742 outline-offset: -2px;
1743}
1744
1745.share-caret {
1746 width: 16px;
1747 height: 16px;
1748 flex: none;
1749 transition: rotate 0.15s ease;
1750}
1751
1752.share-group[open] .share-caret {
1753 rotate: 90deg;
1754}
1755
1756.share-group-count {
1757 font-size: 12px;
1758 font-weight: 400;
1759}
1760
1761/* A live link whose account cannot sign in any more. */
1762.share-group-warn {
1763 font-size: 12px;
1764 font-weight: 400;
1765 color: var(--danger);
1766}
1767
1768.share-group-del {
1769 margin-left: auto;
1770}
1771
1772/* Rows inside a section read as one block: the border moves from each row to
1773 the section. */
1774.share-group .share-list {
1775 gap: 0;
1776 border-top: 1px solid var(--border);
1777}
1778
1779.share-group .share-item {
1780 border: 0;
1781 border-top: 1px solid var(--border);
1782}
1783
1784.share-group .share-item:first-child {
1785 border-top: 0;
1786}
1787
1788/* ------------------------------------------------------------------ */
1789/* Admin (milestone 7) */
1790/* ------------------------------------------------------------------ */
1791
1792.admin-view {
1793 display: flex;
1794 flex-direction: column;
1795 gap: 16px;
1796 width: 100%;
1797 max-width: 960px;
1798 margin: 0 auto;
1799}
1800
1801.admin-section-title {
1802 margin: 0 0 14px;
1803 font-size: 15px;
1804}
1805
1806.users-header {
1807 display: flex;
1808 align-items: center;
1809 justify-content: space-between;
1810 margin-bottom: 4px;
1811}
1812
1813.users-header .admin-section-title {
1814 margin-bottom: 0;
1815}
1816
1817.users-header .btn-primary {
1818 width: auto;
1819}
1820
1821.user-list {
1822 display: flex;
1823 flex-direction: column;
1824 gap: 10px;
1825}
1826
1827.user-row {
1828 display: flex;
1829 align-items: center;
1830 justify-content: space-between;
1831 gap: 12px;
1832 padding: 12px 14px;
1833 border: 1px solid var(--border);
1834}
1835
1836.user-row-main {
1837 min-width: 0;
1838}
1839
1840.user-row-name {
1841 display: flex;
1842 align-items: center;
1843 gap: 6px;
1844 font-weight: 600;
1845 font-size: 14px;
1846 flex-wrap: wrap;
1847}
1848
1849.ic-admin {
1850 width: 16px;
1851 height: 16px;
1852 flex: none;
1853}
1854
1855.user-row-actions {
1856 display: flex;
1857 gap: 6px;
1858 flex-shrink: 0;
1859}
1860
1861.badge-off {
1862 background: color-mix(in srgb, var(--danger) 15%, var(--panel));
1863 border-color: var(--danger);
1864 color: var(--danger);
1865}
1866
1867.setting-row {
1868 display: flex;
1869 align-items: center;
1870 gap: 10px;
1871 padding: 14px;
1872 border: 1px solid var(--border);
1873 cursor: pointer;
1874}
1875
1876.setting-row input {
1877 margin-left: auto;
1878}
1879
1880.setting-label {
1881 display: block;
1882 font-weight: 600;
1883 font-size: 14px;
1884}
1885
1886.setting-desc {
1887 display: block;
1888 font-size: 12px;
1889 color: var(--muted);
1890 margin-top: 2px;
1891}
1892
1893/* Setting row whose control is a select, pushed to the right. */
1894.setting-row-select {
1895 cursor: default;
1896}
1897
1898.setting-row-select > span {
1899 flex: 1;
1900}
1901
1902.setting-row-select select {
1903 appearance: none;
1904 margin-left: auto;
1905 padding: 10px 42px 10px 12px;
1906 border: 1px solid var(--border);
1907 background-color: var(--panel);
1908 background-image: var(--select-arrow);
1909 background-repeat: no-repeat;
1910 background-position: right 12px center;
1911 background-size: 14px 14px;
1912 color: var(--text);
1913 font: inherit;
1914 max-width: 190px;
1915}
1916
1917/* User create/edit form */
1918
1919.user-form {
1920 width: 520px;
1921}
1922
1923.field-input {
1924 width: 100%;
1925 box-sizing: border-box;
1926}
1927
1928/* Search exclusions: a bordered block matching the setting row above it. */
1929.excludes-field {
1930 display: flex;
1931 flex-direction: column;
1932 gap: 10px;
1933 padding: 14px;
1934 border: 1px solid var(--border);
1935}
1936
1937.excludes-list {
1938 display: flex;
1939 flex-direction: column;
1940 gap: 2px;
1941}
1942
1943.exclude-row {
1944 display: flex;
1945 align-items: center;
1946 gap: 8px;
1947 padding: 4px 0;
1948}
1949
1950.exclude-path {
1951 flex: 1;
1952 font-size: 13px;
1953 overflow: hidden;
1954 text-overflow: ellipsis;
1955 white-space: nowrap;
1956}
1957
1958.exclude-remove {
1959 flex: none;
1960 padding: 4px 6px;
1961}
1962
1963.exclude-add {
1964 align-self: flex-start;
1965}
1966
1967.excludes-empty {
1968 font-size: 13px;
1969 margin: 0;
1970}
1971
1972.field-hint {
1973 font-size: 12px;
1974 margin: -2px 0 8px;
1975}
1976
1977.check-row {
1978 display: flex;
1979 align-items: center;
1980 gap: 8px;
1981 font-size: 14px;
1982 margin-bottom: 12px;
1983 cursor: pointer;
1984}
1985
1986.form-error {
1987 color: var(--danger);
1988 font-size: 13px;
1989 margin: 4px 0 8px;
1990}
1991
1992.roots-editor {
1993 display: flex;
1994 flex-direction: column;
1995 gap: 6px;
1996}
1997
1998.root-draft {
1999 display: flex;
2000 align-items: center;
2001 gap: 8px;
2002}
2003
2004.root-draft-path {
2005 flex: 1;
2006 font-size: 13px;
2007 overflow: hidden;
2008 text-overflow: ellipsis;
2009 white-space: nowrap;
2010}
2011
2012.root-draft-mode {
2013 width: auto;
2014 padding: 6px 8px;
2015}
2016
2017.root-add {
2018 align-self: flex-start;
2019 margin-top: 4px;
2020}
2021
2022/* ------------------------------------------------------------------ */
2023/* Empty state + keyboard focus (milestone 8) */
2024/* ------------------------------------------------------------------ */
2025
2026.empty-state {
2027 display: flex;
2028 flex-direction: column;
2029 align-items: center;
2030 justify-content: center;
2031 gap: 8px;
2032 padding: 64px 20px;
2033 text-align: center;
2034}
2035
2036.empty-glyph {
2037 width: 52px;
2038 height: 52px;
2039}
2040
2041.empty-state h3 {
2042 margin: 0;
2043 font-size: 16px;
2044}
2045
2046/* Visible keyboard focus on selectable file/folder entries. The ring is
2047 inset (negative offset) so the scroll container never clips it. */
2048.tile:focus-visible,
2049.row:focus-visible {
2050 outline: 2px solid var(--accent);
2051 outline-offset: -2px;
2052}
2053
2054/* ------------------------------------------------------------------ */
2055/* Search view (#/search) */
2056/* ------------------------------------------------------------------ */
2057
2058.search-view {
2059 display: flex;
2060 flex-direction: column;
2061 gap: 14px;
2062}
2063
2064/* Query bar ------------------------------------------------------ */
2065
2066.query-bar {
2067 display: flex;
2068 align-items: stretch;
2069 gap: 8px;
2070}
2071
2072.query-input {
2073 flex: 1;
2074 display: flex;
2075 align-items: center;
2076 gap: 8px;
2077 background: var(--panel);
2078 border: 1px solid var(--border);
2079 padding: 0 10px;
2080 min-width: 0;
2081}
2082
2083.query-input .qi-icon {
2084 width: 18px;
2085 height: 18px;
2086 color: var(--muted);
2087 flex: none;
2088}
2089
2090.query-input input {
2091 flex: 1;
2092 min-width: 0;
2093 border: none;
2094 outline: none;
2095 background: transparent;
2096 color: var(--text);
2097 font: inherit;
2098 font-size: 14px;
2099 height: 38px;
2100 padding: 0;
2101}
2102
2103.query-input input::placeholder {
2104 color: var(--muted);
2105}
2106
2107/* Segmented Name / Content / Both control */
2108
2109.seg {
2110 display: flex;
2111 border: 1px solid var(--border);
2112 background: var(--panel);
2113}
2114
2115.seg button {
2116 appearance: none;
2117 border: none;
2118 border-right: 1px solid var(--border);
2119 background: transparent;
2120 color: var(--muted);
2121 font: inherit;
2122 font-size: 13px;
2123 padding: 0 14px;
2124 cursor: pointer;
2125}
2126
2127.seg button:last-child {
2128 border-right: none;
2129}
2130
2131.seg button:hover {
2132 background: var(--bg);
2133}
2134
2135.seg button.on {
2136 background: var(--accent);
2137 color: #fff;
2138}
2139
2140/* Start folder --------------------------------------------------- */
2141
2142/* The folder a search starts in; opens the folder picker. */
2143.folder-pick {
2144 flex: none;
2145 display: flex;
2146 align-items: center;
2147 gap: 8px;
2148 max-width: 260px;
2149 border: 1px solid var(--border);
2150 background: var(--panel);
2151 color: var(--text);
2152 font: inherit;
2153 font-size: 13px;
2154 padding: 0 10px;
2155 cursor: pointer;
2156}
2157
2158.folder-pick:hover {
2159 border-color: var(--muted);
2160}
2161
2162.folder-pick .fp-icon {
2163 width: 16px;
2164 height: 16px;
2165 color: var(--muted);
2166 flex: none;
2167}
2168
2169.folder-pick .fp-path {
2170 overflow: hidden;
2171 text-overflow: ellipsis;
2172 white-space: nowrap;
2173}
2174
2175/* Run / stop ------------------------------------------------------- */
2176
2177.btn-run {
2178 border: 1px solid var(--accent-border);
2179 background: var(--accent);
2180 color: #fff;
2181 font: inherit;
2182 font-size: 13px;
2183 font-weight: 600;
2184 padding: 0 18px;
2185 cursor: pointer;
2186}
2187
2188.btn-run:hover {
2189 background: var(--accent-hover);
2190}
2191
2192.btn-stop {
2193 display: flex;
2194 align-items: center;
2195 gap: 6px;
2196 border: 1px solid var(--danger-border);
2197 background: var(--danger);
2198 color: #fff;
2199 font: inherit;
2200 font-size: 13px;
2201 font-weight: 600;
2202 padding: 0 14px;
2203 cursor: pointer;
2204}
2205
2206.btn-stop:hover {
2207 opacity: 0.9;
2208}
2209
2210.btn-stop .ic-stop {
2211 width: 14px;
2212 height: 14px;
2213}
2214
2215/* One row needs more than a phone width. The query and run button stay
2216 first, the scope and the folder get their own rows. */
2217@media (max-width: 640px) {
2218 .query-bar {
2219 flex-wrap: wrap;
2220 }
2221
2222 .seg,
2223 .folder-pick {
2224 order: 1;
2225 flex: 1 1 100%;
2226 min-width: 0;
2227 max-width: none;
2228 min-height: 38px;
2229 }
2230
2231 .seg button {
2232 flex: 1;
2233 }
2234}
2235
2236/* Status line ------------------------------------------------------ */
2237
2238.status-line {
2239 display: flex;
2240 align-items: center;
2241 gap: 8px;
2242 font-size: 13px;
2243 color: var(--muted);
2244}
2245
2246.status-line .dot {
2247 width: 8px;
2248 height: 8px;
2249 background: var(--accent);
2250 animation: search-pulse 1s ease-in-out infinite;
2251}
2252
2253@keyframes search-pulse {
2254 0%,
2255 100% {
2256 opacity: 1;
2257 }
2258 50% {
2259 opacity: 0.3;
2260 }
2261}
2262
2263.status-line .skip {
2264 opacity: 0.75;
2265}
2266
2267/* Group labels ----------------------------------------------------- */
2268
2269.group-label {
2270 display: flex;
2271 align-items: center;
2272 gap: 8px;
2273 font-size: 12px;
2274 font-weight: 600;
2275 letter-spacing: 0.04em;
2276 text-transform: uppercase;
2277 color: var(--muted);
2278}
2279
2280/* Result group: a flex item of `.search-view` that re-establishes the
2281 column layout for its label/list/more-row children. */
2282.result-group {
2283 display: flex;
2284 flex-direction: column;
2285 gap: 14px;
2286}
2287
2288.group-label .gl-icon {
2289 width: 15px;
2290 height: 15px;
2291}
2292
2293.group-label .n {
2294 font-weight: 400;
2295 text-transform: none;
2296 letter-spacing: 0;
2297}
2298
2299.group-label .grow {
2300 flex: 1;
2301}
2302
2303.collapse-all {
2304 appearance: none;
2305 border: 1px solid var(--border);
2306 background: transparent;
2307 color: var(--muted);
2308 font: inherit;
2309 font-size: 12px;
2310 text-transform: none;
2311 letter-spacing: 0;
2312 padding: 3px 10px;
2313 cursor: pointer;
2314}
2315
2316.collapse-all:hover {
2317 background: var(--bg);
2318 color: var(--text);
2319}
2320
2321/* Name-result rows --------------------------------------------------- */
2322
2323.srow {
2324 display: grid;
2325 /* icon | name+dir | size | goto */
2326 grid-template-columns: 28px minmax(0, 1fr) 90px 30px;
2327 gap: 12px;
2328 align-items: center;
2329 padding: 9px 14px;
2330 border-bottom: 1px solid var(--border);
2331 cursor: pointer;
2332 font-size: 14px;
2333 user-select: none;
2334}
2335
2336.srow:last-child {
2337 border-bottom: none;
2338}
2339
2340.srow:hover {
2341 background: var(--bg);
2342}
2343
2344.srow-icon {
2345 width: 20px;
2346 height: 20px;
2347}
2348
2349.srow-main {
2350 min-width: 0;
2351 display: flex;
2352 flex-direction: column;
2353 line-height: 1.3;
2354}
2355
2356.srow-name {
2357 overflow: hidden;
2358 text-overflow: ellipsis;
2359 white-space: nowrap;
2360}
2361
2362/* Highlighted query hits (plain fragments render as bare spans). */
2363mark.hl-hit {
2364 background: color-mix(in srgb, var(--accent) 25%, transparent);
2365 color: inherit;
2366}
2367
2368/* The directory-path line (the name line lives inside .srow-name). */
2369.srow-dir {
2370 font-size: 12px;
2371 color: var(--muted);
2372 overflow: hidden;
2373 text-overflow: ellipsis;
2374 white-space: nowrap;
2375}
2376
2377.srow-dir mark.hl-hit {
2378 color: inherit;
2379}
2380
2381.ssize {
2382 font-size: 13px;
2383 color: var(--muted);
2384 text-align: right;
2385}
2386
2387/* Paging footer: position in the results on the left, the action on the
2388 right. Separated from the list by its own border so the button never reads
2389 as another result row. */
2390.page-foot {
2391 display: flex;
2392 flex-wrap: wrap;
2393 align-items: center;
2394 justify-content: space-between;
2395 gap: 12px;
2396 padding: 10px 14px;
2397 border-top: 1px solid var(--border);
2398 font-size: 12px;
2399 color: var(--muted);
2400}
2401
2402.page-foot .pf-count {
2403 font-variant-numeric: tabular-nums;
2404}
2405
2406.page-foot .pf-more,
2407.pf-btn {
2408 appearance: none;
2409 padding: 5px 12px;
2410 border: 1px solid var(--border);
2411 background: var(--panel);
2412 font: inherit;
2413 font-weight: 500;
2414 color: var(--text);
2415 cursor: pointer;
2416}
2417
2418.page-foot .pf-more:hover,
2419.pf-btn:hover:not(:disabled) {
2420 border-color: var(--accent);
2421 color: var(--accent);
2422}
2423
2424/* Up to 13 buttons: unwrapped they overflow a phone screen. */
2425.pf-pages {
2426 display: flex;
2427 flex-wrap: wrap;
2428 align-items: center;
2429 gap: 4px;
2430 margin-right: auto;
2431}
2432
2433.pf-btn {
2434 min-width: 30px;
2435 padding: 5px 8px;
2436 font-variant-numeric: tabular-nums;
2437}
2438
2439.pf-btn.current {
2440 border-color: var(--accent);
2441 background: var(--accent);
2442 color: #fff;
2443}
2444
2445.pf-btn:disabled {
2446 opacity: 0.4;
2447 cursor: default;
2448}
2449
2450.pf-goto {
2451 display: flex;
2452 align-items: center;
2453 gap: 6px;
2454 margin: 0;
2455}
2456
2457.pf-goto input {
2458 width: 72px;
2459 padding: 5px 8px;
2460}
2461
2462/* An arrow-key page turn is loading. Fixed at the top, so it also shows over
2463 the preview. The delay keeps fast turns from flashing. */
2464.page-busy {
2465 position: fixed;
2466 top: 0;
2467 left: 0;
2468 right: 0;
2469 height: 3px;
2470 overflow: hidden;
2471 z-index: 250;
2472 pointer-events: none;
2473 animation: page-busy-in 0s 200ms both;
2474}
2475
2476.page-busy::after {
2477 content: "";
2478 display: block;
2479 width: 30%;
2480 height: 100%;
2481 background: var(--accent);
2482 animation: upload-indeterminate 1.2s ease-in-out infinite;
2483}
2484
2485body:has(.page-busy) {
2486 cursor: progress;
2487}
2488
2489@keyframes page-busy-in {
2490 from {
2491 opacity: 0;
2492 }
2493 to {
2494 opacity: 1;
2495 }
2496}
2497
2498/* Content matches ----------------------------------------------------- */
2499
2500.match-card {
2501 background: var(--panel);
2502 border: 1px solid var(--border);
2503 border-top: none;
2504}
2505
2506/* The first card is the one right after the group label; `:first-of-type`
2507 cannot be used because the label is an earlier `div` sibling. */
2508.group-label + .match-card {
2509 border-top: 1px solid var(--border);
2510}
2511
2512/* The header opens the file; the matched lines below are text to read. */
2513.match-head {
2514 display: flex;
2515 align-items: center;
2516 gap: 10px;
2517 padding: 9px 14px;
2518 font-size: 14px;
2519 cursor: pointer;
2520}
2521
2522.match-head:hover {
2523 background: var(--bg);
2524}
2525
2526.match-head .mi {
2527 width: 18px;
2528 height: 18px;
2529 color: var(--muted);
2530 flex: none;
2531}
2532
2533/* Takes the slack, so the count and the two buttons sit at the right edge. */
2534.mpath {
2535 flex: 1;
2536 min-width: 0;
2537 overflow: hidden;
2538 text-overflow: ellipsis;
2539 white-space: nowrap;
2540}
2541
2542.mpath b {
2543 font-weight: 600;
2544}
2545
2546.mcount {
2547 flex: none;
2548 font-size: 12px;
2549 color: var(--muted);
2550}
2551
2552/* "Show in folder", in both the name rows and the match-card headers. */
2553.goto-btn {
2554 appearance: none;
2555 flex: none;
2556 justify-self: end;
2557 display: flex;
2558 align-items: center;
2559 justify-content: center;
2560 width: 26px;
2561 height: 26px;
2562 padding: 0;
2563 border: 1px solid transparent;
2564 background: transparent;
2565 cursor: pointer;
2566}
2567
2568.goto-btn:hover {
2569 border-color: var(--border);
2570}
2571
2572.goto-btn .goto-ic {
2573 width: 16px;
2574 height: 16px;
2575 color: var(--muted);
2576}
2577
2578.goto-btn:hover .goto-ic {
2579 color: var(--accent);
2580}
2581
2582.chev {
2583 appearance: none;
2584 border: 1px solid var(--border);
2585 background: transparent;
2586 width: 26px;
2587 height: 26px;
2588 display: flex;
2589 align-items: center;
2590 justify-content: center;
2591 cursor: pointer;
2592 flex: none;
2593}
2594
2595.chev:hover {
2596 background: var(--bg);
2597}
2598
2599.chev .chev-mark {
2600 display: block;
2601 width: 10px;
2602 height: 10px;
2603 border-right: 2px solid var(--muted);
2604 border-bottom: 2px solid var(--muted);
2605 transform: rotate(45deg) translate(-1px, -1px);
2606}
2607
2608.chev .chev-mark.up {
2609 transform: rotate(-135deg) translate(-1px, -1px);
2610}
2611
2612.match-lines {
2613 border-top: 1px solid var(--border);
2614 background: var(--bg);
2615 font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
2616 font-size: 12.5px;
2617}
2618
2619/* Matched lines are text to read, not a control: the card header is what
2620 opens the file, so no pointer cursor and no hover here. */
2621.mline {
2622 display: grid;
2623 grid-template-columns: 52px minmax(0, 1fr);
2624 gap: 12px;
2625 padding: 5px 14px;
2626 border-bottom: 1px solid var(--border);
2627 white-space: pre;
2628 user-select: text;
2629}
2630
2631.mline:last-child {
2632 border-bottom: none;
2633}
2634
2635.mline .ln {
2636 color: var(--muted);
2637 text-align: right;
2638 user-select: none;
2639}
2640
2641.mline .tx {
2642 overflow: hidden;
2643 text-overflow: ellipsis;
2644}
2645
2646.search-empty {
2647 padding: 48px 20px;
2648 text-align: center;
2649 color: var(--muted);
2650 font-size: 14px;
2651}
2652
2653/* ------------------------------------------------------------------ */
2654/* Uploads: overlay panel + log view */
2655/* ------------------------------------------------------------------ */
2656
2657.upload-panel {
2658 position: fixed;
2659 right: 20px;
2660 bottom: 20px;
2661 z-index: 190;
2662 width: min(380px, calc(100vw - 40px));
2663 background: var(--panel);
2664 color: var(--text);
2665 border: 1px solid var(--border);
2666 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
2667 font-size: 13px;
2668}
2669
2670/* The toast sits above the panel when both show. */
2671.upload-panel ~ .toast,
2672.toast:has(~ .upload-panel) {
2673 bottom: 80px;
2674}
2675
2676.upload-panel-head {
2677 display: flex;
2678 align-items: center;
2679 gap: 8px;
2680 padding: 8px 10px;
2681 border-bottom: 1px solid var(--border);
2682}
2683
2684.upload-panel.collapsed .upload-panel-head {
2685 border-bottom: none;
2686}
2687
2688.upload-panel-title {
2689 font-weight: 600;
2690}
2691
2692.upload-panel-summary {
2693 margin-left: auto;
2694 font-size: 12px;
2695}
2696
2697.upload-panel-list {
2698 max-height: 50vh;
2699 overflow-y: auto;
2700}
2701
2702.upload-job {
2703 padding: 8px 10px;
2704 border-bottom: 1px solid var(--border);
2705}
2706
2707.upload-job:last-child {
2708 border-bottom: none;
2709}
2710
2711.upload-job-head {
2712 display: flex;
2713 align-items: center;
2714 gap: 4px;
2715}
2716
2717.upload-job-label {
2718 flex: 1;
2719 min-width: 0;
2720 overflow: hidden;
2721 text-overflow: ellipsis;
2722 white-space: nowrap;
2723 font-weight: 500;
2724}
2725
2726.upload-job-pct {
2727 font-variant-numeric: tabular-nums;
2728 margin-right: 4px;
2729}
2730
2731.upload-bar {
2732 height: 4px;
2733 margin: 6px 0 4px;
2734 /* Clip the indeterminate block: it slides past both ends. */
2735 overflow: hidden;
2736 background: color-mix(in srgb, var(--accent) 15%, var(--panel));
2737}
2738
2739.upload-bar-fill {
2740 height: 100%;
2741 background: var(--accent);
2742 transition: width 0.2s linear;
2743}
2744
2745/* Preparing: nothing to measure yet, so a sliding block instead of a fill. */
2746.upload-bar.indeterminate .upload-bar-fill {
2747 width: 30% !important;
2748 animation: upload-indeterminate 1.2s ease-in-out infinite;
2749}
2750
2751@keyframes upload-indeterminate {
2752 0% {
2753 transform: translateX(-100%);
2754 }
2755 100% {
2756 transform: translateX(333%);
2757 }
2758}
2759
2760.upload-job.failed .upload-bar-fill {
2761 background: var(--danger);
2762}
2763
2764.upload-job.done .upload-bar-fill {
2765 background: var(--muted);
2766}
2767
2768.upload-job-state {
2769 font-size: 12px;
2770 overflow: hidden;
2771 text-overflow: ellipsis;
2772 white-space: nowrap;
2773}
2774
2775.upload-job.failed .upload-job-state {
2776 color: var(--danger);
2777 white-space: normal;
2778}
2779
2780.upload-conflict {
2781 display: flex;
2782 flex-direction: column;
2783 gap: 6px;
2784 margin-top: 8px;
2785 padding: 8px;
2786 border: 1px solid var(--danger-border);
2787 background: color-mix(in srgb, var(--danger) 8%, var(--panel));
2788}
2789
2790.upload-conflict-all {
2791 display: flex;
2792 /* The label wraps on a narrow dialog; keep the box on the first line. */
2793 align-items: flex-start;
2794 gap: 0.5rem;
2795 font-size: 12px;
2796 cursor: pointer;
2797}
2798
2799.upload-conflict-all input {
2800 flex: none;
2801 margin-top: 2px;
2802}
2803
2804.modal .upload-conflict-all {
2805 margin: 8px 0 0;
2806 font-size: 13px;
2807}
2808
2809.upload-conflict-actions {
2810 display: flex;
2811 justify-content: flex-end;
2812 gap: 6px;
2813}
2814
2815.upload-graph {
2816 margin-top: 8px;
2817 color: var(--accent);
2818 border: 1px solid var(--border);
2819 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
2820}
2821
2822/* Peak label above the plot, so the line can never run through it. */
2823.upload-graph-max {
2824 display: block;
2825 text-align: right;
2826 padding: 2px 6px 0;
2827 font-size: 11px;
2828 line-height: 14px;
2829}
2830
2831.upload-graph svg {
2832 display: block;
2833 width: 100%;
2834 height: 46px;
2835}
2836
2837.upload-errors {
2838 margin: 6px 0 0;
2839 padding-left: 18px;
2840 font-size: 12px;
2841 color: var(--danger);
2842}
2843
2844.upload-err-file {
2845 font-weight: 500;
2846}
2847
2848/* Log view: the same rows, full width, roomier. */
2849.uploads-view .upload-log {
2850 border: 1px solid var(--border);
2851 background: var(--panel);
2852 font-size: 14px;
2853}
2854
2855.uploads-view .upload-job {
2856 padding: 12px 14px;
2857}
2858
2859.uploads-view .upload-graph svg {
2860 height: 76px;
2861}
2862
2863/* Security settings tab: password, passkeys, sign-in requirement. */
2864
2865.security-tab {
2866 display: flex;
2867 flex-direction: column;
2868 gap: 10px;
2869}
2870
2871.security-tab h3 {
2872 margin: 10px 0 0;
2873}
2874
2875/* Matches .excludes-field, so the three blocks of this tab read as one list. */
2876.security-form {
2877 display: flex;
2878 flex-direction: column;
2879 gap: 10px;
2880 padding: 14px;
2881 border: 1px solid var(--border);
2882}
2883
2884.security-form .field input {
2885 width: 100%;
2886 box-sizing: border-box;
2887}
2888
2889.passkey-list {
2890 display: flex;
2891 flex-direction: column;
2892 gap: 2px;
2893 padding: 0 14px;
2894}
2895
2896.passkey-row {
2897 display: flex;
2898 align-items: center;
2899 gap: 8px;
2900 padding: 6px 0;
2901}
2902
2903.passkey-main {
2904 flex: 1;
2905 overflow: hidden;
2906}
2907
2908.passkey-name {
2909 display: block;
2910 font-size: 13px;
2911 overflow: hidden;
2912 text-overflow: ellipsis;
2913 white-space: nowrap;
2914}
2915
2916/* "Needs your user name": a note, not an error. This passkey works, it just
2917 cannot be found without the name typed in first. */
2918.passkey-warning {
2919 display: inline-block;
2920 margin-top: 2px;
2921 padding: 1px 6px;
2922 border: 1px solid var(--border);
2923 font-size: 11px;
2924 color: var(--muted);
2925}
2926
2927/* The sign-in requirement's labels are whole phrases, so the shared 190px
2928 cap for setting selects would clip them. */
2929.security-tab .setting-row-select select {
2930 max-width: none;
2931 width: auto;
2932}
2933
2934/* Separator between the password form and the passkey button. `.auth-card p`
2935 sets a bottom-only margin on every paragraph in the card, and wins on
2936 specificity, so both of these have to name the card too. */
2937.auth-card .login-or {
2938 margin: 16px 0 8px;
2939 font-size: 12px;
2940 color: var(--muted);
2941 text-transform: lowercase;
2942}
2943
2944.auth-card .setting-desc {
2945 margin-top: 8px;
2946}
2947
2948.auth-card .btn {
2949 width: 100%;
2950}
2951
2952/* ------------------------------------------------------------------ */
2953/* Calendars and contacts */
2954/* ------------------------------------------------------------------ */
2955
2956.pim-view {
2957 display: flex;
2958 gap: 16px;
2959 flex: 1;
2960 min-height: 0;
2961 width: 100%;
2962}
2963
2964.pim-side {
2965 flex: none;
2966 width: 260px;
2967 display: flex;
2968 flex-direction: column;
2969 gap: 8px;
2970 min-width: 0;
2971}
2972
2973.pim-main {
2974 flex: 1;
2975 min-width: 0;
2976 border: 1px solid var(--border);
2977 background: var(--panel);
2978 display: flex;
2979 flex-direction: column;
2980 min-height: 0;
2981}
2982
2983.pim-side-head {
2984 display: flex;
2985 align-items: center;
2986 gap: 4px;
2987}
2988
2989.pim-side-head .admin-section-title {
2990 flex: 1;
2991 margin: 0;
2992}
2993
2994.pim-list {
2995 display: flex;
2996 flex-direction: column;
2997 border: 1px solid var(--border);
2998 background: var(--panel);
2999}
3000
3001.pim-group-label {
3002 padding: 10px 10px 4px;
3003 font-size: 12px;
3004 color: var(--muted);
3005 border-top: 1px solid var(--border);
3006}
3007
3008.pim-row {
3009 display: flex;
3010 align-items: center;
3011 min-width: 0;
3012}
3013
3014.pim-row:hover {
3015 background: var(--bg);
3016}
3017
3018.pim-toggle {
3019 flex: none;
3020 display: inline-flex;
3021 align-items: center;
3022 justify-content: center;
3023 width: 36px;
3024 height: 36px;
3025 padding: 0;
3026 border: 0;
3027 background: none;
3028 cursor: pointer;
3029}
3030
3031.pim-swatch {
3032 display: inline-block;
3033 flex: none;
3034 width: 14px;
3035 height: 14px;
3036 background: var(--swatch);
3037 border: 2px solid var(--swatch);
3038}
3039
3040/* A hidden collection keeps its color as an outline only. */
3041.pim-row-hidden .pim-swatch {
3042 background: transparent;
3043}
3044
3045.pim-row-hidden .pim-row-name {
3046 color: var(--muted);
3047}
3048
3049.pim-row-main {
3050 flex: 1;
3051 min-width: 0;
3052 display: flex;
3053 align-items: baseline;
3054 justify-content: flex-start;
3055 gap: 6px;
3056 padding: 8px 10px 8px 0;
3057 border: 0;
3058 background: none;
3059 color: var(--text);
3060 font: inherit;
3061 font-size: 14px;
3062 text-align: left;
3063 cursor: pointer;
3064}
3065
3066.pim-row-name {
3067 min-width: 0;
3068 overflow: hidden;
3069 text-overflow: ellipsis;
3070 white-space: nowrap;
3071}
3072
3073.pim-row-gear {
3074 flex: none;
3075 margin-right: 4px;
3076}
3077
3078.pim-show-all {
3079 align-self: flex-start;
3080}
3081
3082.pim-row-note {
3083 flex: none;
3084 max-width: 45%;
3085 font-size: 12px;
3086 color: var(--muted);
3087 overflow: hidden;
3088 text-overflow: ellipsis;
3089 white-space: nowrap;
3090}
3091
3092.pim-modal {
3093 width: 600px;
3094 max-height: calc(100vh - 48px);
3095 overflow-y: auto;
3096}
3097
3098.pim-modal-title {
3099 display: flex;
3100 align-items: center;
3101 gap: 10px;
3102 overflow-wrap: anywhere;
3103}
3104
3105.pim-note {
3106 margin: -6px 0 8px;
3107 font-size: 13px;
3108}
3109
3110.pim-section {
3111 display: block;
3112 padding: 14px 0 0;
3113 margin-top: 14px;
3114 border-top: 1px solid var(--border);
3115}
3116
3117.pim-section-title {
3118 margin: 0 0 8px;
3119 font-size: 14px;
3120}
3121
3122.pim-hint {
3123 margin: 0 0 10px;
3124 font-size: 13px;
3125}
3126
3127.pim-section-actions {
3128 display: flex;
3129 justify-content: flex-end;
3130 flex-wrap: wrap;
3131 gap: 8px;
3132}
3133
3134.pim-section-actions.pim-left {
3135 justify-content: flex-start;
3136}
3137
3138.pim-colors {
3139 display: flex;
3140 flex-wrap: wrap;
3141 align-items: center;
3142 gap: 6px;
3143}
3144
3145.pim-color {
3146 width: 26px;
3147 height: 26px;
3148 padding: 0;
3149 border: 2px solid var(--panel);
3150 outline: 1px solid var(--border);
3151 background: var(--swatch);
3152 cursor: pointer;
3153}
3154
3155.pim-color.selected {
3156 outline: 2px solid var(--text);
3157}
3158
3159.pim-color-input {
3160 width: 34px;
3161 height: 30px;
3162 padding: 0 2px;
3163 cursor: pointer;
3164}
3165
3166.pim-items {
3167 display: flex;
3168 flex-direction: column;
3169 gap: 6px;
3170 margin-bottom: 10px;
3171}
3172
3173.pim-item {
3174 display: flex;
3175 align-items: center;
3176 gap: 8px;
3177 min-width: 0;
3178}
3179
3180.pim-item-name {
3181 flex: 1;
3182 min-width: 0;
3183 font-size: 14px;
3184 overflow: hidden;
3185 text-overflow: ellipsis;
3186 white-space: nowrap;
3187}
3188
3189.pim-mode {
3190 flex: none;
3191 width: auto;
3192 max-width: 55%;
3193 padding: 6px 28px 6px 8px;
3194}
3195
3196.pim-add {
3197 display: flex;
3198 gap: 8px;
3199 align-items: center;
3200}
3201
3202.pim-add .field-input {
3203 flex: 1;
3204 min-width: 0;
3205}
3206
3207.pim-link {
3208 display: flex;
3209 align-items: center;
3210 gap: 10px;
3211 padding: 8px 10px;
3212 border: 1px solid var(--border);
3213 min-width: 0;
3214}
3215
3216.pim-link-main {
3217 flex: 1;
3218 min-width: 0;
3219 display: flex;
3220 flex-direction: column;
3221 gap: 2px;
3222}
3223
3224.pim-link-url {
3225 font-size: 13px;
3226 overflow: hidden;
3227 text-overflow: ellipsis;
3228 white-space: nowrap;
3229}
3230
3231.pim-link-meta {
3232 font-size: 12px;
3233}
3234
3235.pim-link-actions {
3236 display: flex;
3237 flex: none;
3238 gap: 6px;
3239}
3240
3241.pim-link-form {
3242 display: flex;
3243 flex-wrap: wrap;
3244 align-items: flex-end;
3245 gap: 10px;
3246}
3247
3248.pim-inline {
3249 display: flex;
3250 flex-direction: column;
3251 gap: 4px;
3252 font-size: 13px;
3253 color: var(--muted);
3254}
3255
3256.pim-inline input,
3257.pim-inline select {
3258 width: auto;
3259 min-width: 140px;
3260}
3261
3262.pim-hints,
3263.pim-sample {
3264 margin: 0 0 8px;
3265 padding-left: 18px;
3266 font-size: 13px;
3267 line-height: 1.5;
3268}
3269
3270.pim-sample li {
3271 overflow-wrap: anywhere;
3272}
3273
3274.admin-stack {
3275 display: flex;
3276 flex-direction: column;
3277 gap: 28px;
3278}
3279
3280.rooms-hint {
3281 margin-bottom: 12px;
3282}
3283
3284.room-address {
3285 font-size: 12px;
3286 margin-top: 2px;
3287 overflow-wrap: anywhere;
3288}
3289
3290.feeds-header {
3291 margin-top: 28px;
3292}
3293
3294/* Below this the month grid needs the width of the collection list. */
3295@media (max-width: 1180px) {
3296 .pim-view {
3297 flex-direction: column;
3298 }
3299
3300 .pim-side {
3301 width: auto;
3302 }
3303
3304 /* The events and contacts come first; the list of collections would
3305 fill a whole phone screen above them. */
3306 .pim-main {
3307 min-height: 240px;
3308 order: -1;
3309 }
3310}
3311
3312@media (max-width: 640px) {
3313 .pim-link {
3314 flex-direction: column;
3315 align-items: stretch;
3316 }
3317
3318 .pim-link-actions {
3319 flex-wrap: wrap;
3320 }
3321
3322 .pim-add {
3323 flex-wrap: wrap;
3324 }
3325
3326 .pim-add .pim-mode {
3327 max-width: 100%;
3328 }
3329
3330 /* A lent row keeps name, mode and remove button on one line. */
3331 .pim-item .pim-mode {
3332 flex: 0 1 auto;
3333 min-width: 0;
3334 }
3335}
3336
3337/* Mirrored chevron: the icon set has only the right-pointing one. */
3338.flip {
3339 transform: scaleX(-1);
3340}
3341
3342/* Selects in the calendar and contacts views, their dialogs, the shares
3343 page, and any form field: the same height and colors as the inputs beside
3344 them, in both themes. */
3345.pim-modal select,
3346.pim-view select,
3347.shares-view select,
3348select.field-input {
3349 appearance: none;
3350 padding: 10px 34px 10px 12px;
3351 border: 1px solid var(--border);
3352 background-color: var(--bg);
3353 background-image: var(--select-arrow);
3354 background-repeat: no-repeat;
3355 background-position: right 11px center;
3356 background-size: 12px 12px;
3357 color: var(--text);
3358 font: inherit;
3359 font-size: 14px;
3360}
3361
3362.pim-modal select:focus,
3363.pim-view select:focus,
3364.shares-view select:focus,
3365select.field-input:focus {
3366 outline: 2px solid var(--accent);
3367 outline-offset: -1px;
3368}
3369
3370.pim-link-form .pim-inline input {
3371 padding: 10px 12px;
3372}
3373
3374.pim-file-input {
3375 display: none;
3376}
3377
3378/* ---- Calendar ---------------------------------------------------- */
3379
3380.cal {
3381 display: flex;
3382 flex-direction: column;
3383 min-width: 0;
3384}
3385
3386.cal-pad {
3387 padding: 12px 14px;
3388}
3389
3390.cal-toolbar {
3391 display: flex;
3392 align-items: center;
3393 flex-wrap: wrap;
3394 gap: 8px 12px;
3395 padding: 10px 12px;
3396 border-bottom: 1px solid var(--border);
3397}
3398
3399.cal-nav {
3400 display: flex;
3401 align-items: center;
3402 gap: 4px;
3403}
3404
3405.cal-title {
3406 flex: 1;
3407 min-width: 0;
3408 margin: 0;
3409 font-size: 17px;
3410 white-space: nowrap;
3411 overflow: hidden;
3412 text-overflow: ellipsis;
3413}
3414
3415.cal-views {
3416 display: flex;
3417}
3418
3419.cal-view-btn {
3420 padding: 6px 12px;
3421 font-size: 13px;
3422 margin-left: -1px;
3423}
3424
3425.cal-view-btn.active {
3426 background: var(--accent);
3427 border-color: var(--accent-border);
3428 color: #fff;
3429 position: relative;
3430 z-index: 1;
3431}
3432
3433/* The buttons overlap by one pixel; the hovered one must draw on top, or its
3434 neighbor covers its right edge. */
3435.cal-view-btn:hover,
3436.cal-view-btn:focus-visible {
3437 position: relative;
3438 z-index: 2;
3439}
3440
3441.cal-badge {
3442 min-width: 18px;
3443 padding: 0 5px;
3444 font-size: 11px;
3445 line-height: 18px;
3446 background: var(--danger);
3447 color: #fff;
3448 text-align: center;
3449}
3450
3451.cal-truncated {
3452 margin: 0;
3453 padding: 8px 12px;
3454 font-size: 13px;
3455 color: var(--danger);
3456 border-bottom: 1px solid var(--border);
3457}
3458
3459.cal-head {
3460 display: grid;
3461 grid-template-columns: repeat(7, minmax(0, 1fr));
3462 border-bottom: 1px solid var(--border);
3463}
3464
3465.cal-head-day {
3466 padding: 6px 8px;
3467 font-size: 12px;
3468 color: var(--muted);
3469 overflow: hidden;
3470 text-overflow: ellipsis;
3471 white-space: nowrap;
3472}
3473
3474.cal-week {
3475 display: grid;
3476 grid-template-columns: repeat(7, minmax(0, 1fr));
3477 grid-template-rows: 26px repeat(var(--lines), 22px) 6px;
3478 border-bottom: 1px solid var(--border);
3479}
3480
3481.cal-cell {
3482 grid-row: 1 / -1;
3483 display: flex;
3484 flex-direction: column;
3485 align-items: flex-start;
3486 justify-content: flex-start;
3487 gap: 4px;
3488 min-width: 0;
3489 padding: 4px 6px;
3490 border: 0;
3491 border-right: 1px solid var(--border);
3492 background: none;
3493 color: var(--text);
3494 font: inherit;
3495 cursor: pointer;
3496}
3497
3498.cal-cell:nth-child(7) {
3499 border-right: 0;
3500}
3501
3502.cal-cell:hover {
3503 background: var(--bg);
3504}
3505
3506.cal-cell.cal-out .cal-num {
3507 color: var(--muted);
3508}
3509
3510.cal-cell.cal-sel {
3511 background: var(--bg);
3512 box-shadow: inset 0 0 0 2px var(--accent);
3513}
3514
3515.cal-num {
3516 font-size: 13px;
3517 line-height: 18px;
3518 min-width: 20px;
3519 text-align: center;
3520}
3521
3522.cal-today .cal-num {
3523 background: var(--accent);
3524 color: #fff;
3525 font-weight: 600;
3526}
3527
3528/* Phone only: the colors of a day's events instead of their titles. */
3529.cal-dots {
3530 display: none;
3531 gap: 3px;
3532}
3533
3534.cal-dot {
3535 display: inline-block;
3536 flex: none;
3537 width: 8px;
3538 height: 8px;
3539 background: var(--c, var(--accent));
3540}
3541
3542.cal-item {
3543 position: relative;
3544 z-index: 1;
3545 display: flex;
3546 align-items: center;
3547 justify-content: flex-start;
3548 gap: 4px;
3549 min-width: 0;
3550 margin: 1px 2px;
3551 padding: 0 6px;
3552 border: 0;
3553 font-size: 12px;
3554 line-height: 20px;
3555 text-align: left;
3556 cursor: pointer;
3557 overflow: hidden;
3558}
3559
3560.cal-bar {
3561 background: var(--c);
3562 color: #fff;
3563 text-shadow: 0 0 2px rgb(0 0 0 / 40%);
3564}
3565
3566.cal-bar.cal-cont-l {
3567 margin-left: 0;
3568}
3569
3570.cal-bar.cal-cont-r {
3571 margin-right: 0;
3572}
3573
3574.cal-chip {
3575 background: none;
3576 color: var(--text);
3577}
3578
3579.cal-chip:hover {
3580 background: var(--bg);
3581}
3582
3583.cal-time {
3584 flex: none;
3585 color: var(--muted);
3586 font-variant-numeric: tabular-nums;
3587}
3588
3589.cal-item-title {
3590 min-width: 0;
3591 overflow: hidden;
3592 text-overflow: ellipsis;
3593 white-space: nowrap;
3594}
3595
3596.cal-cancelled .cal-item-title,
3597.cal-cancelled .cal-row-title,
3598.cal-detail-title.cal-cancelled {
3599 text-decoration: line-through;
3600}
3601
3602.cal-more {
3603 position: relative;
3604 z-index: 1;
3605 justify-content: flex-start;
3606 margin: 1px 2px;
3607 padding: 0 6px;
3608 border: 0;
3609 background: none;
3610 color: var(--muted);
3611 font-size: 12px;
3612 line-height: 20px;
3613 white-space: nowrap;
3614 overflow: hidden;
3615 min-width: 0;
3616}
3617
3618.cal-more:hover {
3619 color: var(--text);
3620 text-decoration: underline;
3621}
3622
3623.cal-day {
3624 border-top: 1px solid var(--border);
3625}
3626
3627.cal-day-title {
3628 margin: 0;
3629 padding: 12px 14px 6px;
3630 font-size: 14px;
3631}
3632
3633.cal-day-today {
3634 color: var(--accent);
3635}
3636
3637.cal-rows {
3638 display: flex;
3639 flex-direction: column;
3640 padding-bottom: 8px;
3641}
3642
3643.cal-row {
3644 display: flex;
3645 align-items: baseline;
3646 justify-content: flex-start;
3647 gap: 10px;
3648 padding: 7px 14px;
3649 border: 0;
3650 background: none;
3651 color: var(--text);
3652 font: inherit;
3653 text-align: left;
3654 cursor: pointer;
3655 min-width: 0;
3656}
3657
3658.cal-row:hover {
3659 background: var(--bg);
3660}
3661
3662.cal-row .cal-dot {
3663 align-self: center;
3664}
3665
3666.cal-row-time {
3667 flex: none;
3668 width: 120px;
3669 font-size: 13px;
3670 color: var(--muted);
3671 font-variant-numeric: tabular-nums;
3672}
3673
3674.cal-row-main {
3675 display: flex;
3676 flex-direction: column;
3677 gap: 2px;
3678 min-width: 0;
3679}
3680
3681.cal-row-title {
3682 font-size: 14px;
3683 overflow-wrap: anywhere;
3684}
3685
3686.cal-row-meta {
3687 font-size: 12.5px;
3688 color: var(--muted);
3689 overflow-wrap: anywhere;
3690}
3691
3692.cal-agenda-day + .cal-agenda-day {
3693 border-top: 1px solid var(--border);
3694}
3695
3696.cal-inv {
3697 display: flex;
3698 align-items: center;
3699 gap: 12px;
3700 padding: 12px 14px;
3701 border-bottom: 1px solid var(--border);
3702}
3703
3704.cal-inv-main {
3705 flex: 1;
3706 min-width: 0;
3707 display: flex;
3708 flex-direction: column;
3709 align-items: flex-start;
3710 gap: 3px;
3711 padding: 0;
3712 border: 0;
3713 background: none;
3714 color: var(--text);
3715 font: inherit;
3716 text-align: left;
3717 cursor: pointer;
3718}
3719
3720.cal-inv-title {
3721 font-weight: 600;
3722 overflow-wrap: anywhere;
3723}
3724
3725.cal-inv-actions {
3726 display: flex;
3727 flex-wrap: wrap;
3728 gap: 6px;
3729}
3730
3731.cal-detail-title {
3732 overflow-wrap: anywhere;
3733}
3734
3735.cal-flag {
3736 display: inline-block;
3737 margin: 0 0 12px;
3738 padding: 2px 8px;
3739 font-size: 12px;
3740 border: 1px solid var(--danger);
3741 color: var(--danger);
3742}
3743
3744.cal-facts {
3745 display: grid;
3746 grid-template-columns: max-content minmax(0, 1fr);
3747 gap: 8px 16px;
3748 margin: 0 0 16px;
3749 font-size: 14px;
3750}
3751
3752.cal-facts dt {
3753 color: var(--muted);
3754}
3755
3756.cal-facts dd {
3757 margin: 0;
3758 overflow-wrap: anywhere;
3759}
3760
3761.cal-pre {
3762 white-space: pre-wrap;
3763}
3764
3765.cal-desc {
3766 margin-bottom: 16px;
3767 padding: 10px 12px;
3768 border: 1px solid var(--border);
3769 background: var(--bg);
3770 font-size: 14px;
3771 overflow-wrap: anywhere;
3772 max-height: 40vh;
3773 overflow-y: auto;
3774}
3775
3776.cal-attendees {
3777 list-style: none;
3778 margin: 0 0 16px;
3779 padding: 0;
3780 display: flex;
3781 flex-direction: column;
3782 gap: 6px;
3783 font-size: 14px;
3784}
3785
3786.cal-attendees li {
3787 display: flex;
3788 align-items: center;
3789 gap: 8px;
3790 min-width: 0;
3791}
3792
3793.cal-att-name {
3794 min-width: 0;
3795 overflow: hidden;
3796 text-overflow: ellipsis;
3797 white-space: nowrap;
3798}
3799
3800.ps-badge {
3801 margin-left: auto;
3802 flex: none;
3803 padding: 1px 7px;
3804 font-size: 11.5px;
3805 border: 1px solid var(--border);
3806 color: var(--muted);
3807}
3808
3809.ps-accepted {
3810 border-color: #16a34a;
3811 color: #16a34a;
3812}
3813
3814.ps-declined {
3815 border-color: var(--danger);
3816 color: var(--danger);
3817}
3818
3819.ps-tentative {
3820 border-color: #d97706;
3821 color: #d97706;
3822}
3823
3824.cal-reply {
3825 border-top: 1px solid var(--border);
3826 padding-top: 12px;
3827}
3828
3829.cal-scope {
3830 display: flex;
3831 flex-wrap: wrap;
3832 gap: 4px 16px;
3833}
3834
3835.cal-scope input[type="radio"] {
3836 width: auto;
3837 margin: 0;
3838 padding: 0;
3839 accent-color: var(--accent);
3840}
3841
3842/* ---- Contacts ---------------------------------------------------- */
3843
3844.ct {
3845 display: flex;
3846 min-height: 0;
3847 flex: 1;
3848}
3849
3850.ct-list {
3851 flex: none;
3852 width: 320px;
3853 display: flex;
3854 flex-direction: column;
3855 border-right: 1px solid var(--border);
3856 min-height: 0;
3857 /* Rows do not wrap; on a phone the list must not grow to their width. */
3858 min-width: 0;
3859}
3860
3861.ct-search {
3862 position: relative;
3863 padding: 10px;
3864 border-bottom: 1px solid var(--border);
3865}
3866
3867.ct-search input {
3868 padding-left: 34px;
3869}
3870
3871.ct-search-icon {
3872 position: absolute;
3873 left: 20px;
3874 top: 50%;
3875 width: 16px;
3876 height: 16px;
3877 transform: translateY(-50%);
3878 color: var(--muted);
3879 pointer-events: none;
3880}
3881
3882.ct-rows {
3883 display: flex;
3884 flex-direction: column;
3885 overflow-y: auto;
3886 max-height: calc(100vh - var(--topbar-h) - 120px);
3887}
3888
3889.ct-row {
3890 display: flex;
3891 align-items: center;
3892 justify-content: flex-start;
3893 gap: 10px;
3894 padding: 8px 12px;
3895 border: 0;
3896 border-bottom: 1px solid var(--border);
3897 background: none;
3898 color: var(--text);
3899 font: inherit;
3900 text-align: left;
3901 cursor: pointer;
3902 min-width: 0;
3903}
3904
3905.ct-row:hover {
3906 background: var(--bg);
3907}
3908
3909.ct-row.ct-sel {
3910 background: var(--bg);
3911 box-shadow: inset 3px 0 0 var(--accent);
3912}
3913
3914/* The contact's address book, colored like its entry in the list beside. */
3915.ct-book {
3916 flex: none;
3917 width: 10px;
3918 height: 10px;
3919 margin-left: auto;
3920 background: var(--swatch);
3921}
3922
3923.ct-avatar {
3924 flex: none;
3925 width: 36px;
3926 height: 36px;
3927 object-fit: cover;
3928 background: var(--bg);
3929}
3930
3931.ct-initials {
3932 position: relative;
3933 overflow: hidden;
3934 display: inline-flex;
3935 align-items: center;
3936 justify-content: center;
3937 border: 1px solid var(--border);
3938 font-size: 13px;
3939 font-weight: 600;
3940 color: var(--muted);
3941}
3942
3943.ct-photo {
3944 position: absolute;
3945 inset: 0;
3946 width: 100%;
3947 height: 100%;
3948 object-fit: cover;
3949 background: var(--bg);
3950}
3951
3952.ct-avatar-lg {
3953 width: 72px;
3954 height: 72px;
3955 font-size: 22px;
3956}
3957
3958.ct-row-main {
3959 display: flex;
3960 flex-direction: column;
3961 gap: 2px;
3962 min-width: 0;
3963}
3964
3965.ct-name {
3966 display: flex;
3967 align-items: center;
3968 gap: 6px;
3969 font-size: 14px;
3970 min-width: 0;
3971}
3972
3973.ct-name-text {
3974 min-width: 0;
3975 overflow: hidden;
3976 text-overflow: ellipsis;
3977 white-space: nowrap;
3978}
3979
3980.ct-meta {
3981 font-size: 12.5px;
3982 color: var(--muted);
3983 overflow: hidden;
3984 text-overflow: ellipsis;
3985 white-space: nowrap;
3986}
3987
3988.ct-detail {
3989 flex: 1;
3990 min-width: 0;
3991 padding: 16px 20px;
3992 overflow-y: auto;
3993}
3994
3995.ct-back {
3996 display: none;
3997 margin-bottom: 12px;
3998}
3999
4000.ct-head {
4001 display: flex;
4002 align-items: center;
4003 gap: 16px;
4004 margin-bottom: 18px;
4005 min-width: 0;
4006}
4007
4008.ct-head-text {
4009 min-width: 0;
4010}
4011
4012.ct-head-text p {
4013 margin: 4px 0 0;
4014 overflow-wrap: anywhere;
4015}
4016
4017.ct-title {
4018 margin: 0;
4019 font-size: 20px;
4020 overflow-wrap: anywhere;
4021}
4022
4023.ct-value {
4024 display: flex;
4025 align-items: baseline;
4026 flex-wrap: wrap;
4027 gap: 0 8px;
4028}
4029
4030.ct-value + .ct-value {
4031 margin-top: 4px;
4032}
4033
4034.ct-label {
4035 font-size: 12px;
4036}
4037
4038@media (max-width: 860px) {
4039 .ct-list {
4040 width: 260px;
4041 }
4042
4043 .cal-row-time {
4044 width: 96px;
4045 }
4046}
4047
4048@media (max-width: 640px) {
4049 .content:has(.pim-view) {
4050 padding: 12px;
4051 }
4052
4053 .cal-toolbar {
4054 padding: 8px;
4055 }
4056
4057 .cal-title {
4058 order: -1;
4059 flex-basis: 100%;
4060 }
4061
4062 .cal-views {
4063 margin-left: auto;
4064 }
4065
4066 .cal-view-btn {
4067 padding: 6px 9px;
4068 }
4069
4070 /* Phones get compact day cells with dots; the day list below the grid
4071 shows the chosen day's events. */
4072 .cal-week {
4073 grid-template-rows: 46px;
4074 }
4075
4076 .cal-item,
4077 .cal-more {
4078 display: none;
4079 }
4080
4081 .cal-dots {
4082 display: flex;
4083 }
4084
4085 .cal-cell {
4086 align-items: center;
4087 padding: 4px 2px;
4088 }
4089
4090 .cal-head-day {
4091 padding: 6px 2px;
4092 text-align: center;
4093 }
4094
4095 .cal-inv {
4096 flex-direction: column;
4097 align-items: stretch;
4098 }
4099
4100 .cal-row-time {
4101 width: 80px;
4102 }
4103
4104 .cal-facts {
4105 grid-template-columns: minmax(0, 1fr);
4106 gap: 2px;
4107 }
4108
4109 .cal-facts dd {
4110 margin-bottom: 8px;
4111 }
4112
4113 /* List and details take turns. */
4114 .ct-list {
4115 width: auto;
4116 flex: 1;
4117 border-right: 0;
4118 }
4119
4120 .ct-rows {
4121 max-height: none;
4122 }
4123
4124 .ct-detail,
4125 .ct-has-open .ct-list {
4126 display: none;
4127 }
4128
4129 .ct-has-open .ct-detail {
4130 display: block;
4131 padding: 12px;
4132 }
4133
4134 .ct-back {
4135 display: inline-flex;
4136 }
4137}
4138