diff --git a/media_server/static/css/styles.css b/media_server/static/css/styles.css index 968f872..fa479d7 100644 --- a/media_server/static/css/styles.css +++ b/media_server/static/css/styles.css @@ -4292,7 +4292,7 @@ header > div:first-child { } /* ═══════════════════════════════════════════════════════════════ - PLAYER VIEW — magazine spread + PLAYER VIEW — magazine spread with vinyl stage ═══════════════════════════════════════════════════════════════ */ .player-container { background: transparent; @@ -4300,71 +4300,226 @@ header > div:first-child { padding: 0; box-shadow: none; margin-top: 12px; + position: relative; } -.player-layout { +/* Folio marks at top corners of the player container */ +.player-container > .folio { + position: absolute; + top: -42px; + font-family: var(--mono); + font-size: 10px; + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--ink-faint); + z-index: 1; +} +.player-container > .folio.tl { left: 0; } +.player-container > .folio.tr { right: 0; } + +.player-layout, +.now-playing { display: grid; - grid-template-columns: minmax(280px, 480px) 1fr; + grid-template-columns: minmax(280px, 460px) 1fr; gap: 64px; align-items: start; + margin-top: 28px; } @media (max-width: 980px) { - .player-layout { + .player-layout, + .now-playing { grid-template-columns: 1fr; gap: 36px; } } -.album-art-container { +/* ─── Vinyl stage ──────────────────────────────────────────── */ +.album-art-container.vinyl-stage { position: relative; aspect-ratio: 1; width: 100%; max-width: none; - padding: 14px; - background: var(--bg-card); - border: 1px solid var(--rule-strong); + padding: 0; + background: transparent; + border: 0; + box-shadow: none; + display: flex; + align-items: center; + justify-content: center; + overflow: visible; +} + +.album-art-container.vinyl-stage:hover { transform: none; } + +.vinyl-stage .vinyl { + position: relative; + width: 86%; + aspect-ratio: 1; + border-radius: 50%; + background: + radial-gradient(circle at 50% 50%, + #0a0907 0%, + #0a0907 18%, + #1a1611 18.3%, + #0a0907 18.6%, + #14110c 22%, + #0a0907 22.3%, + #14110c 26%, + #0a0907 26.3%, + #14110c 30%, + #0a0907 30.3%, + #14110c 34%, + #0a0907 34.3%, + #14110c 38%, + #0a0907 38.3%, + #14110c 42%, + #0a0907 42.3%, + #14110c 46%, + #0a0907 46.3%, + #1c1812 47%, + #0a0907 100%); box-shadow: - 0 1px 0 var(--bg-paper), - 0 28px 60px -20px rgba(0, 0, 0, 0.5), - 0 8px 20px -8px rgba(0, 0, 0, 0.35); - transition: transform 400ms var(--ease); + inset 0 0 60px rgba(0,0,0,0.7), + 0 30px 80px rgba(0,0,0,0.6), + 0 6px 20px rgba(0,0,0,0.5); + animation: sr-vinyl-spin 14s linear infinite; + animation-play-state: paused; } -.album-art-container:hover { transform: translateY(-2px); } - -#album-art-glow { - border-radius: 0; - filter: blur(40px) saturate(1.4); - opacity: 0.5; - inset: 14px; - width: auto; - height: auto; +:root[data-playstate="playing"] .vinyl-stage .vinyl { + animation-play-state: running; } -#album-art { - border-radius: 0; +.vinyl-stage .vinyl::before { + content: ""; + position: absolute; + inset: 12%; + border-radius: 50%; + background: + conic-gradient(from 0deg, + rgba(255,255,255,0.04) 0deg, + transparent 30deg, + rgba(255,255,255,0.06) 90deg, + transparent 150deg, + rgba(255,255,255,0.03) 210deg, + transparent 270deg, + rgba(255,255,255,0.05) 330deg, + transparent 360deg); + mix-blend-mode: screen; + pointer-events: none; +} + +.vinyl-stage .vinyl-label { + position: absolute; + inset: 28%; + border-radius: 50%; + overflow: hidden; + box-shadow: + inset 0 0 24px rgba(0,0,0,0.4), + 0 0 0 4px var(--bg-deep), + 0 0 0 5px var(--copper-lo); + background: var(--bg-card); + z-index: 1; +} + +.vinyl-stage .vinyl-label::after { + /* Spindle hole */ + content: ""; + position: absolute; + width: 8%; height: 8%; + top: 46%; left: 46%; + border-radius: 50%; + background: var(--bg-deep); + box-shadow: inset 0 1px 2px rgba(255,255,255,0.1); + z-index: 3; +} + +.vinyl-stage #album-art-glow { + position: absolute; + inset: -8%; + width: 116%; + height: 116%; + border-radius: 50%; + filter: blur(20px) saturate(1.4); + opacity: 0.55; + z-index: 0; + object-fit: cover; +} + +.vinyl-stage #album-art { + position: relative; width: 100%; height: 100%; object-fit: cover; display: block; + border-radius: 50%; + z-index: 2; } -.spectrogram-canvas { - bottom: 14px; - left: 14px; - right: 14px; - width: auto; +/* ─── Tonearm SVG ──────────────────────────────────────────── */ +.vinyl-stage .tonearm { + position: absolute; + top: -6%; + right: -4%; + width: 56%; + height: 56%; + pointer-events: none; + transform-origin: 88% 12%; + transform: rotate(-22deg); + transition: transform 1s var(--ease); + z-index: 3; + filter: drop-shadow(0 4px 12px rgba(0,0,0,0.5)); +} + +:root[data-playstate="playing"] .vinyl-stage .tonearm { + transform: rotate(0deg); +} + +@keyframes sr-vinyl-spin { to { transform: rotate(360deg); } } + +.vinyl-stage .spectrogram-canvas { + position: absolute; + bottom: -52px; + left: 0; + right: 0; + width: 100%; + height: 44px; border-radius: 0; - height: 56px; + opacity: 0.7; } /* ─── Player details (right column / masthead) ──────────────── */ -.player-details { +.player-details, +.track-masthead { gap: 0; - padding-top: 12px; + padding-top: 0; + display: flex; + flex-direction: column; } +/* Kicker — copper italic mono with hairlines */ +.track-masthead > .kicker { + font-family: var(--mono); + font-size: 10px; + letter-spacing: 0.32em; + text-transform: uppercase; + color: var(--copper); + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 22px; +} +.track-masthead > .kicker::before, +.track-masthead > .kicker::after { + content: ""; + height: 1px; + background: var(--copper); + opacity: 0.6; + flex: 0 0 24px; +} +.track-masthead > .kicker::after { flex: 1 0 auto; } + .track-info { margin-bottom: 0; position: relative; @@ -4399,27 +4554,225 @@ header > div:first-child { margin-bottom: 0; } -.playback-state { - margin-top: 22px; - padding: 12px 0; +/* Editorial 4-cell metadata grid (now houses State / Source / Elapsed / Length) */ +.meta-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + margin-top: 32px; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); +} +.meta-cell { + padding: 16px 18px 16px 0; + border-right: 1px solid var(--rule); + min-width: 0; +} +.meta-cell:last-child { border-right: 0; padding-right: 0; } +.meta-cell .meta-label { font-family: var(--mono); - font-size: 11px; + font-size: 9px; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--ink-faint); + margin-bottom: 8px; +} +.meta-cell .meta-value { + font-family: var(--serif); + font-style: italic; + font-weight: 400; + font-size: 18px; + color: var(--ink); + font-variation-settings: 'opsz' 30; + display: flex; + align-items: center; + gap: 8px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.meta-cell .meta-value.mono { + font-family: var(--mono); + font-style: normal; + font-size: 15px; + color: var(--ink); + font-variant-numeric: tabular-nums; + letter-spacing: 0.02em; +} +.meta-cell .meta-value .state-icon, +.meta-cell .meta-value .source-icon { + width: 14px; + height: 14px; + flex-shrink: 0; + color: var(--copper); +} +.meta-cell .source-value { font-size: 16px; } + +@media (max-width: 720px) { + .meta-grid { grid-template-columns: repeat(2, 1fr); } + .meta-cell:nth-child(2) { border-right: 0; } + .meta-cell:nth-child(-n+2) { border-bottom: 1px solid var(--rule); } +} + +/* Hide the legacy .playback-state container (its data is now in meta-grid) */ +.track-info > .playback-state { display: none; } + +/* Spectrum decorative bars */ +.spectrum { + display: flex; + align-items: flex-end; + gap: 3px; + height: 38px; + margin-top: 28px; + margin-bottom: 8px; +} +.spectrum span { + flex: 1; + background: linear-gradient(to top, var(--copper-lo), var(--copper-hi)); + opacity: 0.85; + border-radius: 99px 99px 0 0; + transform-origin: bottom; + animation: sr-spectrum 1.1s ease-in-out infinite; + animation-play-state: paused; +} +:root[data-playstate="playing"] .spectrum span { animation-play-state: running; } + +.spectrum span:nth-child(1) { animation-delay: -0.10s; height: 30%; } +.spectrum span:nth-child(2) { animation-delay: -0.45s; height: 60%; } +.spectrum span:nth-child(3) { animation-delay: -0.20s; height: 80%; } +.spectrum span:nth-child(4) { animation-delay: -0.55s; height: 50%; } +.spectrum span:nth-child(5) { animation-delay: -0.30s; height: 95%; } +.spectrum span:nth-child(6) { animation-delay: -0.05s; height: 70%; } +.spectrum span:nth-child(7) { animation-delay: -0.65s; height: 40%; } +.spectrum span:nth-child(8) { animation-delay: -0.25s; height: 85%; } +.spectrum span:nth-child(9) { animation-delay: -0.40s; height: 55%; } +.spectrum span:nth-child(10) { animation-delay: -0.10s; height: 75%; } +.spectrum span:nth-child(11) { animation-delay: -0.50s; height: 35%; } +.spectrum span:nth-child(12) { animation-delay: -0.15s; height: 90%; } +.spectrum span:nth-child(13) { animation-delay: -0.60s; height: 45%; } +.spectrum span:nth-child(14) { animation-delay: -0.30s; height: 65%; } +.spectrum span:nth-child(15) { animation-delay: -0.45s; height: 85%; } +.spectrum span:nth-child(16) { animation-delay: -0.20s; height: 55%; } +.spectrum span:nth-child(17) { animation-delay: -0.55s; height: 70%; } +.spectrum span:nth-child(18) { animation-delay: -0.10s; height: 30%; } +.spectrum span:nth-child(19) { animation-delay: -0.40s; height: 80%; } +.spectrum span:nth-child(20) { animation-delay: -0.25s; height: 60%; } +.spectrum span:nth-child(21) { animation-delay: -0.50s; height: 90%; } +.spectrum span:nth-child(22) { animation-delay: -0.15s; height: 50%; } +.spectrum span:nth-child(23) { animation-delay: -0.60s; height: 70%; } +.spectrum span:nth-child(24) { animation-delay: -0.30s; height: 40%; } +.spectrum span:nth-child(25) { animation-delay: -0.45s; height: 85%; } +.spectrum span:nth-child(26) { animation-delay: -0.20s; height: 55%; } +.spectrum span:nth-child(27) { animation-delay: -0.55s; height: 75%; } +.spectrum span:nth-child(28) { animation-delay: -0.10s; height: 35%; } +.spectrum span:nth-child(29) { animation-delay: -0.40s; height: 65%; } +.spectrum span:nth-child(30) { animation-delay: -0.25s; height: 95%; } + +@keyframes sr-spectrum { + 0%, 100% { transform: scaleY(0.4); } + 50% { transform: scaleY(1); } +} + +/* Transport — wraps progress + controls */ +.transport { + margin-top: 8px; +} + +.progress-row { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 18px; + align-items: center; + margin-bottom: 26px; +} +.progress-row .timecode { + font-family: var(--mono); + font-size: 12px; + color: var(--ink-mute); + letter-spacing: 0.06em; + font-variant-numeric: tabular-nums; +} +.progress-row .timecode.elapsed { color: var(--copper); font-weight: 500; } + +/* Override the legacy .progress-container layout when inside .transport */ +.transport .progress-container { + margin-top: 0; +} + +/* VU cluster (replaces freestanding volume container) */ +.vu-cluster { + margin-left: auto; + display: flex; + align-items: center; + gap: 14px; +} + +.vu-meter { + position: relative; + width: 130px; + height: 56px; + background: linear-gradient(180deg, #1a1610 0%, #0e0c08 100%); + border: 1px solid var(--rule-strong); + border-radius: 4px 4px 0 0; + overflow: hidden; + box-shadow: inset 0 2px 6px rgba(0,0,0,0.5), inset 0 0 30px rgba(224,128,56,0.08); +} +.vu-meter::before { + content: ""; + position: absolute; + inset: 0; + background: repeating-conic-gradient(from 195deg at 50% 100%, + transparent 0deg 4deg, + rgba(242, 235, 220, 0.08) 4deg 5deg, + transparent 5deg 9deg); +} +.vu-meter::after { + content: "VU"; + position: absolute; + bottom: 4px; left: 50%; + transform: translateX(-50%); + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.3em; + color: var(--ink-faint); +} +.vu-needle { + position: absolute; + bottom: 0; left: 50%; + width: 1.5px; + height: 88%; + background: linear-gradient(to top, var(--copper) 0%, var(--copper-hi) 70%, var(--ink) 100%); + transform-origin: bottom center; + transform: rotate(-22deg); + transition: transform 350ms var(--ease); + box-shadow: 0 0 8px var(--copper-glow); +} + +/* Volume container nested inside vu-cluster — compact stacked controls */ +.vu-cluster .volume-container { + margin-top: 0; + padding-top: 0; + border-top: 0; + flex-direction: column; + gap: 8px; + align-items: stretch; + min-width: 140px; +} +.vu-cluster .volume-container > .mute-btn { + align-self: flex-start; +} +.vu-cluster .volume-container > #volume-slider { + width: 100%; +} +.vu-cluster .volume-container > .volume-display { + text-align: right; + font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-mute); - gap: 10px; } - -.state-icon { - width: 14px; - height: 14px; - color: var(--copper); -} - -#playback-state { - color: var(--ink-soft); +.vu-cluster .volume-container > .volume-display::before { + content: "VOL · "; + color: var(--ink-faint); } /* ─── Progress (hairline editorial) ─────────────────────────── */ diff --git a/media_server/static/index.html b/media_server/static/index.html index a1bd75b..85984cb 100644 --- a/media_server/static/index.html +++ b/media_server/static/index.html @@ -153,66 +153,138 @@
-
-
- - Album Art + Now Spinning · v— + Vol. I — Studio Reference + +
+ + +
+
+
+ + Album Art +
+
+
-
+ +
+ +
Now Playing
+
No media playing
-
- - - - Idle +
+ + +
+
+
State
+
+ + + + Idle +
+
+
+
Source
+
+ + Unknown +
+
+
+
Elapsed
+
0:00
+
+
+
Length
+
0:00
-
-
- 0:00 - 0:00 + + + + +
+
+ 0:00 +
+
+
+ 0:00
-
-
+ +
+ + + + + +
+ +
+ + +
50%
+
+
-
- - - -
- -
- - -
50%
-
- +
- Unknown + + Modes +
+

Studio Reference

+
    +
  • Editorial hi-fi mockup
  • +
  • Fraunces · Geist · Geist Mono
  • +
  • Copper-on-charcoal · grain
  • +
  • Asymmetric magazine grid
  • +
  • Vinyl + tonearm + VU meter
  • +
  • Hover over cards & buttons
  • +
+ + +
+ + + № 0008 · v0.2.0 + Vol. I — APR · MMXXVI + + +
+
+ + Connected · Local 8765 +
+
+ Media Server + Studio Reference Edition +
+
+ + + + + + + +
+
+ + + + + +
+ + +
+
+
+
+ Kind of
Blue +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
Now Spinning · Track 03 of 05 · Spotify
+ +

So What — Take One

+ +
Kind of Blue · Columbia · 1959 · Remastered
+ + +
+
+
Bitrate
+
320 kbps
+
+
+
Format
+
FLAC · 24/96
+
+
+
Output
+
Studio Mon.
+
+
+
Genre
+
Modal Jazz
+
+
+ + +
+ + + + + + +
+ + +
+ +
+ 03:42 +
+
+
+ 09:22 +
+ +
+ + + + +
+
+
+
+
+ OUT −6 dB + VOL 72% +
+
+
+
+
+
+ + +
+
§ 03The Library
+
+
14 folders · 2,148 items
+
+ +
+
+ Music + / + Jazz + / + Miles Davis +
+
+ + + + +
+ +
+
+
+ A1 +
+
+
+
Kind of Blue
+
1959 · 5 tracks
+
+
+
+
+ A2 +
+
+
+
Sketches of Spain
+
1960 · 5 tracks
+
+
+
+
+ A3 +
+
+
+
Bitches Brew
+
1970 · 7 tracks
+
+
+
+
+ A4 +
+
+
+
In a Silent Way
+
1969 · 4 tracks
+
+
+
+
+ A5 +
+
+
+
Birth of the Cool
+
1957 · 11 tracks
+
+
+
+
+ A6 +
+
+
+
Porgy and Bess
+
1959 · 13 tracks
+
+
+
+
+ A7 +
+
+
+
Milestones
+
1958 · 7 tracks
+
+
+
+
+ A8 +
+
+
+
'Round About Midnight
+
1957 · 6 tracks
+
+
+
+ + +
+
§ 04Quick Access
+
+
Scripts · Shortcuts · Links
+
+ +
+ + + + + + + + +
+ + +
+
§ 05Settings
+
+
System · Audio · Folders · Callbacks
+
+ +
+ +
+ 5.01 +

Audio Output

+

Loopback device captured by the visualizer & VU meter. Auto-detection picks the system default.

+
+ Device + Realtek HD +
+
+ Status + CONNECTED +
+
+ Latency + 12 ms +
+ +
+ +
+ 5.02 +

Media Folders

+

Library roots scanned for browsing. Network shares show availability status.

+
+ Music + D:\Music +
+
+ Movies + \\NAS\Films +
+
+ Podcasts + UNREACHABLE +
+ +
+ +
+ 5.03 +

Scripts & Hooks

+

Custom commands that run on demand or trigger on playback events.

+
+ Scripts + 7 active +
+
+ Callbacks + 3 wired +
+
+ Last run + OK · 14:22 +
+ +
+ +
+ 5.04 +

System Health

+

Server diagnostics, websocket state, and update channel.

+
+ Version + v0.2.0 +
+
+ Uptime + 04 d · 11 h +
+
+ Update + UP TO DATE +
+ +
+ +
+ + + + +
+ + +
+
+
+
+
So What — Take One
+
Miles Davis · Kind of Blue
+
+
+
+ + + +
+
+ 03:42 / 09:22 +
+ +
+
+
+
+ + + +