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