a simple web player for subsonic
at main 531 lines 9.4 kB view raw
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}