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/* Sections of the share management view: the viewer's own links and, for an
1659 admin, one section per account. Native <details>/<summary>. */
1660
1661.share-groups {
1662 display: flex;
1663 flex-direction: column;
1664 gap: 12px;
1665}
1666
1667.share-group {
1668 border: 1px solid var(--border);
1669 background: var(--panel);
1670}
1671
1672.share-group-head {
1673 display: flex;
1674 align-items: center;
1675 gap: 8px;
1676 padding: 10px 12px;
1677 font-size: 14px;
1678 font-weight: 600;
1679 cursor: pointer;
1680 /* The caret below stands in for the disclosure triangle. */
1681 list-style: none;
1682}
1683
1684.share-group-head::-webkit-details-marker {
1685 display: none;
1686}
1687
1688.share-group-head:hover {
1689 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
1690}
1691
1692.share-group-head:focus-visible {
1693 outline: 2px solid var(--accent);
1694 outline-offset: -2px;
1695}
1696
1697.share-caret {
1698 width: 16px;
1699 height: 16px;
1700 flex: none;
1701 transition: rotate 0.15s ease;
1702}
1703
1704.share-group[open] .share-caret {
1705 rotate: 90deg;
1706}
1707
1708.share-group-count {
1709 font-size: 12px;
1710 font-weight: 400;
1711}
1712
1713/* A live link whose account cannot sign in any more. */
1714.share-group-warn {
1715 font-size: 12px;
1716 font-weight: 400;
1717 color: var(--danger);
1718}
1719
1720.share-group-del {
1721 margin-left: auto;
1722}
1723
1724/* Rows inside a section read as one block: the border moves from each row to
1725 the section. */
1726.share-group .share-list {
1727 gap: 0;
1728 border-top: 1px solid var(--border);
1729}
1730
1731.share-group .share-item {
1732 border: 0;
1733 border-top: 1px solid var(--border);
1734}
1735
1736.share-group .share-item:first-child {
1737 border-top: 0;
1738}
1739
1740/* ------------------------------------------------------------------ */
1741/* Admin (milestone 7) */
1742/* ------------------------------------------------------------------ */
1743
1744.admin-view {
1745 display: flex;
1746 flex-direction: column;
1747 gap: 16px;
1748 width: 100%;
1749 max-width: 960px;
1750 margin: 0 auto;
1751}
1752
1753.admin-section-title {
1754 margin: 0 0 14px;
1755 font-size: 15px;
1756}
1757
1758.users-header {
1759 display: flex;
1760 align-items: center;
1761 justify-content: space-between;
1762 margin-bottom: 4px;
1763}
1764
1765.users-header .admin-section-title {
1766 margin-bottom: 0;
1767}
1768
1769.users-header .btn-primary {
1770 width: auto;
1771}
1772
1773.user-list {
1774 display: flex;
1775 flex-direction: column;
1776 gap: 10px;
1777}
1778
1779.user-row {
1780 display: flex;
1781 align-items: center;
1782 justify-content: space-between;
1783 gap: 12px;
1784 padding: 12px 14px;
1785 border: 1px solid var(--border);
1786}
1787
1788.user-row-main {
1789 min-width: 0;
1790}
1791
1792.user-row-name {
1793 display: flex;
1794 align-items: center;
1795 gap: 6px;
1796 font-weight: 600;
1797 font-size: 14px;
1798 flex-wrap: wrap;
1799}
1800
1801.ic-admin {
1802 width: 16px;
1803 height: 16px;
1804 flex: none;
1805}
1806
1807.user-row-actions {
1808 display: flex;
1809 gap: 6px;
1810 flex-shrink: 0;
1811}
1812
1813.badge-off {
1814 background: color-mix(in srgb, var(--danger) 15%, var(--panel));
1815 border-color: var(--danger);
1816 color: var(--danger);
1817}
1818
1819.setting-row {
1820 display: flex;
1821 align-items: center;
1822 gap: 10px;
1823 padding: 14px;
1824 border: 1px solid var(--border);
1825 cursor: pointer;
1826}
1827
1828.setting-row input {
1829 margin-left: auto;
1830}
1831
1832.setting-label {
1833 display: block;
1834 font-weight: 600;
1835 font-size: 14px;
1836}
1837
1838.setting-desc {
1839 display: block;
1840 font-size: 12px;
1841 color: var(--muted);
1842 margin-top: 2px;
1843}
1844
1845/* Setting row whose control is a select, pushed to the right. */
1846.setting-row-select {
1847 cursor: default;
1848}
1849
1850.setting-row-select > span {
1851 flex: 1;
1852}
1853
1854.setting-row-select select {
1855 appearance: none;
1856 margin-left: auto;
1857 padding: 10px 42px 10px 12px;
1858 border: 1px solid var(--border);
1859 background-color: var(--panel);
1860 background-image: var(--select-arrow);
1861 background-repeat: no-repeat;
1862 background-position: right 12px center;
1863 background-size: 14px 14px;
1864 color: var(--text);
1865 font: inherit;
1866 max-width: 190px;
1867}
1868
1869/* User create/edit form */
1870
1871.user-form {
1872 width: 520px;
1873}
1874
1875.field-input {
1876 width: 100%;
1877 box-sizing: border-box;
1878}
1879
1880/* Search exclusions: a bordered block matching the setting row above it. */
1881.excludes-field {
1882 display: flex;
1883 flex-direction: column;
1884 gap: 10px;
1885 padding: 14px;
1886 border: 1px solid var(--border);
1887}
1888
1889.excludes-list {
1890 display: flex;
1891 flex-direction: column;
1892 gap: 2px;
1893}
1894
1895.exclude-row {
1896 display: flex;
1897 align-items: center;
1898 gap: 8px;
1899 padding: 4px 0;
1900}
1901
1902.exclude-path {
1903 flex: 1;
1904 font-size: 13px;
1905 overflow: hidden;
1906 text-overflow: ellipsis;
1907 white-space: nowrap;
1908}
1909
1910.exclude-remove {
1911 flex: none;
1912 padding: 4px 6px;
1913}
1914
1915.exclude-add {
1916 align-self: flex-start;
1917}
1918
1919.excludes-empty {
1920 font-size: 13px;
1921 margin: 0;
1922}
1923
1924.field-hint {
1925 font-size: 12px;
1926 margin: -2px 0 8px;
1927}
1928
1929.check-row {
1930 display: flex;
1931 align-items: center;
1932 gap: 8px;
1933 font-size: 14px;
1934 margin-bottom: 12px;
1935 cursor: pointer;
1936}
1937
1938.form-error {
1939 color: var(--danger);
1940 font-size: 13px;
1941 margin: 4px 0 8px;
1942}
1943
1944.roots-editor {
1945 display: flex;
1946 flex-direction: column;
1947 gap: 6px;
1948}
1949
1950.root-draft {
1951 display: flex;
1952 align-items: center;
1953 gap: 8px;
1954}
1955
1956.root-draft-path {
1957 flex: 1;
1958 font-size: 13px;
1959 overflow: hidden;
1960 text-overflow: ellipsis;
1961 white-space: nowrap;
1962}
1963
1964.root-draft-mode {
1965 width: auto;
1966 padding: 6px 8px;
1967}
1968
1969.root-add {
1970 align-self: flex-start;
1971 margin-top: 4px;
1972}
1973
1974/* ------------------------------------------------------------------ */
1975/* Empty state + keyboard focus (milestone 8) */
1976/* ------------------------------------------------------------------ */
1977
1978.empty-state {
1979 display: flex;
1980 flex-direction: column;
1981 align-items: center;
1982 justify-content: center;
1983 gap: 8px;
1984 padding: 64px 20px;
1985 text-align: center;
1986}
1987
1988.empty-glyph {
1989 width: 52px;
1990 height: 52px;
1991}
1992
1993.empty-state h3 {
1994 margin: 0;
1995 font-size: 16px;
1996}
1997
1998/* Visible keyboard focus on selectable file/folder entries. The ring is
1999 inset (negative offset) so the scroll container never clips it. */
2000.tile:focus-visible,
2001.row:focus-visible {
2002 outline: 2px solid var(--accent);
2003 outline-offset: -2px;
2004}
2005
2006/* ------------------------------------------------------------------ */
2007/* Search view (#/search) */
2008/* ------------------------------------------------------------------ */
2009
2010.search-view {
2011 display: flex;
2012 flex-direction: column;
2013 gap: 14px;
2014}
2015
2016/* Query bar ------------------------------------------------------ */
2017
2018.query-bar {
2019 display: flex;
2020 align-items: stretch;
2021 gap: 8px;
2022}
2023
2024.query-input {
2025 flex: 1;
2026 display: flex;
2027 align-items: center;
2028 gap: 8px;
2029 background: var(--panel);
2030 border: 1px solid var(--border);
2031 padding: 0 10px;
2032 min-width: 0;
2033}
2034
2035.query-input .qi-icon {
2036 width: 18px;
2037 height: 18px;
2038 color: var(--muted);
2039 flex: none;
2040}
2041
2042.query-input input {
2043 flex: 1;
2044 min-width: 0;
2045 border: none;
2046 outline: none;
2047 background: transparent;
2048 color: var(--text);
2049 font: inherit;
2050 font-size: 14px;
2051 height: 38px;
2052 padding: 0;
2053}
2054
2055.query-input input::placeholder {
2056 color: var(--muted);
2057}
2058
2059/* Segmented Name / Content / Both control */
2060
2061.seg {
2062 display: flex;
2063 border: 1px solid var(--border);
2064 background: var(--panel);
2065}
2066
2067.seg button {
2068 appearance: none;
2069 border: none;
2070 border-right: 1px solid var(--border);
2071 background: transparent;
2072 color: var(--muted);
2073 font: inherit;
2074 font-size: 13px;
2075 padding: 0 14px;
2076 cursor: pointer;
2077}
2078
2079.seg button:last-child {
2080 border-right: none;
2081}
2082
2083.seg button:hover {
2084 background: var(--bg);
2085}
2086
2087.seg button.on {
2088 background: var(--accent);
2089 color: #fff;
2090}
2091
2092/* Start folder --------------------------------------------------- */
2093
2094/* The folder a search starts in; opens the folder picker. */
2095.folder-pick {
2096 flex: none;
2097 display: flex;
2098 align-items: center;
2099 gap: 8px;
2100 max-width: 260px;
2101 border: 1px solid var(--border);
2102 background: var(--panel);
2103 color: var(--text);
2104 font: inherit;
2105 font-size: 13px;
2106 padding: 0 10px;
2107 cursor: pointer;
2108}
2109
2110.folder-pick:hover {
2111 border-color: var(--muted);
2112}
2113
2114.folder-pick .fp-icon {
2115 width: 16px;
2116 height: 16px;
2117 color: var(--muted);
2118 flex: none;
2119}
2120
2121.folder-pick .fp-path {
2122 overflow: hidden;
2123 text-overflow: ellipsis;
2124 white-space: nowrap;
2125}
2126
2127/* Run / stop ------------------------------------------------------- */
2128
2129.btn-run {
2130 border: 1px solid var(--accent-border);
2131 background: var(--accent);
2132 color: #fff;
2133 font: inherit;
2134 font-size: 13px;
2135 font-weight: 600;
2136 padding: 0 18px;
2137 cursor: pointer;
2138}
2139
2140.btn-run:hover {
2141 background: var(--accent-hover);
2142}
2143
2144.btn-stop {
2145 display: flex;
2146 align-items: center;
2147 gap: 6px;
2148 border: 1px solid var(--danger-border);
2149 background: var(--danger);
2150 color: #fff;
2151 font: inherit;
2152 font-size: 13px;
2153 font-weight: 600;
2154 padding: 0 14px;
2155 cursor: pointer;
2156}
2157
2158.btn-stop:hover {
2159 opacity: 0.9;
2160}
2161
2162.btn-stop .ic-stop {
2163 width: 14px;
2164 height: 14px;
2165}
2166
2167/* Status line ------------------------------------------------------ */
2168
2169.status-line {
2170 display: flex;
2171 align-items: center;
2172 gap: 8px;
2173 font-size: 13px;
2174 color: var(--muted);
2175}
2176
2177.status-line .dot {
2178 width: 8px;
2179 height: 8px;
2180 background: var(--accent);
2181 animation: search-pulse 1s ease-in-out infinite;
2182}
2183
2184@keyframes search-pulse {
2185 0%,
2186 100% {
2187 opacity: 1;
2188 }
2189 50% {
2190 opacity: 0.3;
2191 }
2192}
2193
2194.status-line .skip {
2195 opacity: 0.75;
2196}
2197
2198/* Group labels ----------------------------------------------------- */
2199
2200.group-label {
2201 display: flex;
2202 align-items: center;
2203 gap: 8px;
2204 font-size: 12px;
2205 font-weight: 600;
2206 letter-spacing: 0.04em;
2207 text-transform: uppercase;
2208 color: var(--muted);
2209}
2210
2211/* Result group: a `display:flex` (or `none`, from the inline style) flex
2212 item of `.search-view` that re-establishes the column layout for its
2213 label/list/more-row children. */
2214.result-group {
2215 flex-direction: column;
2216 gap: 14px;
2217}
2218
2219.group-label .gl-icon {
2220 width: 15px;
2221 height: 15px;
2222}
2223
2224.group-label .n {
2225 font-weight: 400;
2226 text-transform: none;
2227 letter-spacing: 0;
2228}
2229
2230.group-label .grow {
2231 flex: 1;
2232}
2233
2234.collapse-all {
2235 appearance: none;
2236 border: 1px solid var(--border);
2237 background: transparent;
2238 color: var(--muted);
2239 font: inherit;
2240 font-size: 12px;
2241 text-transform: none;
2242 letter-spacing: 0;
2243 padding: 3px 10px;
2244 cursor: pointer;
2245}
2246
2247.collapse-all:hover {
2248 background: var(--bg);
2249 color: var(--text);
2250}
2251
2252/* Name-result rows --------------------------------------------------- */
2253
2254.srow {
2255 display: grid;
2256 /* icon | name+dir | size | goto */
2257 grid-template-columns: 28px minmax(0, 1fr) 90px 30px;
2258 gap: 12px;
2259 align-items: center;
2260 padding: 9px 14px;
2261 border-bottom: 1px solid var(--border);
2262 cursor: pointer;
2263 font-size: 14px;
2264 user-select: none;
2265}
2266
2267.srow:last-child {
2268 border-bottom: none;
2269}
2270
2271.srow:hover {
2272 background: var(--bg);
2273}
2274
2275.srow-icon {
2276 width: 20px;
2277 height: 20px;
2278}
2279
2280.srow-main {
2281 min-width: 0;
2282 display: flex;
2283 flex-direction: column;
2284 line-height: 1.3;
2285}
2286
2287.srow-name {
2288 overflow: hidden;
2289 text-overflow: ellipsis;
2290 white-space: nowrap;
2291}
2292
2293/* Highlighted query hits (plain fragments render as bare spans). */
2294mark.hl-hit {
2295 background: color-mix(in srgb, var(--accent) 25%, transparent);
2296 color: inherit;
2297}
2298
2299/* The directory-path line (the name line lives inside .srow-name). */
2300.srow-dir {
2301 font-size: 12px;
2302 color: var(--muted);
2303 overflow: hidden;
2304 text-overflow: ellipsis;
2305 white-space: nowrap;
2306}
2307
2308.srow-dir mark.hl-hit {
2309 color: inherit;
2310}
2311
2312.ssize {
2313 font-size: 13px;
2314 color: var(--muted);
2315 text-align: right;
2316}
2317
2318/* Paging footer: position in the results on the left, the action on the
2319 right. Separated from the list by its own border so the button never reads
2320 as another result row. */
2321.page-foot {
2322 display: flex;
2323 flex-wrap: wrap;
2324 align-items: center;
2325 justify-content: space-between;
2326 gap: 12px;
2327 padding: 10px 14px;
2328 border-top: 1px solid var(--border);
2329 font-size: 12px;
2330 color: var(--muted);
2331}
2332
2333.page-foot .pf-count {
2334 font-variant-numeric: tabular-nums;
2335}
2336
2337.page-foot .pf-more,
2338.pf-btn {
2339 appearance: none;
2340 padding: 5px 12px;
2341 border: 1px solid var(--border);
2342 background: var(--panel);
2343 font: inherit;
2344 font-weight: 500;
2345 color: var(--text);
2346 cursor: pointer;
2347}
2348
2349.page-foot .pf-more:hover,
2350.pf-btn:hover:not(:disabled) {
2351 border-color: var(--accent);
2352 color: var(--accent);
2353}
2354
2355.pf-pages {
2356 display: flex;
2357 align-items: center;
2358 gap: 4px;
2359 margin-right: auto;
2360}
2361
2362.pf-btn {
2363 min-width: 30px;
2364 padding: 5px 8px;
2365 font-variant-numeric: tabular-nums;
2366}
2367
2368.pf-btn.current {
2369 border-color: var(--accent);
2370 background: var(--accent);
2371 color: #fff;
2372}
2373
2374.pf-btn:disabled {
2375 opacity: 0.4;
2376 cursor: default;
2377}
2378
2379.pf-goto {
2380 display: flex;
2381 align-items: center;
2382 gap: 6px;
2383 margin: 0;
2384}
2385
2386.pf-goto input {
2387 width: 72px;
2388 padding: 5px 8px;
2389}
2390
2391/* An arrow-key page turn is loading. Fixed at the top, so it also shows over
2392 the preview. The delay keeps fast turns from flashing. */
2393.page-busy {
2394 position: fixed;
2395 top: 0;
2396 left: 0;
2397 right: 0;
2398 height: 3px;
2399 overflow: hidden;
2400 z-index: 250;
2401 pointer-events: none;
2402 animation: page-busy-in 0s 200ms both;
2403}
2404
2405.page-busy::after {
2406 content: "";
2407 display: block;
2408 width: 30%;
2409 height: 100%;
2410 background: var(--accent);
2411 animation: upload-indeterminate 1.2s ease-in-out infinite;
2412}
2413
2414body:has(.page-busy) {
2415 cursor: progress;
2416}
2417
2418@keyframes page-busy-in {
2419 from {
2420 opacity: 0;
2421 }
2422 to {
2423 opacity: 1;
2424 }
2425}
2426
2427/* Content matches ----------------------------------------------------- */
2428
2429.match-card {
2430 background: var(--panel);
2431 border: 1px solid var(--border);
2432 border-top: none;
2433}
2434
2435/* The first card is the one right after the group label; `:first-of-type`
2436 cannot be used because the label is an earlier `div` sibling. */
2437.group-label + .match-card {
2438 border-top: 1px solid var(--border);
2439}
2440
2441/* The header opens the file; the matched lines below are text to read. */
2442.match-head {
2443 display: flex;
2444 align-items: center;
2445 gap: 10px;
2446 padding: 9px 14px;
2447 font-size: 14px;
2448 cursor: pointer;
2449}
2450
2451.match-head:hover {
2452 background: var(--bg);
2453}
2454
2455.match-head .mi {
2456 width: 18px;
2457 height: 18px;
2458 color: var(--muted);
2459 flex: none;
2460}
2461
2462/* Takes the slack, so the count and the two buttons sit at the right edge. */
2463.mpath {
2464 flex: 1;
2465 min-width: 0;
2466 overflow: hidden;
2467 text-overflow: ellipsis;
2468 white-space: nowrap;
2469}
2470
2471.mpath b {
2472 font-weight: 600;
2473}
2474
2475.mcount {
2476 flex: none;
2477 font-size: 12px;
2478 color: var(--muted);
2479}
2480
2481/* "Show in folder", in both the name rows and the match-card headers. */
2482.goto-btn {
2483 appearance: none;
2484 flex: none;
2485 justify-self: end;
2486 display: flex;
2487 align-items: center;
2488 justify-content: center;
2489 width: 26px;
2490 height: 26px;
2491 padding: 0;
2492 border: 1px solid transparent;
2493 background: transparent;
2494 cursor: pointer;
2495}
2496
2497.goto-btn:hover {
2498 border-color: var(--border);
2499}
2500
2501.goto-btn .goto-ic {
2502 width: 16px;
2503 height: 16px;
2504 color: var(--muted);
2505}
2506
2507.goto-btn:hover .goto-ic {
2508 color: var(--accent);
2509}
2510
2511.chev {
2512 appearance: none;
2513 border: 1px solid var(--border);
2514 background: transparent;
2515 width: 26px;
2516 height: 26px;
2517 display: flex;
2518 align-items: center;
2519 justify-content: center;
2520 cursor: pointer;
2521 flex: none;
2522}
2523
2524.chev:hover {
2525 background: var(--bg);
2526}
2527
2528.chev .chev-down,
2529.chev .chev-up {
2530 display: block;
2531 width: 10px;
2532 height: 10px;
2533 border-right: 2px solid var(--muted);
2534 border-bottom: 2px solid var(--muted);
2535}
2536
2537.chev .chev-down {
2538 transform: rotate(45deg) translate(-1px, -1px);
2539}
2540
2541.chev .chev-up {
2542 transform: rotate(-135deg) translate(-1px, -1px);
2543}
2544
2545.match-lines {
2546 border-top: 1px solid var(--border);
2547 background: var(--bg);
2548 font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
2549 font-size: 12.5px;
2550}
2551
2552/* Matched lines are text to read, not a control: the card header is what
2553 opens the file, so no pointer cursor and no hover here. */
2554.mline {
2555 display: grid;
2556 grid-template-columns: 52px minmax(0, 1fr);
2557 gap: 12px;
2558 padding: 5px 14px;
2559 border-bottom: 1px solid var(--border);
2560 white-space: pre;
2561 user-select: text;
2562}
2563
2564.mline:last-child {
2565 border-bottom: none;
2566}
2567
2568.mline .ln {
2569 color: var(--muted);
2570 text-align: right;
2571 user-select: none;
2572}
2573
2574.mline .tx {
2575 overflow: hidden;
2576 text-overflow: ellipsis;
2577}
2578
2579.search-empty {
2580 padding: 48px 20px;
2581 text-align: center;
2582 color: var(--muted);
2583 font-size: 14px;
2584}
2585
2586/* ------------------------------------------------------------------ */
2587/* Uploads: overlay panel + log view */
2588/* ------------------------------------------------------------------ */
2589
2590.upload-panel {
2591 position: fixed;
2592 right: 20px;
2593 bottom: 20px;
2594 z-index: 190;
2595 width: min(380px, calc(100vw - 40px));
2596 background: var(--panel);
2597 color: var(--text);
2598 border: 1px solid var(--border);
2599 box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
2600 font-size: 13px;
2601}
2602
2603/* The toast sits above the panel when both show. */
2604.upload-panel ~ .toast,
2605.toast:has(~ .upload-panel) {
2606 bottom: 80px;
2607}
2608
2609.upload-panel-head {
2610 display: flex;
2611 align-items: center;
2612 gap: 8px;
2613 padding: 8px 10px;
2614 border-bottom: 1px solid var(--border);
2615}
2616
2617.upload-panel.collapsed .upload-panel-head {
2618 border-bottom: none;
2619}
2620
2621.upload-panel-title {
2622 font-weight: 600;
2623}
2624
2625.upload-panel-summary {
2626 margin-left: auto;
2627 font-size: 12px;
2628}
2629
2630.upload-panel-list {
2631 max-height: 50vh;
2632 overflow-y: auto;
2633}
2634
2635.upload-job {
2636 padding: 8px 10px;
2637 border-bottom: 1px solid var(--border);
2638}
2639
2640.upload-job:last-child {
2641 border-bottom: none;
2642}
2643
2644.upload-job-head {
2645 display: flex;
2646 align-items: center;
2647 gap: 4px;
2648}
2649
2650.upload-job-label {
2651 flex: 1;
2652 min-width: 0;
2653 overflow: hidden;
2654 text-overflow: ellipsis;
2655 white-space: nowrap;
2656 font-weight: 500;
2657}
2658
2659.upload-job-pct {
2660 font-variant-numeric: tabular-nums;
2661 margin-right: 4px;
2662}
2663
2664.upload-bar {
2665 height: 4px;
2666 margin: 6px 0 4px;
2667 /* Clip the indeterminate block: it slides past both ends. */
2668 overflow: hidden;
2669 background: color-mix(in srgb, var(--accent) 15%, var(--panel));
2670}
2671
2672.upload-bar-fill {
2673 height: 100%;
2674 background: var(--accent);
2675 transition: width 0.2s linear;
2676}
2677
2678/* Preparing: nothing to measure yet, so a sliding block instead of a fill. */
2679.upload-bar.indeterminate .upload-bar-fill {
2680 width: 30% !important;
2681 animation: upload-indeterminate 1.2s ease-in-out infinite;
2682}
2683
2684@keyframes upload-indeterminate {
2685 0% {
2686 transform: translateX(-100%);
2687 }
2688 100% {
2689 transform: translateX(333%);
2690 }
2691}
2692
2693.upload-job.failed .upload-bar-fill {
2694 background: var(--danger);
2695}
2696
2697.upload-job.done .upload-bar-fill {
2698 background: var(--muted);
2699}
2700
2701.upload-job-state {
2702 font-size: 12px;
2703 overflow: hidden;
2704 text-overflow: ellipsis;
2705 white-space: nowrap;
2706}
2707
2708.upload-job.failed .upload-job-state {
2709 color: var(--danger);
2710 white-space: normal;
2711}
2712
2713.upload-conflict {
2714 display: flex;
2715 flex-direction: column;
2716 gap: 6px;
2717 margin-top: 8px;
2718 padding: 8px;
2719 border: 1px solid var(--danger-border);
2720 background: color-mix(in srgb, var(--danger) 8%, var(--panel));
2721}
2722
2723.upload-conflict-all {
2724 display: flex;
2725 /* The label wraps on a narrow dialog; keep the box on the first line. */
2726 align-items: flex-start;
2727 gap: 0.5rem;
2728 font-size: 12px;
2729 cursor: pointer;
2730}
2731
2732.upload-conflict-all input {
2733 flex: none;
2734 margin-top: 2px;
2735}
2736
2737.modal .upload-conflict-all {
2738 margin: 8px 0 0;
2739 font-size: 13px;
2740}
2741
2742.upload-conflict-actions {
2743 display: flex;
2744 justify-content: flex-end;
2745 gap: 6px;
2746}
2747
2748.upload-graph {
2749 margin-top: 8px;
2750 color: var(--accent);
2751 border: 1px solid var(--border);
2752 background: color-mix(in srgb, var(--accent) 6%, var(--panel));
2753}
2754
2755/* Peak label above the plot, so the line can never run through it. */
2756.upload-graph-max {
2757 display: block;
2758 text-align: right;
2759 padding: 2px 6px 0;
2760 font-size: 11px;
2761 line-height: 14px;
2762}
2763
2764.upload-graph svg {
2765 display: block;
2766 width: 100%;
2767 height: 46px;
2768}
2769
2770.upload-errors {
2771 margin: 6px 0 0;
2772 padding-left: 18px;
2773 font-size: 12px;
2774 color: var(--danger);
2775}
2776
2777.upload-err-file {
2778 font-weight: 500;
2779}
2780
2781/* Log view: the same rows, full width, roomier. */
2782.uploads-view .upload-log {
2783 border: 1px solid var(--border);
2784 background: var(--panel);
2785 font-size: 14px;
2786}
2787
2788.uploads-view .upload-job {
2789 padding: 12px 14px;
2790}
2791
2792.uploads-view .upload-graph svg {
2793 height: 76px;
2794}
2795
2796/* Security settings tab: password, passkeys, sign-in requirement. */
2797
2798.security-tab {
2799 display: flex;
2800 flex-direction: column;
2801 gap: 10px;
2802}
2803
2804.security-tab h3 {
2805 margin: 10px 0 0;
2806}
2807
2808/* Matches .excludes-field, so the three blocks of this tab read as one list. */
2809.security-form {
2810 display: flex;
2811 flex-direction: column;
2812 gap: 10px;
2813 padding: 14px;
2814 border: 1px solid var(--border);
2815}
2816
2817.security-form .field input {
2818 width: 100%;
2819 box-sizing: border-box;
2820}
2821
2822.passkey-list {
2823 display: flex;
2824 flex-direction: column;
2825 gap: 2px;
2826 padding: 0 14px;
2827}
2828
2829.passkey-row {
2830 display: flex;
2831 align-items: center;
2832 gap: 8px;
2833 padding: 6px 0;
2834}
2835
2836.passkey-main {
2837 flex: 1;
2838 overflow: hidden;
2839}
2840
2841.passkey-name {
2842 display: block;
2843 font-size: 13px;
2844 overflow: hidden;
2845 text-overflow: ellipsis;
2846 white-space: nowrap;
2847}
2848
2849/* "Needs your user name": a note, not an error. This passkey works, it just
2850 cannot be found without the name typed in first. */
2851.passkey-warning {
2852 display: inline-block;
2853 margin-top: 2px;
2854 padding: 1px 6px;
2855 border: 1px solid var(--border);
2856 font-size: 11px;
2857 color: var(--muted);
2858}
2859
2860/* The sign-in requirement's labels are whole phrases, so the shared 190px
2861 cap for setting selects would clip them. */
2862.security-tab .setting-row-select select {
2863 max-width: none;
2864 width: auto;
2865}
2866
2867/* Separator between the password form and the passkey button. `.auth-card p`
2868 sets a bottom-only margin on every paragraph in the card, and wins on
2869 specificity, so both of these have to name the card too. */
2870.auth-card .login-or {
2871 margin: 16px 0 8px;
2872 font-size: 12px;
2873 color: var(--muted);
2874 text-transform: lowercase;
2875}
2876
2877.auth-card .setting-desc {
2878 margin-top: 8px;
2879}
2880
2881.auth-card .btn {
2882 width: 100%;
2883}
2884
2885/* ------------------------------------------------------------------ */
2886/* Calendars and contacts */
2887/* ------------------------------------------------------------------ */
2888
2889.pim-view {
2890 display: flex;
2891 gap: 16px;
2892 flex: 1;
2893 min-height: 0;
2894 width: 100%;
2895}
2896
2897.pim-side {
2898 flex: none;
2899 width: 260px;
2900 display: flex;
2901 flex-direction: column;
2902 gap: 8px;
2903 min-width: 0;
2904}
2905
2906.pim-side-head {
2907 display: flex;
2908 align-items: center;
2909 gap: 4px;
2910}
2911
2912.pim-side-head .admin-section-title {
2913 flex: 1;
2914 margin: 0;
2915}
2916
2917.pim-list {
2918 display: flex;
2919 flex-direction: column;
2920 border: 1px solid var(--border);
2921 background: var(--panel);
2922}
2923
2924.pim-group-label {
2925 padding: 10px 10px 4px;
2926 font-size: 12px;
2927 color: var(--muted);
2928 border-top: 1px solid var(--border);
2929}
2930
2931.pim-row {
2932 display: flex;
2933 align-items: center;
2934 min-width: 0;
2935}
2936
2937.pim-row:hover {
2938 background: var(--bg);
2939}
2940
2941.pim-toggle {
2942 flex: none;
2943 display: inline-flex;
2944 align-items: center;
2945 justify-content: center;
2946 width: 36px;
2947 height: 36px;
2948 padding: 0;
2949 border: 0;
2950 background: none;
2951 cursor: pointer;
2952}
2953
2954.pim-swatch {
2955 display: inline-block;
2956 flex: none;
2957 width: 14px;
2958 height: 14px;
2959 background: var(--swatch);
2960 border: 2px solid var(--swatch);
2961}
2962
2963/* A hidden collection keeps its color as an outline only. */
2964.pim-row-hidden .pim-swatch {
2965 background: transparent;
2966}
2967
2968.pim-row-hidden .pim-row-name {
2969 color: var(--muted);
2970}
2971
2972.pim-row-main {
2973 flex: 1;
2974 min-width: 0;
2975 display: flex;
2976 align-items: baseline;
2977 justify-content: flex-start;
2978 gap: 6px;
2979 padding: 8px 10px 8px 0;
2980 border: 0;
2981 background: none;
2982 color: var(--text);
2983 font: inherit;
2984 font-size: 14px;
2985 text-align: left;
2986 cursor: pointer;
2987}
2988
2989.pim-row-name {
2990 min-width: 0;
2991 overflow: hidden;
2992 text-overflow: ellipsis;
2993 white-space: nowrap;
2994}
2995
2996.pim-row-gear {
2997 flex: none;
2998 margin-right: 4px;
2999}
3000
3001.pim-show-all {
3002 align-self: flex-start;
3003}
3004
3005.pim-row-note {
3006 flex: none;
3007 max-width: 45%;
3008 font-size: 12px;
3009 color: var(--muted);
3010 overflow: hidden;
3011 text-overflow: ellipsis;
3012 white-space: nowrap;
3013}
3014
3015.pim-main {
3016 flex: 1;
3017 min-width: 0;
3018 border: 1px solid var(--border);
3019 background: var(--panel);
3020}
3021
3022.pim-modal {
3023 width: 600px;
3024 max-height: calc(100vh - 48px);
3025 overflow-y: auto;
3026}
3027
3028.pim-modal-title {
3029 display: flex;
3030 align-items: center;
3031 gap: 10px;
3032 overflow-wrap: anywhere;
3033}
3034
3035.pim-note {
3036 margin: -6px 0 8px;
3037 font-size: 13px;
3038}
3039
3040.pim-section {
3041 display: block;
3042 padding: 14px 0 0;
3043 margin-top: 14px;
3044 border-top: 1px solid var(--border);
3045}
3046
3047.pim-section-title {
3048 margin: 0 0 8px;
3049 font-size: 14px;
3050}
3051
3052.pim-hint {
3053 margin: 0 0 10px;
3054 font-size: 13px;
3055}
3056
3057.pim-section-actions {
3058 display: flex;
3059 justify-content: flex-end;
3060 flex-wrap: wrap;
3061 gap: 8px;
3062}
3063
3064.pim-section-actions.pim-left {
3065 justify-content: flex-start;
3066}
3067
3068.pim-colors {
3069 display: flex;
3070 flex-wrap: wrap;
3071 align-items: center;
3072 gap: 6px;
3073}
3074
3075.pim-color {
3076 width: 26px;
3077 height: 26px;
3078 padding: 0;
3079 border: 2px solid var(--panel);
3080 outline: 1px solid var(--border);
3081 background: var(--swatch);
3082 cursor: pointer;
3083}
3084
3085.pim-color.selected {
3086 outline: 2px solid var(--text);
3087}
3088
3089.pim-color-input {
3090 width: 34px;
3091 height: 30px;
3092 padding: 0 2px;
3093 cursor: pointer;
3094}
3095
3096.pim-items {
3097 display: flex;
3098 flex-direction: column;
3099 gap: 6px;
3100 margin-bottom: 10px;
3101}
3102
3103.pim-item {
3104 display: flex;
3105 align-items: center;
3106 gap: 8px;
3107 min-width: 0;
3108}
3109
3110.pim-item-name {
3111 flex: 1;
3112 min-width: 0;
3113 font-size: 14px;
3114 overflow: hidden;
3115 text-overflow: ellipsis;
3116 white-space: nowrap;
3117}
3118
3119.pim-mode {
3120 flex: none;
3121 width: auto;
3122 max-width: 55%;
3123 padding: 6px 28px 6px 8px;
3124}
3125
3126.pim-add {
3127 display: flex;
3128 gap: 8px;
3129 align-items: center;
3130}
3131
3132.pim-add .field-input {
3133 flex: 1;
3134 min-width: 0;
3135}
3136
3137.pim-link {
3138 display: flex;
3139 align-items: center;
3140 gap: 10px;
3141 padding: 8px 10px;
3142 border: 1px solid var(--border);
3143 min-width: 0;
3144}
3145
3146.pim-link-main {
3147 flex: 1;
3148 min-width: 0;
3149 display: flex;
3150 flex-direction: column;
3151 gap: 2px;
3152}
3153
3154.pim-link-url {
3155 font-size: 13px;
3156 overflow: hidden;
3157 text-overflow: ellipsis;
3158 white-space: nowrap;
3159}
3160
3161.pim-link-meta {
3162 font-size: 12px;
3163}
3164
3165.pim-link-actions {
3166 display: flex;
3167 flex: none;
3168 gap: 6px;
3169}
3170
3171.pim-link-form {
3172 display: flex;
3173 flex-wrap: wrap;
3174 align-items: flex-end;
3175 gap: 10px;
3176}
3177
3178.pim-inline {
3179 display: flex;
3180 flex-direction: column;
3181 gap: 4px;
3182 font-size: 13px;
3183 color: var(--muted);
3184}
3185
3186.pim-inline input,
3187.pim-inline select {
3188 width: auto;
3189 min-width: 140px;
3190}
3191
3192.pim-hints,
3193.pim-sample {
3194 margin: 0 0 8px;
3195 padding-left: 18px;
3196 font-size: 13px;
3197 line-height: 1.5;
3198}
3199
3200.pim-sample li {
3201 overflow-wrap: anywhere;
3202}
3203
3204.admin-stack {
3205 display: flex;
3206 flex-direction: column;
3207 gap: 28px;
3208}
3209
3210.rooms-hint {
3211 margin-bottom: 12px;
3212}
3213
3214.room-address {
3215 font-size: 12px;
3216 margin-top: 2px;
3217 overflow-wrap: anywhere;
3218}
3219
3220.feeds-header {
3221 margin-top: 28px;
3222}
3223
3224/* Below this the month grid needs the width of the collection list. */
3225@media (max-width: 1180px) {
3226 .pim-view {
3227 flex-direction: column;
3228 }
3229
3230 .pim-side {
3231 width: auto;
3232 }
3233
3234 /* The events and contacts come first; the list of collections would
3235 fill a whole phone screen above them. */
3236 .pim-main {
3237 min-height: 240px;
3238 order: -1;
3239 }
3240}
3241
3242@media (max-width: 640px) {
3243 .pim-link {
3244 flex-direction: column;
3245 align-items: stretch;
3246 }
3247
3248 .pim-link-actions {
3249 flex-wrap: wrap;
3250 }
3251
3252 .pim-add {
3253 flex-wrap: wrap;
3254 }
3255
3256 .pim-add .pim-mode {
3257 max-width: 100%;
3258 }
3259
3260 /* A lent row keeps name, mode and remove button on one line. */
3261 .pim-item .pim-mode {
3262 flex: 0 1 auto;
3263 min-width: 0;
3264 }
3265}
3266
3267/* Mirrored chevron: the icon set has only the right-pointing one. */
3268.flip {
3269 transform: scaleX(-1);
3270}
3271
3272/* Selects in the calendar and contacts views, their dialogs, and any form
3273 field: the same height and colors as the inputs beside them, in both
3274 themes. */
3275.pim-modal select,
3276.pim-view select,
3277select.field-input {
3278 appearance: none;
3279 padding: 10px 34px 10px 12px;
3280 border: 1px solid var(--border);
3281 background-color: var(--bg);
3282 background-image: var(--select-arrow);
3283 background-repeat: no-repeat;
3284 background-position: right 11px center;
3285 background-size: 12px 12px;
3286 color: var(--text);
3287 font: inherit;
3288 font-size: 14px;
3289}
3290
3291.pim-modal select:focus,
3292.pim-view select:focus,
3293select.field-input:focus {
3294 outline: 2px solid var(--accent);
3295 outline-offset: -1px;
3296}
3297
3298.pim-link-form .pim-inline input {
3299 padding: 10px 12px;
3300}
3301
3302.pim-file-input {
3303 display: none;
3304}
3305
3306.pim-main {
3307 display: flex;
3308 flex-direction: column;
3309 min-height: 0;
3310}
3311
3312/* ---- Calendar ---------------------------------------------------- */
3313
3314.cal {
3315 display: flex;
3316 flex-direction: column;
3317 min-width: 0;
3318}
3319
3320.cal-pad {
3321 padding: 12px 14px;
3322}
3323
3324.cal-toolbar {
3325 display: flex;
3326 align-items: center;
3327 flex-wrap: wrap;
3328 gap: 8px 12px;
3329 padding: 10px 12px;
3330 border-bottom: 1px solid var(--border);
3331}
3332
3333.cal-nav {
3334 display: flex;
3335 align-items: center;
3336 gap: 4px;
3337}
3338
3339.cal-title {
3340 flex: 1;
3341 min-width: 0;
3342 margin: 0;
3343 font-size: 17px;
3344 white-space: nowrap;
3345 overflow: hidden;
3346 text-overflow: ellipsis;
3347}
3348
3349.cal-views {
3350 display: flex;
3351}
3352
3353.cal-view-btn {
3354 padding: 6px 12px;
3355 font-size: 13px;
3356 margin-left: -1px;
3357}
3358
3359.cal-view-btn.active {
3360 background: var(--accent);
3361 border-color: var(--accent-border);
3362 color: #fff;
3363 position: relative;
3364 z-index: 1;
3365}
3366
3367/* The buttons overlap by one pixel; the hovered one must draw on top, or its
3368 neighbor covers its right edge. */
3369.cal-view-btn:hover,
3370.cal-view-btn:focus-visible {
3371 position: relative;
3372 z-index: 2;
3373}
3374
3375.cal-badge {
3376 min-width: 18px;
3377 padding: 0 5px;
3378 font-size: 11px;
3379 line-height: 18px;
3380 background: var(--danger);
3381 color: #fff;
3382 text-align: center;
3383}
3384
3385.cal-truncated {
3386 margin: 0;
3387 padding: 8px 12px;
3388 font-size: 13px;
3389 color: var(--danger);
3390 border-bottom: 1px solid var(--border);
3391}
3392
3393.cal-head {
3394 display: grid;
3395 grid-template-columns: repeat(7, minmax(0, 1fr));
3396 border-bottom: 1px solid var(--border);
3397}
3398
3399.cal-head-day {
3400 padding: 6px 8px;
3401 font-size: 12px;
3402 color: var(--muted);
3403 overflow: hidden;
3404 text-overflow: ellipsis;
3405 white-space: nowrap;
3406}
3407
3408.cal-week {
3409 display: grid;
3410 grid-template-columns: repeat(7, minmax(0, 1fr));
3411 grid-template-rows: 26px repeat(var(--lines), 22px) 6px;
3412 border-bottom: 1px solid var(--border);
3413}
3414
3415.cal-cell {
3416 grid-row: 1 / -1;
3417 display: flex;
3418 flex-direction: column;
3419 align-items: flex-start;
3420 justify-content: flex-start;
3421 gap: 4px;
3422 min-width: 0;
3423 padding: 4px 6px;
3424 border: 0;
3425 border-right: 1px solid var(--border);
3426 background: none;
3427 color: var(--text);
3428 font: inherit;
3429 cursor: pointer;
3430}
3431
3432.cal-cell:nth-child(7) {
3433 border-right: 0;
3434}
3435
3436.cal-cell:hover {
3437 background: var(--bg);
3438}
3439
3440.cal-cell.cal-out .cal-num {
3441 color: var(--muted);
3442}
3443
3444.cal-cell.cal-sel {
3445 background: var(--bg);
3446 box-shadow: inset 0 0 0 2px var(--accent);
3447}
3448
3449.cal-num {
3450 font-size: 13px;
3451 line-height: 18px;
3452 min-width: 20px;
3453 text-align: center;
3454}
3455
3456.cal-today .cal-num {
3457 background: var(--accent);
3458 color: #fff;
3459 font-weight: 600;
3460}
3461
3462/* Phone only: the colors of a day's events instead of their titles. */
3463.cal-dots {
3464 display: none;
3465 gap: 3px;
3466}
3467
3468.cal-dot {
3469 display: inline-block;
3470 flex: none;
3471 width: 8px;
3472 height: 8px;
3473 background: var(--c, var(--accent));
3474}
3475
3476.cal-item {
3477 position: relative;
3478 z-index: 1;
3479 display: flex;
3480 align-items: center;
3481 justify-content: flex-start;
3482 gap: 4px;
3483 min-width: 0;
3484 margin: 1px 2px;
3485 padding: 0 6px;
3486 border: 0;
3487 font-size: 12px;
3488 line-height: 20px;
3489 text-align: left;
3490 cursor: pointer;
3491 overflow: hidden;
3492}
3493
3494.cal-bar {
3495 background: var(--c);
3496 color: #fff;
3497 text-shadow: 0 0 2px rgb(0 0 0 / 40%);
3498}
3499
3500.cal-bar.cal-cont-l {
3501 margin-left: 0;
3502}
3503
3504.cal-bar.cal-cont-r {
3505 margin-right: 0;
3506}
3507
3508.cal-chip {
3509 background: none;
3510 color: var(--text);
3511}
3512
3513.cal-chip:hover {
3514 background: var(--bg);
3515}
3516
3517.cal-time {
3518 flex: none;
3519 color: var(--muted);
3520 font-variant-numeric: tabular-nums;
3521}
3522
3523.cal-item-title {
3524 min-width: 0;
3525 overflow: hidden;
3526 text-overflow: ellipsis;
3527 white-space: nowrap;
3528}
3529
3530.cal-cancelled .cal-item-title,
3531.cal-cancelled .cal-row-title,
3532.cal-detail-title.cal-cancelled {
3533 text-decoration: line-through;
3534}
3535
3536.cal-more {
3537 position: relative;
3538 z-index: 1;
3539 justify-content: flex-start;
3540 margin: 1px 2px;
3541 padding: 0 6px;
3542 border: 0;
3543 background: none;
3544 color: var(--muted);
3545 font-size: 12px;
3546 line-height: 20px;
3547 white-space: nowrap;
3548 overflow: hidden;
3549 min-width: 0;
3550}
3551
3552.cal-more:hover {
3553 color: var(--text);
3554 text-decoration: underline;
3555}
3556
3557.cal-day {
3558 border-top: 1px solid var(--border);
3559}
3560
3561.cal-day-title {
3562 margin: 0;
3563 padding: 12px 14px 6px;
3564 font-size: 14px;
3565}
3566
3567.cal-day-today {
3568 color: var(--accent);
3569}
3570
3571.cal-rows {
3572 display: flex;
3573 flex-direction: column;
3574 padding-bottom: 8px;
3575}
3576
3577.cal-row {
3578 display: flex;
3579 align-items: baseline;
3580 justify-content: flex-start;
3581 gap: 10px;
3582 padding: 7px 14px;
3583 border: 0;
3584 background: none;
3585 color: var(--text);
3586 font: inherit;
3587 text-align: left;
3588 cursor: pointer;
3589 min-width: 0;
3590}
3591
3592.cal-row:hover {
3593 background: var(--bg);
3594}
3595
3596.cal-row .cal-dot {
3597 align-self: center;
3598}
3599
3600.cal-row-time {
3601 flex: none;
3602 width: 120px;
3603 font-size: 13px;
3604 color: var(--muted);
3605 font-variant-numeric: tabular-nums;
3606}
3607
3608.cal-row-main {
3609 display: flex;
3610 flex-direction: column;
3611 gap: 2px;
3612 min-width: 0;
3613}
3614
3615.cal-row-title {
3616 font-size: 14px;
3617 overflow-wrap: anywhere;
3618}
3619
3620.cal-row-meta {
3621 font-size: 12.5px;
3622 color: var(--muted);
3623 overflow-wrap: anywhere;
3624}
3625
3626.cal-agenda-day + .cal-agenda-day {
3627 border-top: 1px solid var(--border);
3628}
3629
3630.cal-inv {
3631 display: flex;
3632 align-items: center;
3633 gap: 12px;
3634 padding: 12px 14px;
3635 border-bottom: 1px solid var(--border);
3636}
3637
3638.cal-inv-main {
3639 flex: 1;
3640 min-width: 0;
3641 display: flex;
3642 flex-direction: column;
3643 align-items: flex-start;
3644 gap: 3px;
3645 padding: 0;
3646 border: 0;
3647 background: none;
3648 color: var(--text);
3649 font: inherit;
3650 text-align: left;
3651 cursor: pointer;
3652}
3653
3654.cal-inv-title {
3655 font-weight: 600;
3656 overflow-wrap: anywhere;
3657}
3658
3659.cal-inv-actions {
3660 display: flex;
3661 flex-wrap: wrap;
3662 gap: 6px;
3663}
3664
3665.cal-detail-title {
3666 overflow-wrap: anywhere;
3667}
3668
3669.cal-flag {
3670 display: inline-block;
3671 margin: 0 0 12px;
3672 padding: 2px 8px;
3673 font-size: 12px;
3674 border: 1px solid var(--danger);
3675 color: var(--danger);
3676}
3677
3678.cal-facts {
3679 display: grid;
3680 grid-template-columns: max-content minmax(0, 1fr);
3681 gap: 8px 16px;
3682 margin: 0 0 16px;
3683 font-size: 14px;
3684}
3685
3686.cal-facts dt {
3687 color: var(--muted);
3688}
3689
3690.cal-facts dd {
3691 margin: 0;
3692 overflow-wrap: anywhere;
3693}
3694
3695.cal-pre {
3696 white-space: pre-wrap;
3697}
3698
3699.cal-desc {
3700 margin-bottom: 16px;
3701 padding: 10px 12px;
3702 border: 1px solid var(--border);
3703 background: var(--bg);
3704 font-size: 14px;
3705 overflow-wrap: anywhere;
3706 max-height: 40vh;
3707 overflow-y: auto;
3708}
3709
3710.cal-attendees {
3711 list-style: none;
3712 margin: 0 0 16px;
3713 padding: 0;
3714 display: flex;
3715 flex-direction: column;
3716 gap: 6px;
3717 font-size: 14px;
3718}
3719
3720.cal-attendees li {
3721 display: flex;
3722 align-items: center;
3723 gap: 8px;
3724 min-width: 0;
3725}
3726
3727.cal-att-name {
3728 min-width: 0;
3729 overflow: hidden;
3730 text-overflow: ellipsis;
3731 white-space: nowrap;
3732}
3733
3734.ps-badge {
3735 margin-left: auto;
3736 flex: none;
3737 padding: 1px 7px;
3738 font-size: 11.5px;
3739 border: 1px solid var(--border);
3740 color: var(--muted);
3741}
3742
3743.ps-accepted {
3744 border-color: #16a34a;
3745 color: #16a34a;
3746}
3747
3748.ps-declined {
3749 border-color: var(--danger);
3750 color: var(--danger);
3751}
3752
3753.ps-tentative {
3754 border-color: #d97706;
3755 color: #d97706;
3756}
3757
3758.cal-reply {
3759 border-top: 1px solid var(--border);
3760 padding-top: 12px;
3761}
3762
3763.cal-scope {
3764 display: flex;
3765 flex-wrap: wrap;
3766 gap: 4px 16px;
3767}
3768
3769.cal-scope input[type="radio"] {
3770 width: auto;
3771 margin: 0;
3772 padding: 0;
3773 accent-color: var(--accent);
3774}
3775
3776/* ---- Contacts ---------------------------------------------------- */
3777
3778.ct {
3779 display: flex;
3780 min-height: 0;
3781 flex: 1;
3782}
3783
3784.ct-list {
3785 flex: none;
3786 width: 320px;
3787 display: flex;
3788 flex-direction: column;
3789 border-right: 1px solid var(--border);
3790 min-height: 0;
3791 /* Rows do not wrap; on a phone the list must not grow to their width. */
3792 min-width: 0;
3793}
3794
3795.ct-search {
3796 position: relative;
3797 padding: 10px;
3798 border-bottom: 1px solid var(--border);
3799}
3800
3801.ct-search input {
3802 padding-left: 34px;
3803}
3804
3805.ct-search-icon {
3806 position: absolute;
3807 left: 20px;
3808 top: 50%;
3809 width: 16px;
3810 height: 16px;
3811 transform: translateY(-50%);
3812 color: var(--muted);
3813 pointer-events: none;
3814}
3815
3816.ct-rows {
3817 display: flex;
3818 flex-direction: column;
3819 overflow-y: auto;
3820 max-height: calc(100vh - var(--topbar-h) - 120px);
3821}
3822
3823.ct-row {
3824 display: flex;
3825 align-items: center;
3826 justify-content: flex-start;
3827 gap: 10px;
3828 padding: 8px 12px;
3829 border: 0;
3830 border-bottom: 1px solid var(--border);
3831 background: none;
3832 color: var(--text);
3833 font: inherit;
3834 text-align: left;
3835 cursor: pointer;
3836 min-width: 0;
3837}
3838
3839.ct-row:hover {
3840 background: var(--bg);
3841}
3842
3843.ct-row.ct-sel {
3844 background: var(--bg);
3845 box-shadow: inset 3px 0 0 var(--accent);
3846}
3847
3848/* The contact's address book, colored like its entry in the list beside. */
3849.ct-book {
3850 flex: none;
3851 width: 10px;
3852 height: 10px;
3853 margin-left: auto;
3854 background: var(--swatch);
3855}
3856
3857.ct-avatar {
3858 flex: none;
3859 width: 36px;
3860 height: 36px;
3861 object-fit: cover;
3862 background: var(--bg);
3863}
3864
3865.ct-initials {
3866 position: relative;
3867 overflow: hidden;
3868 display: inline-flex;
3869 align-items: center;
3870 justify-content: center;
3871 border: 1px solid var(--border);
3872 font-size: 13px;
3873 font-weight: 600;
3874 color: var(--muted);
3875}
3876
3877.ct-photo {
3878 position: absolute;
3879 inset: 0;
3880 width: 100%;
3881 height: 100%;
3882 object-fit: cover;
3883 background: var(--bg);
3884}
3885
3886.ct-avatar-lg {
3887 width: 72px;
3888 height: 72px;
3889 font-size: 22px;
3890}
3891
3892.ct-row-main {
3893 display: flex;
3894 flex-direction: column;
3895 gap: 2px;
3896 min-width: 0;
3897}
3898
3899.ct-name {
3900 display: flex;
3901 align-items: center;
3902 gap: 6px;
3903 font-size: 14px;
3904 min-width: 0;
3905}
3906
3907.ct-name-text {
3908 min-width: 0;
3909 overflow: hidden;
3910 text-overflow: ellipsis;
3911 white-space: nowrap;
3912}
3913
3914.ct-meta {
3915 font-size: 12.5px;
3916 color: var(--muted);
3917 overflow: hidden;
3918 text-overflow: ellipsis;
3919 white-space: nowrap;
3920}
3921
3922.ct-detail {
3923 flex: 1;
3924 min-width: 0;
3925 padding: 16px 20px;
3926 overflow-y: auto;
3927}
3928
3929.ct-back {
3930 display: none;
3931 margin-bottom: 12px;
3932}
3933
3934.ct-head {
3935 display: flex;
3936 align-items: center;
3937 gap: 16px;
3938 margin-bottom: 18px;
3939 min-width: 0;
3940}
3941
3942.ct-head-text {
3943 min-width: 0;
3944}
3945
3946.ct-head-text p {
3947 margin: 4px 0 0;
3948 overflow-wrap: anywhere;
3949}
3950
3951.ct-title {
3952 margin: 0;
3953 font-size: 20px;
3954 overflow-wrap: anywhere;
3955}
3956
3957.ct-value {
3958 display: flex;
3959 align-items: baseline;
3960 flex-wrap: wrap;
3961 gap: 0 8px;
3962}
3963
3964.ct-value + .ct-value {
3965 margin-top: 4px;
3966}
3967
3968.ct-label {
3969 font-size: 12px;
3970}
3971
3972@media (max-width: 860px) {
3973 .ct-list {
3974 width: 260px;
3975 }
3976
3977 .cal-row-time {
3978 width: 96px;
3979 }
3980}
3981
3982@media (max-width: 640px) {
3983 .content:has(.pim-view) {
3984 padding: 12px;
3985 }
3986
3987 .cal-toolbar {
3988 padding: 8px;
3989 }
3990
3991 .cal-title {
3992 order: -1;
3993 flex-basis: 100%;
3994 }
3995
3996 .cal-views {
3997 margin-left: auto;
3998 }
3999
4000 .cal-view-btn {
4001 padding: 6px 9px;
4002 }
4003
4004 /* Phones get compact day cells with dots; the day list below the grid
4005 shows the chosen day's events. */
4006 .cal-week {
4007 grid-template-rows: 46px;
4008 }
4009
4010 .cal-item,
4011 .cal-more {
4012 display: none;
4013 }
4014
4015 .cal-dots {
4016 display: flex;
4017 }
4018
4019 .cal-cell {
4020 align-items: center;
4021 padding: 4px 2px;
4022 }
4023
4024 .cal-head-day {
4025 padding: 6px 2px;
4026 text-align: center;
4027 }
4028
4029 .cal-inv {
4030 flex-direction: column;
4031 align-items: stretch;
4032 }
4033
4034 .cal-row-time {
4035 width: 80px;
4036 }
4037
4038 .cal-facts {
4039 grid-template-columns: minmax(0, 1fr);
4040 gap: 2px;
4041 }
4042
4043 .cal-facts dd {
4044 margin-bottom: 8px;
4045 }
4046
4047 /* List and details take turns. */
4048 .ct-list {
4049 width: auto;
4050 flex: 1;
4051 border-right: 0;
4052 }
4053
4054 .ct-rows {
4055 max-height: none;
4056 }
4057
4058 .ct-detail,
4059 .ct-has-open .ct-list {
4060 display: none;
4061 }
4062
4063 .ct-has-open .ct-detail {
4064 display: block;
4065 padding: 12px;
4066 }
4067
4068 .ct-back {
4069 display: inline-flex;
4070 }
4071}
4072