style.css
⎇
Raw
1/* Custom layout and components on top of Pico.css */
2
3/* Scale down Pico's default sizing */
4html {
5 font-size: 16px;
6}
7
8/* Remove Pico's default bottom margin on button types */
9[type="button"],
10[type="reset"],
11[type="submit"] {
12 margin-bottom: 0;
13}
14
15/* Layout */
16body {
17 min-height: 100vh;
18 display: flex;
19 flex-direction: column;
20}
21
22header {
23 padding: 1rem 2rem;
24 position: sticky;
25 top: 0;
26 z-index: 100;
27 background-color: var(--pico-card-background-color);
28 border-bottom: 1px solid var(--pico-muted-border-color);
29 display: flex;
30 align-items: center;
31 gap: 2rem;
32}
33
34header h1 {
35 margin: 0;
36 flex-shrink: 0;
37 font-size: 1.5rem;
38}
39
40nav {
41 display: flex;
42 gap: 1rem;
43}
44
45nav a {
46 color: var(--pico-muted-color);
47 text-decoration: none;
48 padding: 0.5rem 1rem;
49 border-radius: var(--pico-border-radius);
50 transition: all 0.2s;
51}
52
53nav a:hover {
54 color: var(--pico-primary);
55 background-color: var(--pico-card-sectioning-background-color);
56}
57
58nav a.active {
59 background-color: var(--pico-primary);
60 color: var(--pico-primary-inverse);
61 font-weight: 600;
62}
63
64main {
65 flex: 1;
66 padding: 2rem 1rem;
67}
68
69main > * {
70 max-width: 1400px;
71 margin-left: auto;
72 margin-right: auto;
73}
74
75footer {
76 padding: 1rem 2rem;
77 text-align: center;
78 color: var(--pico-muted-color);
79 font-size: 0.9rem;
80 background-color: var(--pico-card-background-color);
81 border-top: 1px solid var(--pico-muted-border-color);
82}
83
84/* Page headers */
85.page-header {
86 display: flex;
87 justify-content: space-between;
88 align-items: center;
89 margin-bottom: 1.5rem;
90 flex-wrap: wrap;
91 gap: 1rem;
92}
93
94.page-header h2 {
95 font-size: 1.5rem;
96 margin: 0;
97}
98
99.actions {
100 display: flex;
101 gap: 1rem;
102 align-items: center;
103 flex-wrap: wrap;
104}
105
106/* Forms */
107.form-group {
108 margin-bottom: 1rem;
109}
110
111.form-row {
112 display: grid;
113 grid-template-columns: 1fr 1fr;
114 gap: 1rem;
115}
116
117.radio-group {
118 display: flex;
119 flex-wrap: wrap;
120 gap: 1rem;
121}
122
123.radio-group label {
124 display: flex;
125 align-items: center;
126 gap: 0.3rem;
127 cursor: pointer;
128 font-weight: normal;
129 margin-bottom: 0;
130}
131
132.form-actions {
133 display: flex;
134 gap: 1rem;
135 margin-top: 1.5rem;
136}
137
138/* Buttons */
139button.small,
140[role="button"].small {
141 padding: 0.3rem 0.6rem;
142 font-size: 0.85rem;
143}
144
145button.danger,
146button.danger:hover,
147button.danger:focus,
148[role="button"].danger,
149[role="button"].danger:hover,
150[role="button"].danger:focus {
151 --pico-background-color: #c62828;
152 --pico-border-color: #c62828;
153 --pico-color: white;
154}
155
156button.danger:hover,
157button.danger:focus,
158[role="button"].danger:hover,
159[role="button"].danger:focus {
160 --pico-background-color: #b71c1c;
161 --pico-border-color: #b71c1c;
162}
163
164button:disabled {
165 opacity: 0.5;
166 cursor: not-allowed;
167}
168
169/* Library */
170.breadcrumbs {
171 margin-bottom: 1rem;
172 display: flex;
173 align-items: center;
174 justify-content: flex-start;
175 gap: 0.35rem;
176 flex-wrap: wrap;
177 text-align: left;
178 width: 100%;
179}
180
181.breadcrumbs a,
182.breadcrumbs span {
183 flex-shrink: 0;
184}
185
186.breadcrumbs .current {
187 cursor: default;
188 pointer-events: none;
189}
190
191.library-grid {
192 display: grid;
193 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
194 gap: 1rem;
195}
196
197.folder-item,
198.media-item {
199 background: var(--pico-card-background-color);
200 border: 1px solid var(--pico-card-border-color);
201 border-radius: var(--pico-border-radius);
202 overflow: hidden;
203 text-decoration: none;
204 color: var(--pico-color);
205 box-shadow: var(--pico-card-box-shadow);
206}
207
208.folder-item:hover,
209.media-item:hover {
210 background-color: var(--pico-primary-background);
211 border-color: var(--pico-primary-border);
212 box-shadow: var(--pico-card-box-shadow), 0 4px 12px rgba(0, 0, 0, 0.15);
213}
214
215.folder-item:hover .folder-name,
216.media-item:hover .media-info,
217.media-item:hover .media-title,
218.media-item:hover .media-path {
219 color: var(--pico-primary-inverse);
220}
221
222.media-info {
223 transition: color 0.2s;
224}
225
226.media-thumbnail {
227 aspect-ratio: 16/9;
228 background: var(--pico-card-sectioning-background-color);
229 display: flex;
230 align-items: center;
231 justify-content: center;
232 font-size: 3rem;
233 position: relative;
234}
235
236.media-thumbnail .media-duration {
237 position: absolute;
238 bottom: 0.4rem;
239 right: 0.4rem;
240 background: rgba(0, 0, 0, 0.75);
241 color: #fff;
242 padding: 0.15rem 0.4rem;
243 border-radius: var(--pico-border-radius);
244 font-size: 0.75rem;
245 font-weight: 500;
246 line-height: 1;
247}
248
249.folder-icon {
250 aspect-ratio: 16/9;
251 background: var(--pico-card-sectioning-background-color);
252 display: flex;
253 align-items: center;
254 justify-content: center;
255 font-size: 3rem;
256}
257
258.media-thumbnail img {
259 width: 100%;
260 height: 100%;
261 /* contain: show the whole thumbnail (letterboxed) and never overflow the
262 16/9 box, including portrait/vertical sources. */
263 object-fit: contain;
264}
265
266.media-thumbnail img[src$=".svg"] {
267 width: 48px;
268 height: 48px;
269 opacity: 0.5;
270 object-fit: contain;
271}
272
273/* Multi-file items: tile each media file's thumbnail in a grid. */
274.media-thumbnail.count-2,
275.media-thumbnail.count-3,
276.media-thumbnail.count-many {
277 display: grid;
278 gap: 2px;
279}
280
281.media-thumbnail.count-2 {
282 grid-template-columns: 1fr 1fr;
283}
284
285.media-thumbnail.count-3,
286.media-thumbnail.count-many {
287 grid-template-columns: 1fr 1fr;
288 grid-template-rows: 1fr 1fr;
289}
290
291/* For 3 files, let the first thumbnail span the full top row. */
292.media-thumbnail.count-3 img:first-of-type {
293 grid-column: 1 / -1;
294}
295
296.media-thumbnail.count-2 img,
297.media-thumbnail.count-3 img,
298.media-thumbnail.count-many img {
299 width: 100%;
300 height: 100%;
301 object-fit: contain;
302 min-width: 0;
303 min-height: 0;
304}
305
306/* "+N" overlay on the last tile when there are more than 4 files. */
307.media-thumbnail .thumb-more {
308 position: absolute;
309 right: 0;
310 bottom: 0;
311 width: 50%;
312 height: 50%;
313 display: flex;
314 align-items: center;
315 justify-content: center;
316 background: rgba(0, 0, 0, 0.6);
317 color: #fff;
318 font-size: 1.1rem;
319 font-weight: 600;
320 pointer-events: none;
321}
322
323/* File-count badge in the top-left for multi-file items. */
324.media-thumbnail .thumb-count {
325 position: absolute;
326 top: 0.4rem;
327 left: 0.4rem;
328 background: rgba(0, 0, 0, 0.75);
329 color: #fff;
330 padding: 0.15rem 0.4rem;
331 border-radius: var(--pico-border-radius);
332 font-size: 0.7rem;
333 font-weight: 500;
334 line-height: 1;
335}
336
337.folder-name,
338.media-info {
339 padding: 0.75rem;
340}
341
342.media-title {
343 font-weight: 500;
344 margin-bottom: 0.3rem;
345 overflow: hidden;
346 text-overflow: ellipsis;
347 white-space: nowrap;
348}
349
350.media-path {
351 font-size: 0.85rem;
352}
353
354.media-info .media-duration {
355 display: none;
356}
357
358/* Detail view */
359.detail-view {
360 max-width: 900px;
361}
362
363.detail-header {
364 display: flex;
365 justify-content: space-between;
366 align-items: center;
367 margin-bottom: 1.5rem;
368 flex-wrap: wrap;
369 gap: 1rem;
370}
371
372.player-container {
373 background: #000;
374 border-radius: var(--pico-border-radius);
375 overflow: hidden;
376 margin-bottom: 1.5rem;
377 max-height: 80vh;
378 display: flex;
379 align-items: center;
380 justify-content: center;
381}
382
383.player-container video,
384.player-container audio {
385 max-width: 100%;
386 max-height: 80vh;
387 width: auto;
388 height: auto;
389 display: block;
390}
391
392.detail-meta dl {
393 display: grid;
394 grid-template-columns: 150px 1fr;
395 gap: 0.5rem 1rem;
396}
397
398.detail-meta dd {
399 word-break: break-all;
400 margin: 0;
401}
402
403/* Description: preserve the source line breaks; collapse long ones to ~2 lines
404 with a pure-CSS (no-JS, CSP-safe) Show more/less toggle. */
405.detail-meta dd.description {
406 margin: 0;
407}
408
409.description .desc-text {
410 white-space: pre-wrap;
411 word-break: normal;
412 overflow-wrap: anywhere;
413 line-height: 1.5;
414}
415
416/* The checkbox is the toggle state; it's driven by the label, never shown. */
417.description.collapsible .desc-toggle {
418 display: none;
419}
420
421/* Collapsed: clamp to ~2 lines (max-height works with pre-wrap; line-clamp
422 does not), with a fade so the cut reads as intentional. */
423.description.collapsible .desc-text {
424 position: relative;
425 max-height: 3em;
426 overflow: hidden;
427}
428
429.description.collapsible .desc-text::after {
430 content: "";
431 position: absolute;
432 left: 0;
433 right: 0;
434 bottom: 0;
435 height: 1.5em;
436 background: linear-gradient(to bottom, transparent, var(--pico-background-color));
437 pointer-events: none;
438}
439
440.description.collapsible .desc-toggle:checked ~ .desc-text {
441 max-height: none;
442}
443
444.description.collapsible .desc-toggle:checked ~ .desc-text::after {
445 display: none;
446}
447
448.description.collapsible .desc-toggle-label {
449 display: inline-block;
450 margin-top: 0.4rem;
451 color: var(--pico-primary);
452 font-size: 0.85rem;
453 font-weight: 600;
454 cursor: pointer;
455 user-select: none;
456}
457
458.description.collapsible .desc-toggle-label::after {
459 content: "Show more \25be";
460}
461
462.description.collapsible .desc-toggle:checked ~ .desc-toggle-label::after {
463 content: "Show less \25b4";
464}
465
466/* Queue */
467.queue-list {
468 display: flex;
469 flex-direction: column;
470 gap: 1rem;
471}
472
473.queue-item {
474 padding: 1rem;
475 margin-bottom: 0;
476}
477
478.queue-header {
479 display: flex;
480 justify-content: space-between;
481 align-items: flex-start;
482 gap: 1rem;
483 flex-wrap: wrap;
484 margin-bottom: 0.5rem;
485}
486
487.queue-url {
488 flex: 1;
489 min-width: 0;
490}
491
492.queue-url a {
493 word-break: break-all;
494}
495
496.queue-status {
497 display: flex;
498 gap: 0.5rem;
499 align-items: center;
500 flex-wrap: wrap;
501}
502
503.status-badge {
504 padding: 0.2rem 0.6rem;
505 border-radius: 12px;
506 font-size: 0.85rem;
507 font-weight: 500;
508}
509
510.status-badge.queued {
511 background: var(--pico-card-sectioning-background-color);
512 color: var(--pico-muted-color);
513}
514
515.status-badge.downloading {
516 background: var(--pico-primary);
517 color: var(--pico-primary-inverse);
518}
519
520.status-badge.completed {
521 background: var(--pico-ins-color);
522 color: white;
523}
524
525.status-badge.error {
526 background: var(--pico-del-color);
527 color: white;
528}
529
530.error-message {
531 color: var(--pico-del-color);
532 font-size: 0.9rem;
533 margin-top: 0.5rem;
534}
535
536.error-text {
537 color: var(--pico-del-color);
538}
539
540.queue-meta {
541 margin-top: 0.5rem;
542 font-size: 0.85rem;
543 display: flex;
544 gap: 1rem;
545}
546
547/* Logs */
548.logs {
549 max-height: 500px;
550 overflow-y: auto;
551 white-space: pre-wrap;
552 word-break: break-all;
553}
554
555pre {
556 padding: 1.5rem;
557}
558
559/* Settings */
560.settings-layout {
561 display: flex;
562 flex-direction: column;
563 gap: 2rem;
564}
565
566.presets-list {
567 display: flex;
568 flex-direction: column;
569 gap: 1rem;
570 margin-bottom: 1.5rem;
571}
572
573.preset-header {
574 display: flex;
575 justify-content: space-between;
576 align-items: center;
577 margin-bottom: 0.5rem;
578}
579
580.preset-header h4 {
581 display: flex;
582 align-items: center;
583 gap: 0.5rem;
584 margin: 0;
585}
586
587.badge {
588 background: var(--pico-primary);
589 color: var(--pico-primary-inverse);
590 padding: 0.1rem 0.4rem;
591 border-radius: 10px;
592 font-size: 0.75rem;
593}
594
595.preset-actions {
596 display: flex;
597 gap: 0.5rem;
598 flex-wrap: wrap;
599 margin-top: 1rem;
600}
601
602.preset-description {
603 margin-bottom: 0.5rem;
604}
605
606.preset-flags {
607 margin-top: 0.5rem;
608}
609
610.preset-flags pre {
611 overflow-x: auto;
612 margin-top: 0.3rem;
613 margin-bottom: 0;
614}
615
616/* Nested articles need visible borders */
617.presets-list article {
618 border: 1px solid var(--pico-muted-border-color);
619}
620
621/* Suppress Pico details marker for button-like summaries */
622details:has(summary[role="button"]) summary::after {
623 display: none !important;
624}
625
626details summary[role="button"] {
627 display: inline-flex;
628 width: auto;
629}
630
631/* Toggle text: Edit vs Cancel */
632details .cancel-text {
633 display: none;
634}
635
636details[open] .edit-text {
637 display: none;
638}
639
640details[open] .cancel-text {
641 display: inline;
642}
643
644/* Delete overlay popover */
645.delete-overlay {
646 position: relative;
647}
648
649.delete-overlay summary {
650 list-style: none;
651 cursor: pointer;
652}
653
654.delete-overlay summary::-webkit-details-marker {
655 display: none;
656}
657
658.delete-overlay summary::after {
659 display: none !important;
660}
661
662.delete-overlay .overlay-panel {
663 position: absolute;
664 top: calc(100% + 0.5rem);
665 right: 0;
666 z-index: 1000;
667 width: 260px;
668 background: var(--pico-background-color);
669 border: 1px solid var(--pico-muted-border-color);
670 border-radius: var(--pico-border-radius);
671 padding: 1rem;
672 box-shadow: var(--pico-dropdown-box-shadow);
673}
674
675/* Popover arrow */
676.delete-overlay .overlay-panel::before {
677 content: '';
678 position: absolute;
679 top: -6px;
680 right: 1rem;
681 width: 10px;
682 height: 10px;
683 background: var(--pico-background-color);
684 border-left: 1px solid var(--pico-muted-border-color);
685 border-top: 1px solid var(--pico-muted-border-color);
686 transform: rotate(45deg);
687}
688
689.delete-overlay .overlay-panel p {
690 margin-bottom: 1rem;
691 font-size: 0.9rem;
692}
693
694.delete-overlay .overlay-panel .form-actions {
695 margin-top: 0;
696 justify-content: flex-end;
697}
698
699/* Inline edit form */
700.edit-form {
701 padding: 1rem;
702}
703
704/* Conditional sections (no-JS show/hide) */
705.conditional-section {
706 margin-bottom: 1rem;
707}
708
709.conditional-section .conditional-content {
710 display: none;
711 margin-top: 1rem;
712}
713
714/* Checkbox triggers */
715.conditional-section:has(input[type="checkbox"].conditional-trigger:checked) .conditional-content {
716 display: block;
717}
718
719/* Radio triggers for format mode */
720.conditional-section:has(.radio-group input[value="preset"]:checked) .conditional-content.format-preset,
721.conditional-section:has(.radio-group input[value="custom"]:checked) .conditional-content.format-custom {
722 display: block;
723}
724
725/* Format mode selector - hidden radio inputs with button-like labels */
726.radio-group.format-mode {
727 display: flex;
728 gap: 0.5rem;
729 margin-top: 0.5rem;
730 flex-wrap: wrap;
731}
732
733.radio-group.format-mode input[type="radio"] {
734 position: absolute;
735 opacity: 0;
736 width: 0;
737 height: 0;
738}
739
740.radio-group.format-mode label {
741 padding: 0.4rem 0.8rem;
742 border: 1px solid var(--pico-muted-border-color);
743 border-radius: var(--pico-border-radius);
744 cursor: pointer;
745 background: var(--pico-background-color);
746 transition: all 0.2s;
747 font-weight: normal;
748}
749
750.radio-group.format-mode label:hover {
751 background: var(--pico-card-sectioning-background-color);
752}
753
754.radio-group.format-mode label:has(input:checked) {
755 background: var(--pico-primary);
756 color: var(--pico-primary-inverse);
757 border-color: var(--pico-primary);
758}
759
760/* Theme selector */
761.theme-selector {
762 display: flex;
763 gap: 0.5rem;
764}
765
766.theme-selector button {
767 min-width: 80px;
768}
769
770.theme-selector button.active {
771 background: var(--pico-primary);
772 color: var(--pico-primary-inverse);
773 border-color: var(--pico-primary);
774}
775
776/* Download form */
777.download-form {
778 max-width: 900px;
779}
780
781.download-form form {
782 max-width: 800px;
783}
784
785/* Filter form inline */
786.filter-form {
787 display: flex;
788 gap: 0.5rem;
789 align-items: center;
790}
791
792.filter-form input,
793.filter-form select,
794.filter-form button {
795 margin-bottom: 0;
796 width: auto;
797 display: inline-block;
798 flex-shrink: 0;
799}
800
801.filter-form input[type="text"] {
802 min-width: 200px;
803}
804
805/* Empty state */
806.empty-state {
807 text-align: center;
808 padding: 3rem;
809}
810
811/* Utility */
812.inline {
813 display: inline;
814}
815
816/* Responsive */
817@media (max-width: 768px) {
818 main {
819 padding: 1rem;
820 }
821
822 .page-header {
823 flex-direction: column;
824 align-items: flex-start;
825 }
826
827 .library-grid {
828 grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
829 }
830
831 .form-row {
832 grid-template-columns: 1fr;
833 }
834
835 .detail-meta dl {
836 grid-template-columns: 1fr;
837 }
838
839 .queue-header {
840 flex-direction: column;
841 }
842
843 header {
844 flex-direction: column;
845 gap: 1rem;
846 padding: 1rem;
847 }
848
849 nav {
850 width: 100%;
851 justify-content: center;
852 }
853}
854