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