forked from
devins.page/tinysub
a simple web player for subsonic
1/* LAYOUT */
2body {
3 height: 100vh;
4 display: grid;
5 grid-template-columns: 20rem 1fr;
6 grid-template-rows: 1fr auto auto;
7 grid-template-areas:
8 "sidebar main"
9 "header header"
10 "footer footer";
11}
12
13@supports (height: 100dvh) {
14 body {
15 height: 100dvh;
16 }
17}
18
19/* HEADER */
20header {
21 grid-area: header;
22 display: flex;
23 align-items: center;
24 justify-content: center;
25 gap: 0.5rem;
26 padding: 0.5rem;
27 border-top: 1px solid var(--border);
28 background: var(--bg-tertiary);
29}
30
31#progress {
32 flex: 1;
33}
34
35#playback #loop-btn {
36 opacity: 0.5;
37}
38
39#playback #loop-btn.active {
40 opacity: 1;
41}
42
43/* BASE STYLES */
44button {
45 background: none;
46 border: none;
47 display: inline-flex;
48 align-items: center;
49 gap: 0.5rem;
50 color: inherit;
51 font: inherit;
52}
53
54@keyframes pulse {
55 0%,
56 100% {
57 background-color: var(--playing);
58 }
59 50% {
60 background-color: var(--playing-pulse);
61 }
62}
63
64/* SIDEBAR */
65#sidebar {
66 grid-area: sidebar;
67 border-right: 1px solid var(--border);
68 background: var(--bg-secondary);
69 display: flex;
70 flex-direction: column;
71 min-height: 0;
72}
73
74#sidebar h2 {
75 font-size: 1rem;
76 margin-block-start: 0.5rem;
77}
78
79#sidebar h2 a {
80 color: inherit;
81 text-decoration: none;
82}
83
84#sidebar ul {
85 list-style: none;
86}
87
88#sidebar li {
89 margin-block-start: 0.5rem;
90}
91
92/* sidebar - library */
93#sidebar #library {
94 flex: 1;
95 overflow-y: auto;
96 min-height: 0;
97 padding-inline: 1rem;
98}
99
100/* sidebar - library - tree items */
101#sidebar #library .tree-item {
102 display: flex;
103 align-items: center;
104 gap: 0.5rem;
105}
106
107#sidebar #library .tree-toggle,
108#sidebar #library .tree-name {
109 color: inherit;
110 text-decoration: none;
111 flex: 1;
112 display: flex;
113 gap: 0.5rem;
114 align-items: center;
115 min-width: 0;
116}
117
118#sidebar #library .tree-toggle span,
119#sidebar #library .tree-name span {
120 overflow: hidden;
121 text-overflow: ellipsis;
122 white-space: nowrap;
123}
124
125#sidebar #library .tree-action.disabled {
126 opacity: 0.5;
127}
128
129#sidebar #library .tree-toggle img,
130#sidebar #library .tree-name img {
131 width: var(--art-artist);
132 height: var(--art-artist);
133 aspect-ratio: 1 / 1;
134 object-fit: cover;
135 flex-shrink: 0;
136}
137
138#sidebar #library ul.nested > li .tree-toggle img,
139#sidebar #library ul.nested > li .tree-name img {
140 width: var(--art-album);
141 height: var(--art-album);
142}
143
144#sidebar #library .nested,
145#sidebar #library .nested-songs {
146 list-style: none;
147 margin-inline-start: 1rem;
148}
149
150#sidebar #library .nested li {
151 margin-block-start: 0.25rem;
152}
153
154#sidebar #library #library-search {
155 width: 100%;
156 margin-block-start: 0.5rem;
157}
158
159#sidebar #library .tree-toggle.focused,
160#sidebar #library .tree-name.focused,
161#sidebar #library .section-toggle.focused {
162 background: Highlight;
163 color: HighlightText;
164}
165
166#sidebar #library:not(:focus-within) .tree-toggle.focused,
167#sidebar #library:not(:focus-within) .tree-name.focused,
168#sidebar #library:not(:focus-within) .section-toggle.focused {
169 background: GrayText;
170}
171
172/* sidebar - now playing */
173#sidebar #now-playing {
174 display: flex;
175 flex-direction: column;
176 align-items: center;
177 gap: 0.75rem;
178 padding: 1rem;
179 border-top: 1px solid var(--border-subtle);
180 flex-shrink: 0;
181}
182
183#sidebar #now-playing #cover-art {
184 width: var(--art-now-playing);
185 height: var(--art-now-playing);
186 object-fit: cover;
187}
188
189#sidebar #now-playing #track-info {
190 text-align: center;
191 width: 100%;
192}
193
194#sidebar #now-playing #track-title {
195 font-weight: bold;
196 overflow: hidden;
197 text-overflow: ellipsis;
198 white-space: nowrap;
199}
200
201#sidebar #now-playing #track-artist {
202 font-size: 0.8rem;
203 opacity: 0.75;
204 overflow: hidden;
205 text-overflow: ellipsis;
206 white-space: nowrap;
207}
208
209#sidebar #now-playing #track-lyric {
210 font-size: 0.8rem;
211 opacity: 0.75;
212}
213
214/* QUEUE */
215#queue {
216 grid-area: main;
217 overflow-y: auto;
218 padding-inline: 1rem;
219}
220
221#queue #queue-table {
222 width: 100%;
223 border-collapse: collapse;
224 table-layout: fixed;
225 margin-block-start: 0.5rem;
226}
227
228#queue #queue-table th,
229#queue #queue-table td {
230 text-align: left;
231 overflow: hidden;
232 text-overflow: ellipsis;
233 white-space: nowrap;
234}
235
236/* queue - cover art column */
237#queue #queue-table th:nth-child(1),
238#queue #queue-table td:nth-child(1) {
239 width: calc(var(--art-song) * 1.5);
240}
241
242/* queue - duration column */
243#queue #queue-table th:nth-child(5),
244#queue #queue-table td:nth-child(5) {
245 width: 6rem;
246}
247
248/* queue - actions column */
249#queue #queue-table th:nth-child(6),
250#queue #queue-table td:nth-child(6) {
251 text-align: right;
252 overflow: visible;
253 white-space: normal;
254}
255
256#queue #queue-table td:nth-child(6) button {
257 margin-inline-start: 0.5rem;
258}
259
260/* queue - rows */
261#queue #queue-table tbody tr.stripe {
262 background: var(--bg-secondary);
263}
264
265html:not(.no-animations) #queue #queue-table tbody tr.currently-playing {
266 animation: pulse 4s linear infinite;
267}
268
269#queue #queue-table tbody tr.currently-playing {
270 background: var(--playing);
271}
272
273#queue #queue-table tbody tr.dragging {
274 opacity: 0.5;
275}
276
277#queue #queue-table tbody tr.selected {
278 background: Highlight;
279 color: HighlightText;
280}
281
282#queue:not(:focus-within) #queue-table tbody tr.selected {
283 background: GrayText;
284}
285
286#queue #queue-table tbody tr.drag-over-above {
287 border-block-start: 2px solid currentColor;
288}
289
290#queue #queue-table tbody tr.drag-over-below {
291 border-block-end: 2px solid currentColor;
292}
293
294/* queue - row items */
295#queue #queue-table .queue-cover {
296 width: var(--art-song);
297 height: var(--art-song);
298 aspect-ratio: 1 / 1;
299 object-fit: cover;
300 flex-shrink: 0;
301}
302
303#queue #queue-table .queue-favorite {
304 opacity: 0.25;
305}
306
307#queue #queue-table .queue-favorite:hover {
308 opacity: 0.5;
309}
310
311#queue #queue-table .queue-favorite.favorited {
312 opacity: 1;
313}
314
315#queue #queue-table .queue-rating-star {
316 opacity: 0.25;
317}
318
319#queue #queue-table .queue-rating-star:hover {
320 opacity: 0.5;
321}
322
323#queue #queue-table .queue-rating-star.rated {
324 opacity: 1;
325}
326
327#queue #queue-table .queue-play,
328#queue #queue-table .queue-play-next,
329#queue #queue-table .queue-move-up,
330#queue #queue-table .queue-move-down {
331 display: none;
332}
333
334/* FOOTER */
335#footer {
336 grid-area: footer;
337 display: flex;
338 align-items: center;
339 justify-content: space-between;
340 gap: 1rem;
341 padding: 0.5rem 1rem;
342 border-top: 1px solid var(--border-subtle);
343 background: var(--bg-tertiary);
344}
345
346#footer #actions {
347 display: flex;
348 align-items: center;
349 gap: 1rem;
350}
351
352/* CONTEXT MENU */
353#context-menu {
354 position: fixed;
355 background: Menu;
356 border: 1px solid var(--border);
357 border-radius: 0.25rem;
358 box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
359 z-index: 1000;
360 min-width: 10rem;
361}
362
363html:not(.no-animations) #context-menu {
364 background: var(--bg-context-menu);
365 backdrop-filter: blur(16px);
366}
367
368#context-menu .context-menu-item {
369 display: block;
370 width: 100%;
371 padding: 0.25rem 1rem;
372 background: none;
373 border: none;
374 text-align: left;
375}
376
377#context-menu .context-menu-item:hover {
378 background: Highlight;
379 color: HighlightText;
380}
381
382#context-menu .context-menu-item.focused {
383 background: Highlight;
384 color: HighlightText;
385}
386
387/* MODAL */
388.modal {
389 position: fixed;
390 inset: 0;
391 background: rgba(0, 0, 0, 0.75);
392 display: flex;
393 align-items: center;
394 justify-content: center;
395 z-index: 2000;
396}
397
398.modal.hidden {
399 display: none;
400}
401
402.modal-content {
403 background: Menu;
404 border: 1px solid var(--border);
405 padding: 1rem;
406 max-height: 100%;
407 min-width: 24rem;
408 overflow-y: auto;
409 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
410 display: flex;
411 flex-direction: column;
412 gap: 1rem;
413}
414
415.modal-actions {
416 display: flex;
417 gap: 0.5rem;
418 justify-content: flex-end;
419}
420
421.modal-actions button {
422 background: ButtonFace;
423 padding: 0.5rem 1rem;
424}
425
426#keyboard-help-modal .shortcuts-grid {
427 display: grid;
428 grid-template-columns: 1fr 1fr;
429 gap: 1rem 2rem;
430}
431
432#keyboard-help-modal .shortcut-section-group {
433 display: flex;
434 flex-direction: column;
435 gap: 0.5rem;
436}
437
438#keyboard-help-modal .shortcuts-items-grid {
439 display: grid;
440 grid-template-columns: 10rem 1fr;
441 gap: 0.5rem 1rem;
442}
443
444#keyboard-help-modal kbd {
445 background: ButtonFace;
446 padding: 0.25rem 0.5rem;
447 font-family: ui-monospace, monospace;
448}
449
450/* form groups */
451.form-group {
452 display: flex;
453 flex-direction: column;
454 gap: 1rem;
455}
456
457.modal-content form {
458 display: flex;
459 flex-direction: column;
460 gap: 1rem;
461}
462
463/* UTILITIES */
464/* danger */
465.danger {
466 color: var(--error-color);
467}
468
469button.danger {
470 background-color: var(--error-color);
471 color: white;
472}
473
474/* famfamfam-silk icons - force pixelated rendering for retina displays */
475img[src*="famfamfam-silk"] {
476 image-rendering: pixelated;
477}
478
479/* MEDIA QUERIES */
480@media (max-width: 768px) {
481 /* layout */
482 body {
483 grid-template-columns: 1fr;
484 grid-template-rows: 1fr 1fr auto auto;
485 grid-template-areas:
486 "main"
487 "sidebar"
488 "header"
489 "footer";
490 }
491
492 /* queue - hide album and duration columns */
493 #queue-table th:nth-child(4),
494 #queue-table th:nth-child(5),
495 #queue-table td:nth-child(4),
496 #queue-table td:nth-child(5) {
497 display: none;
498 }
499
500 #sidebar {
501 border-right: none;
502 border-top: 1px solid var(--border);
503 overflow: hidden;
504 }
505
506 /* sidebar - now playing cover art */
507 #now-playing #cover-art {
508 display: none;
509 }
510
511 /* footer - button labels */
512 footer button span {
513 display: none;
514 }
515}
516
517@media (pointer: coarse) {
518 /* queue - show action buttons */
519 #queue #queue-table .queue-play,
520 #queue #queue-table .queue-play-next,
521 #queue #queue-table .queue-move-up,
522 #queue #queue-table .queue-move-down {
523 display: inline-block;
524 }
525
526 /* queue - hide artist column */
527 #queue-table th:nth-child(3),
528 #queue-table td:nth-child(3) {
529 display: none;
530 }
531}