Files
media-player-server/media_server/static/redesign-mockup.html
T
alexei.dolgolyov 14e9f2294e feat(ui): rebuild player view to match Studio Reference mockup
Restructures the player tab DOM to actually look like the editorial
mockup, not just inherit new fonts. The previous commit only swapped
tokens & typography on the legacy Spotify-clone layout.

DOM additions (all preserve existing JS-touched IDs):
- Vinyl stage: rotating vinyl wrapping the existing #album-art as a
  circular center label; spins only when state=playing via CSS hook
- SVG tonearm: pivots in/out based on data-playstate
- Kicker line: copper italic mono header above the track title
- Editorial 4-cell metadata grid: State / Source / Elapsed / Length
- Decorative spectrum bars (30, CSS-only animation, paused when idle)
- VU meter cluster: needle visual driven by volume %, alongside the
  preserved volume slider for a11y
- Folio marks: top-left and top-right of the player container

JS hooks (small, additive):
- updatePlaybackState now sets :root[data-playstate] for CSS
- progress tick mirrors timecode into meta-grid cells
- volume update rotates the VU needle
- folio-version mirrors the version label

i18n:
- new keys: player.kicker, player.modes, player.folio_*, meta.*
- added to both en.json and ru.json

Restored: media_server/static/redesign-mockup.html (Studio Reference
visual reference; deleting it in the prior commit was a mistake).
2026-04-25 01:24:11 +03:00

1672 lines
52 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Media Server — Studio Reference (Redesign Mockup)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Geist:wght@300..700&family=Geist+Mono:wght@300..600&display=swap" rel="stylesheet">
<style>
/* ────────────────────────────────────────────────────────────
STUDIO REFERENCE — Editorial hi-fi mockup
Palette: warm charcoal · paper cream · copper accent
──────────────────────────────────────────────────────────── */
:root {
--bg-deep: #0E0D0B;
--bg-paper: #18150F;
--bg-card: #1F1B14;
--bg-card-2: #26211A;
--bg-rule: #2E2820;
--ink: #F2EBDC;
--ink-soft: #D6CDB9;
--ink-mute: #9C937F;
--ink-faint: #5C5447;
--ink-ghost: #3A3528;
--copper: #E08038;
--copper-hi: #F4A064;
--copper-lo: #B0561F;
--copper-glow: rgba(224, 128, 56, 0.35);
--amber: #F5C26B;
--jade: #7AB294;
--rust: #C2553F;
--rule: rgba(242, 235, 220, 0.08);
--rule-strong:rgba(242, 235, 220, 0.18);
--serif: 'Fraunces', 'Iowan Old Style', 'Times New Roman', serif;
--sans: 'Geist', 'Helvetica Neue', system-ui, sans-serif;
--mono: 'Geist Mono', 'JetBrains Mono', ui-monospace, monospace;
--ease: cubic-bezier(.2, .7, .2, 1);
--ease-out: cubic-bezier(.16, 1, .3, 1);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg-deep);
color: var(--ink);
font-family: var(--sans);
font-feature-settings: 'ss01', 'cv11';
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
overflow-x: hidden;
}
/* Film-grain overlay — adds analog warmth */
body::before {
content: "";
position: fixed; inset: 0;
pointer-events: none;
z-index: 9999;
opacity: 0.06;
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.95 0 0 0 0 0.92 0 0 0 0 0.86 0 0 0 0.7 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}
/* Vignette */
body::after {
content: "";
position: fixed; inset: 0;
pointer-events: none;
z-index: 9998;
background: radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.45) 100%);
}
/* Selection */
::selection { background: var(--copper); color: var(--bg-deep); }
/* Scrollbar */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--ink-ghost); border-radius: 0; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }
/* ─── Layout shell ─────────────────────────────────────────── */
.shell {
max-width: 1380px;
margin: 0 auto;
padding: 32px 56px 160px;
position: relative;
}
@media (max-width: 900px) {
.shell { padding: 20px 20px 160px; }
}
/* ─── Masthead ─────────────────────────────────────────────── */
.masthead {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
padding-bottom: 22px;
border-bottom: 1px solid var(--rule-strong);
position: relative;
animation: fadeUp 0.7s var(--ease-out) both;
}
.masthead .left {
display: flex; align-items: center; gap: 18px;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--ink-mute);
}
.dot-live {
width: 8px; height: 8px; border-radius: 50%;
background: var(--jade);
box-shadow: 0 0 0 0 var(--jade);
animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(122, 178, 148, 0.55); }
50% { box-shadow: 0 0 0 8px rgba(122, 178, 148, 0); }
}
.brand {
text-align: center;
font-family: var(--serif);
font-weight: 400;
font-style: italic;
font-size: 28px;
line-height: 1;
letter-spacing: -0.01em;
font-variation-settings: 'opsz' 144, 'SOFT' 50;
}
.brand span {
display: block;
font-family: var(--mono);
font-style: normal;
font-weight: 400;
font-size: 9px;
letter-spacing: 0.32em;
margin-top: 6px;
color: var(--ink-mute);
text-transform: uppercase;
}
.toolbar {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 6px;
}
.toolbar button, .toolbar select {
background: transparent;
border: 1px solid transparent;
color: var(--ink-soft);
width: 36px; height: 36px;
display: inline-flex; align-items: center; justify-content: center;
cursor: pointer;
border-radius: 0;
transition: all 180ms var(--ease);
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.1em;
}
.toolbar button:hover, .toolbar select:hover {
color: var(--copper);
border-color: var(--rule-strong);
}
.toolbar select {
width: auto; padding: 0 10px;
text-transform: uppercase;
-webkit-appearance: none; appearance: none;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%239C937F' fill='none' stroke-width='1.4'/></svg>");
background-repeat: no-repeat;
background-position: right 4px center;
padding-right: 18px;
}
.toolbar .accent-swatch {
width: 14px; height: 14px; border-radius: 50%;
background: var(--copper);
box-shadow: 0 0 0 1px var(--rule-strong), 0 0 14px var(--copper-glow);
}
.toolbar .sep {
width: 1px; height: 20px;
background: var(--rule-strong);
margin: 0 6px;
}
/* Folio marks at corners */
.folio {
position: absolute;
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.2em;
color: var(--ink-faint);
text-transform: uppercase;
}
.folio.tl { top: 14px; left: 14px; }
.folio.tr { top: 14px; right: 14px; }
.folio.bl { bottom: 14px; left: 14px; }
.folio.br { bottom: 14px; right: 14px; }
/* ─── Tab strip ────────────────────────────────────────────── */
.tabstrip {
display: flex;
gap: 0;
margin-top: 14px;
border-bottom: 1px solid var(--rule);
position: relative;
overflow-x: auto;
scrollbar-width: none;
animation: fadeUp 0.8s 0.1s var(--ease-out) both;
}
.tabstrip::-webkit-scrollbar { display: none; }
.tab {
background: transparent;
border: 0;
color: var(--ink-mute);
padding: 18px 26px 16px;
cursor: pointer;
font-family: var(--sans);
font-size: 13px;
font-weight: 400;
letter-spacing: 0.04em;
position: relative;
display: inline-flex;
align-items: baseline;
gap: 10px;
transition: color 180ms var(--ease);
white-space: nowrap;
}
.tab .num {
font-family: var(--mono);
font-size: 10px;
color: var(--ink-faint);
letter-spacing: 0.15em;
}
.tab:hover { color: var(--ink-soft); }
.tab.active {
color: var(--ink);
font-family: var(--serif);
font-style: italic;
font-size: 17px;
font-variation-settings: 'opsz' 144;
}
.tab.active::after {
content: "";
position: absolute;
bottom: -1px; left: 0; right: 0;
height: 2px;
background: var(--copper);
box-shadow: 0 0 12px var(--copper-glow);
}
/* ─── Hero — Now Playing editorial spread ──────────────────── */
.now-playing {
display: grid;
grid-template-columns: minmax(320px, 520px) 1fr;
gap: 64px;
margin-top: 48px;
position: relative;
animation: fadeUp 0.9s 0.2s var(--ease-out) both;
}
@media (max-width: 980px) {
.now-playing { grid-template-columns: 1fr; gap: 40px; }
}
/* ─── Vinyl + Tonearm ──────────────────────────────────────── */
.vinyl-stage {
position: relative;
aspect-ratio: 1;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.vinyl {
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:
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);
position: relative;
animation: spin 14s linear infinite;
}
.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;
}
/* Album art label in vinyl center */
.vinyl-label {
position: absolute;
inset: 32%;
border-radius: 50%;
background:
radial-gradient(circle at 30% 30%, #C26B3D, #8B3F1A 60%, #5C2510 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: var(--ink);
box-shadow: inset 0 0 24px rgba(0,0,0,0.4);
overflow: hidden;
}
.vinyl-label::before {
content: "";
position: absolute;
width: 12%; height: 12%;
border-radius: 50%;
background: var(--bg-deep);
box-shadow: inset 0 1px 2px rgba(255,255,255,0.1);
}
.vinyl-label-text {
position: relative;
font-family: var(--serif);
font-style: italic;
font-size: 11px;
font-variation-settings: 'opsz' 144;
letter-spacing: 0.03em;
margin-top: 36%;
padding: 0 14%;
opacity: 0.85;
line-height: 1.2;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Tonearm */
.tonearm {
position: absolute;
top: -8%; right: -4%;
width: 58%; height: 58%;
pointer-events: none;
transform-origin: 88% 12%;
transform: rotate(-22deg);
transition: transform 1s var(--ease);
z-index: 2;
}
.now-playing.is-playing .tonearm { transform: rotate(0deg); }
/* ─── Track masthead ───────────────────────────────────────── */
.track-masthead {
display: flex;
flex-direction: column;
justify-content: center;
}
.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: 20px;
}
.kicker::before, .kicker::after {
content: ""; flex: 0 0 24px; height: 1px;
background: var(--copper);
opacity: 0.6;
}
.kicker::after { flex: 1 0 auto; }
.track-title {
font-family: var(--serif);
font-weight: 400;
font-size: clamp(42px, 5.6vw, 78px);
line-height: 0.96;
letter-spacing: -0.02em;
font-variation-settings: 'opsz' 144, 'SOFT' 30;
margin-bottom: 18px;
color: var(--ink);
}
.track-title em {
font-style: italic;
color: var(--copper-hi);
font-variation-settings: 'opsz' 144, 'SOFT' 60;
}
.track-byline {
font-family: var(--serif);
font-style: italic;
font-size: 22px;
font-weight: 300;
color: var(--ink-soft);
font-variation-settings: 'opsz' 60;
margin-bottom: 4px;
}
.track-album {
font-family: var(--sans);
font-size: 13px;
letter-spacing: 0.04em;
color: var(--ink-mute);
}
/* ─── Metadata grid (magazine sidebar) ─────────────────────── */
.meta-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0;
margin-top: 36px;
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);
}
.meta-cell:last-child { border-right: 0; padding-right: 0; }
.meta-cell .label {
font-family: var(--mono);
font-size: 9px;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--ink-faint);
margin-bottom: 8px;
}
.meta-cell .value {
font-family: var(--mono);
font-size: 13px;
color: var(--ink);
letter-spacing: 0.02em;
}
.meta-cell .value.serif {
font-family: var(--serif);
font-style: italic;
font-size: 18px;
font-variation-settings: 'opsz' 60;
}
/* ─── Progress + transport ─────────────────────────────────── */
.transport {
margin-top: 36px;
}
.progress-row {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 18px;
margin-bottom: 28px;
}
.timecode {
font-family: var(--mono);
font-size: 12px;
color: var(--ink-mute);
letter-spacing: 0.06em;
font-variant-numeric: tabular-nums;
}
.timecode.elapsed { color: var(--copper); }
.progress-track {
height: 2px;
background: var(--rule-strong);
position: relative;
cursor: pointer;
}
.progress-fill {
position: absolute; left: 0; top: 0;
height: 100%;
width: 38%;
background: var(--copper);
box-shadow: 0 0 12px var(--copper-glow);
transition: width 200ms linear;
}
.progress-fill::after {
content: "";
position: absolute; right: -5px; top: -4px;
width: 10px; height: 10px;
background: var(--copper);
border-radius: 50%;
box-shadow: 0 0 14px var(--copper-glow), 0 0 0 4px rgba(224, 128, 56, 0.12);
}
/* Spectrum — visual signature of the player */
.spectrum {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 3px;
height: 38px;
margin-bottom: 24px;
}
.spectrum span {
display: block;
width: 3px;
background: linear-gradient(to top, var(--copper-lo), var(--copper-hi));
opacity: 0.85;
animation: bar 1.1s ease-in-out infinite;
}
.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 bar {
0%, 100% { transform: scaleY(0.4); }
50% { transform: scaleY(1); }
}
/* Transport controls */
.controls {
display: flex;
align-items: center;
gap: 18px;
}
.btn-trans {
background: transparent;
border: 1px solid var(--rule-strong);
color: var(--ink-soft);
width: 48px; height: 48px;
display: inline-flex; align-items: center; justify-content: center;
cursor: pointer;
border-radius: 50%;
transition: all 200ms var(--ease);
}
.btn-trans:hover {
border-color: var(--copper);
color: var(--copper);
background: rgba(224, 128, 56, 0.06);
}
.btn-trans.primary {
width: 64px; height: 64px;
background: var(--ink);
color: var(--bg-deep);
border-color: var(--ink);
box-shadow: 0 8px 28px rgba(0,0,0,0.45), inset 0 -6px 16px rgba(0,0,0,0.05);
}
.btn-trans.primary:hover {
background: var(--copper);
border-color: var(--copper);
color: var(--bg-deep);
transform: scale(1.04);
box-shadow: 0 8px 28px var(--copper-glow);
}
.btn-trans svg { width: 20px; height: 20px; fill: currentColor; }
.btn-trans.primary svg { width: 24px; height: 24px; }
/* VU meter style volume */
.vu-cluster {
margin-left: auto;
display: flex;
align-items: center;
gap: 16px;
}
.vu-meter {
position: relative;
width: 140px; height: 60px;
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-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);
}
.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-readout {
font-family: var(--mono);
font-size: 11px;
color: var(--ink-mute);
letter-spacing: 0.06em;
font-variant-numeric: tabular-nums;
display: flex; flex-direction: column; gap: 4px;
}
.vu-readout strong { color: var(--copper); font-weight: 400; }
/* ─── Section divider ──────────────────────────────────────── */
.section-divider {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 24px;
margin: 96px 0 32px;
animation: fadeUp 1s 0.4s var(--ease-out) both;
}
.section-divider .label {
font-family: var(--serif);
font-style: italic;
font-size: 22px;
font-variation-settings: 'opsz' 144;
}
.section-divider .label .num {
font-family: var(--mono);
font-style: normal;
font-size: 10px;
letter-spacing: 0.3em;
color: var(--copper);
margin-right: 12px;
vertical-align: middle;
}
.section-divider .rule {
height: 1px;
background: var(--rule-strong);
position: relative;
}
.section-divider .rule::before {
content: "";
position: absolute;
left: 0; top: 50%; transform: translateY(-50%);
width: 6px; height: 6px;
background: var(--copper);
border-radius: 50%;
}
.section-divider .meta {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.2em;
color: var(--ink-faint);
text-transform: uppercase;
}
/* ─── Browser preview ──────────────────────────────────────── */
.browser-toolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 0;
border-bottom: 1px solid var(--rule);
margin-bottom: 28px;
}
.browser-toolbar .crumb {
font-family: var(--serif);
font-style: italic;
font-size: 16px;
color: var(--ink-soft);
font-variation-settings: 'opsz' 60;
}
.browser-toolbar .crumb b {
color: var(--ink);
font-weight: 400;
}
.browser-toolbar .crumb .sep {
color: var(--ink-faint);
margin: 0 8px;
font-style: normal;
}
.browser-toolbar .spacer { flex: 1; }
.browser-toolbar .pill {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--ink-mute);
padding: 6px 12px;
border: 1px solid var(--rule-strong);
cursor: pointer;
background: transparent;
transition: all 180ms var(--ease);
}
.browser-toolbar .pill:hover { color: var(--copper); border-color: var(--copper); }
.browser-toolbar .pill.active {
background: var(--copper);
color: var(--bg-deep);
border-color: var(--copper);
}
.browser-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 28px 24px;
}
.media-card {
cursor: pointer;
transition: transform 280ms var(--ease);
}
.media-card:hover { transform: translateY(-4px); }
.media-cover {
aspect-ratio: 1;
background: var(--bg-card);
position: relative;
overflow: hidden;
margin-bottom: 14px;
box-shadow: 0 14px 40px rgba(0,0,0,0.4);
transition: box-shadow 280ms var(--ease);
}
.media-card:hover .media-cover {
box-shadow: 0 20px 50px rgba(0,0,0,0.55), 0 0 0 1px var(--copper);
}
.media-cover::after {
content: "";
position: absolute; inset: 0;
background: radial-gradient(circle at 70% 30%, rgba(255,255,255,0.08), transparent 60%);
}
.media-cover .play-overlay {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
background: rgba(14, 13, 11, 0.4);
opacity: 0;
transition: opacity 200ms var(--ease);
}
.media-card:hover .play-overlay { opacity: 1; }
.media-cover .play-overlay svg {
width: 44px; height: 44px;
fill: var(--ink);
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.6));
}
.media-cover .index {
position: absolute;
top: 10px; left: 12px;
font-family: var(--mono);
font-size: 10px;
color: var(--ink);
letter-spacing: 0.15em;
opacity: 0.7;
}
.media-meta .title {
font-family: var(--serif);
font-size: 16px;
color: var(--ink);
line-height: 1.25;
margin-bottom: 4px;
font-variation-settings: 'opsz' 60;
}
.media-meta .sub {
font-family: var(--mono);
font-size: 10px;
color: var(--ink-mute);
letter-spacing: 0.1em;
text-transform: uppercase;
}
/* Cover swatches */
.cover-1 { background: linear-gradient(135deg, #1F2933 0%, #4D5C6E 100%); }
.cover-2 { background: linear-gradient(135deg, #6B2B1F 0%, #C26B3D 70%, #F4A064 100%); }
.cover-3 { background: linear-gradient(135deg, #2A2A2A 0%, #1A1A1A 100%); }
.cover-4 { background: radial-gradient(circle at 30% 30%, #7AB294 0%, #2C4A3A 100%); }
.cover-5 { background: linear-gradient(135deg, #F2EBDC 0%, #C8B89A 100%); }
.cover-6 { background: conic-gradient(from 0deg at 50% 50%, #E08038, #C2553F, #6B2B1F, #E08038); }
.cover-7 { background: linear-gradient(180deg, #0E0D0B 0%, #C26B3D 100%); }
.cover-8 { background: repeating-linear-gradient(45deg, #2E2820, #2E2820 4px, #1F1B14 4px, #1F1B14 12px); }
/* ─── Settings preview (compact card grid) ─────────────────── */
.settings-stack {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
}
.settings-card {
background: var(--bg-card);
border: 1px solid var(--rule);
padding: 26px 26px 24px;
position: relative;
transition: border-color 240ms var(--ease);
}
.settings-card:hover { border-color: var(--rule-strong); }
.settings-card .card-num {
position: absolute;
top: 18px; right: 22px;
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.2em;
color: var(--ink-faint);
}
.settings-card h3 {
font-family: var(--serif);
font-weight: 400;
font-size: 24px;
color: var(--ink);
margin-bottom: 4px;
font-variation-settings: 'opsz' 144;
}
.settings-card h3 em {
font-style: italic;
color: var(--copper);
}
.settings-card .desc {
font-family: var(--sans);
font-size: 13px;
color: var(--ink-mute);
line-height: 1.5;
margin-bottom: 22px;
}
.settings-card .stat-line {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-top: 1px solid var(--rule);
font-family: var(--mono);
font-size: 11px;
}
.settings-card .stat-line .k { color: var(--ink-mute); letter-spacing: 0.1em; text-transform: uppercase; }
.settings-card .stat-line .v { color: var(--ink); font-variant-numeric: tabular-nums; }
.settings-card .stat-line .v.ok { color: var(--jade); }
.settings-card .stat-line .v.warn { color: var(--amber); }
.settings-card .stat-line .v.err { color: var(--rust); }
.settings-card .cta {
display: inline-flex;
align-items: center;
gap: 10px;
margin-top: 22px;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--copper);
cursor: pointer;
background: transparent;
border: 0;
padding: 0;
transition: gap 200ms var(--ease);
}
.settings-card .cta:hover { gap: 14px; }
.settings-card .cta::after {
content: "→";
font-family: var(--sans);
font-size: 14px;
}
/* ─── Quick actions strip ──────────────────────────────────── */
.actions-rail {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 1px;
background: var(--rule-strong);
border: 1px solid var(--rule-strong);
}
.action {
background: var(--bg-card);
padding: 24px 18px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
cursor: pointer;
transition: all 200ms var(--ease);
position: relative;
}
.action:hover {
background: var(--bg-card-2);
color: var(--copper);
}
.action:hover .action-icon { color: var(--copper); transform: translateY(-2px); }
.action-icon {
font-size: 22px;
color: var(--ink-soft);
margin-bottom: 12px;
width: 32px; height: 32px;
display: flex; align-items: center; justify-content: center;
transition: all 200ms var(--ease);
}
.action-label {
font-family: var(--serif);
font-style: italic;
font-size: 14px;
color: var(--ink);
font-variation-settings: 'opsz' 60;
margin-bottom: 4px;
}
.action-key {
font-family: var(--mono);
font-size: 9px;
letter-spacing: 0.18em;
color: var(--ink-faint);
text-transform: uppercase;
}
/* ─── Mini player (sticky console strip) ───────────────────── */
.mini-player {
position: fixed;
bottom: 0; left: 0; right: 0;
background: linear-gradient(180deg, rgba(14,13,11,0.6) 0%, rgba(14,13,11,0.95) 30%);
backdrop-filter: blur(20px) saturate(140%);
-webkit-backdrop-filter: blur(20px) saturate(140%);
border-top: 1px solid var(--rule-strong);
padding: 14px 32px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
gap: 32px;
z-index: 100;
animation: slideUp 0.9s 0.5s var(--ease-out) both;
}
.mini-player::before {
content: "";
position: absolute;
top: -1px; left: 32px; right: 32px;
height: 2px;
background: linear-gradient(90deg, transparent, var(--copper), transparent);
opacity: 0.5;
}
.mp-track {
display: flex; align-items: center; gap: 16px;
min-width: 0;
}
.mp-cover {
width: 48px; height: 48px;
flex-shrink: 0;
background: linear-gradient(135deg, #6B2B1F, #C26B3D);
position: relative;
box-shadow: 0 4px 14px rgba(0,0,0,0.4);
}
.mp-cover::after {
content: "";
position: absolute; inset: 0;
background: radial-gradient(circle at 70% 30%, rgba(255,255,255,0.15), transparent 60%);
}
.mp-info { min-width: 0; }
.mp-info .t {
font-family: var(--serif);
font-style: italic;
font-size: 15px;
color: var(--ink);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
font-variation-settings: 'opsz' 60;
}
.mp-info .a {
font-family: var(--mono);
font-size: 10px;
color: var(--ink-mute);
letter-spacing: 0.1em;
text-transform: uppercase;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
margin-top: 4px;
}
.mp-controls {
display: flex; justify-content: center; align-items: center; gap: 18px;
}
.mp-btn {
background: transparent;
border: 0;
color: var(--ink-soft);
width: 32px; height: 32px;
cursor: pointer;
display: inline-flex; align-items: center; justify-content: center;
transition: color 180ms var(--ease);
}
.mp-btn:hover { color: var(--copper); }
.mp-btn.primary {
background: var(--ink);
color: var(--bg-deep);
width: 38px; height: 38px;
border-radius: 50%;
box-shadow: 0 4px 14px rgba(0,0,0,0.4);
}
.mp-btn.primary:hover { background: var(--copper); }
.mp-btn svg { width: 16px; height: 16px; fill: currentColor; }
.mp-btn.primary svg { width: 14px; height: 14px; }
.mp-end {
display: flex; align-items: center; gap: 14px; justify-content: flex-end;
}
.mp-time {
font-family: var(--mono);
font-size: 10px;
color: var(--ink-mute);
letter-spacing: 0.06em;
font-variant-numeric: tabular-nums;
min-width: 80px; text-align: right;
}
.mp-time strong { color: var(--copper); font-weight: 400; }
.mp-vol {
display: flex; align-items: center; gap: 8px;
}
.mp-vol-bar {
width: 80px; height: 2px;
background: var(--rule-strong);
position: relative;
}
.mp-vol-bar::before {
content: "";
position: absolute; left: 0; top: 0; bottom: 0;
width: 60%;
background: var(--copper);
}
/* ─── Footer ───────────────────────────────────────────────── */
footer.colophon {
margin-top: 120px;
padding-top: 28px;
border-top: 1px solid var(--rule-strong);
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 24px;
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-faint);
}
footer.colophon .center {
font-family: var(--serif);
font-style: italic;
font-size: 14px;
letter-spacing: 0.02em;
text-transform: none;
color: var(--ink-mute);
}
footer.colophon a { color: var(--copper); text-decoration: none; }
footer.colophon a:hover { text-decoration: underline; }
footer.colophon .right { text-align: right; }
/* ─── Animations ───────────────────────────────────────────── */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(100%); }
to { opacity: 1; transform: translateY(0); }
}
/* ─── Responsive ───────────────────────────────────────────── */
@media (max-width: 720px) {
.masthead { grid-template-columns: 1fr; gap: 14px; }
.masthead .left, .toolbar { justify-content: center; }
.meta-grid { grid-template-columns: repeat(2, 1fr); }
.meta-cell:nth-child(2) { border-right: 0; }
.progress-row { grid-template-columns: 1fr; gap: 10px; }
.controls { flex-wrap: wrap; }
.vu-cluster { width: 100%; justify-content: space-between; margin-left: 0; margin-top: 16px; }
.mini-player { grid-template-columns: 1fr; gap: 12px; padding: 12px 16px; }
.mp-end { display: none; }
.section-divider { grid-template-columns: 1fr; }
.section-divider .meta { display: none; }
}
/* Banner — design notes panel for the user */
.notes-panel {
position: fixed;
top: 14px; right: 14px;
width: 280px;
background: var(--bg-card);
border: 1px solid var(--copper);
padding: 18px 18px 16px;
font-family: var(--mono);
font-size: 11px;
color: var(--ink-soft);
z-index: 200;
box-shadow: 0 14px 40px rgba(0,0,0,0.5), 0 0 30px var(--copper-glow);
}
.notes-panel h4 {
font-family: var(--serif);
font-style: italic;
font-size: 16px;
color: var(--copper);
margin-bottom: 10px;
font-weight: 400;
}
.notes-panel ul {
list-style: none;
display: flex; flex-direction: column; gap: 6px;
letter-spacing: 0.04em;
}
.notes-panel li::before { content: "→ "; color: var(--copper); }
.notes-panel .close {
position: absolute; top: 10px; right: 12px;
background: transparent; border: 0;
color: var(--ink-mute); cursor: pointer;
font-size: 16px; line-height: 1;
}
.notes-panel.hidden { display: none; }
@media (max-width: 980px) { .notes-panel { display: none; } }
</style>
</head>
<body>
<!-- Design notes panel (dismissible) -->
<aside class="notes-panel" id="notesPanel">
<button class="close" onclick="document.getElementById('notesPanel').classList.add('hidden')">×</button>
<h4>Studio Reference</h4>
<ul>
<li>Editorial hi-fi mockup</li>
<li>Fraunces · Geist · Geist Mono</li>
<li>Copper-on-charcoal · grain</li>
<li>Asymmetric magazine grid</li>
<li>Vinyl + tonearm + VU meter</li>
<li>Hover over cards & buttons</li>
</ul>
</aside>
<div class="shell">
<!-- Folio marks -->
<span class="folio tl">№ 0008 · v0.2.0</span>
<span class="folio tr">Vol. I — APR · MMXXVI</span>
<!-- Masthead -->
<header class="masthead">
<div class="left">
<span class="dot-live"></span>
<span>Connected · Local 8765</span>
</div>
<div class="brand">
Media Server
<span>Studio Reference Edition</span>
</div>
<div class="toolbar">
<button title="API Docs">
<svg width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13z"/></svg>
</button>
<button title="Accent">
<span class="accent-swatch"></span>
</button>
<button title="Background">
<svg width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="M12 3v10.55A4 4 0 1 0 14 17V7h4V3h-6z"/></svg>
</button>
<button title="Theme">
<svg width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="M9 2c-1.05 0-2.05.16-3 .46 4.06 1.27 7 5.06 7 9.54 0 4.48-2.94 8.27-7 9.54.95.3 1.95.46 3 .46 5.52 0 10-4.48 10-10S14.52 2 9 2z"/></svg>
</button>
<select>
<option>EN</option>
<option>RU</option>
</select>
<span class="sep"></span>
<button title="Logout">
<svg width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>
</button>
</div>
</header>
<!-- Tab strip -->
<nav class="tabstrip">
<button class="tab active"><span class="num">01</span>Now Spinning</button>
<button class="tab"><span class="num">02</span>Display</button>
<button class="tab"><span class="num">03</span>Library</button>
<button class="tab"><span class="num">04</span>Quick Access</button>
<button class="tab"><span class="num">05</span>Settings</button>
</nav>
<!-- ═══════════════════════════════════════════════════════
HERO — Now Playing editorial spread
═══════════════════════════════════════════════════════ -->
<section class="now-playing is-playing">
<!-- Vinyl with tonearm -->
<div class="vinyl-stage">
<div class="vinyl">
<div class="vinyl-label">
<div class="vinyl-label-text">
Kind of<br>Blue
</div>
</div>
</div>
<svg class="tonearm" viewBox="0 0 200 200">
<defs>
<linearGradient id="armGrad" x1="0" x2="1">
<stop offset="0" stop-color="#3a3528"/>
<stop offset="0.5" stop-color="#9C937F"/>
<stop offset="1" stop-color="#5C5447"/>
</linearGradient>
</defs>
<!-- arm pivot base -->
<circle cx="176" cy="24" r="14" fill="#1a1611" stroke="#3A3528" stroke-width="1"/>
<circle cx="176" cy="24" r="6" fill="#3A3528"/>
<circle cx="176" cy="24" r="2" fill="#E08038"/>
<!-- arm -->
<line x1="176" y1="24" x2="64" y2="136" stroke="url(#armGrad)" stroke-width="3.5" stroke-linecap="round"/>
<!-- counterweight at pivot end -->
<rect x="180" y="14" width="14" height="20" fill="#26211A" stroke="#3A3528"/>
<!-- headshell -->
<rect x="56" y="128" width="22" height="18" rx="2" fill="#26211A" stroke="#3A3528" transform="rotate(-45 67 137)"/>
<!-- needle tip glow -->
<circle cx="62" cy="138" r="3" fill="#E08038" opacity="0.8"/>
<circle cx="62" cy="138" r="6" fill="none" stroke="#E08038" stroke-width="0.5" opacity="0.4"/>
</svg>
</div>
<!-- Track masthead -->
<div class="track-masthead">
<div class="kicker">Now Spinning · Track 03 of 05 · Spotify</div>
<h1 class="track-title">So What — <em>Take One</em></h1>
<div class="track-byline">Miles Davis Sextet</div>
<div class="track-album">Kind of Blue · Columbia · 1959 · Remastered</div>
<!-- Metadata grid -->
<div class="meta-grid">
<div class="meta-cell">
<div class="label">Bitrate</div>
<div class="value">320 kbps</div>
</div>
<div class="meta-cell">
<div class="label">Format</div>
<div class="value">FLAC · 24/96</div>
</div>
<div class="meta-cell">
<div class="label">Output</div>
<div class="value">Studio Mon.</div>
</div>
<div class="meta-cell">
<div class="label">Genre</div>
<div class="value serif">Modal Jazz</div>
</div>
</div>
<!-- Spectrum -->
<div class="spectrum" style="margin-top: 36px;">
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
</div>
<!-- Transport -->
<div class="transport">
<div class="progress-row">
<span class="timecode elapsed">03:42</span>
<div class="progress-track">
<div class="progress-fill"></div>
</div>
<span class="timecode">09:22</span>
</div>
<div class="controls">
<button class="btn-trans" title="Previous">
<svg viewBox="0 0 24 24"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>
</button>
<button class="btn-trans primary" title="Pause">
<svg viewBox="0 0 24 24"><path d="M6 5h4v14H6zm8 0h4v14h-4z"/></svg>
</button>
<button class="btn-trans" title="Next">
<svg viewBox="0 0 24 24"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>
</button>
<div class="vu-cluster">
<div class="vu-meter">
<div class="vu-needle" id="vuNeedle"></div>
</div>
<div class="vu-readout">
<span>OUT <strong>6 dB</strong></span>
<span>VOL <strong>72%</strong></span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════
LIBRARY preview
═══════════════════════════════════════════════════════ -->
<div class="section-divider">
<div class="label"><span class="num">§ 03</span><em>The Library</em></div>
<div class="rule"></div>
<div class="meta">14 folders · 2,148 items</div>
</div>
<div class="browser-toolbar">
<div class="crumb">
<em>Music</em>
<span class="sep">/</span>
<em>Jazz</em>
<span class="sep">/</span>
<b>Miles Davis</b>
</div>
<div class="spacer"></div>
<button class="pill active">Grid</button>
<button class="pill">Compact</button>
<button class="pill">List</button>
<button class="pill"></button>
</div>
<div class="browser-grid">
<div class="media-card">
<div class="media-cover cover-2">
<span class="index">A1</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">Kind of Blue</div>
<div class="sub">1959 · 5 tracks</div>
</div>
</div>
<div class="media-card">
<div class="media-cover cover-1">
<span class="index">A2</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">Sketches of Spain</div>
<div class="sub">1960 · 5 tracks</div>
</div>
</div>
<div class="media-card">
<div class="media-cover cover-6">
<span class="index">A3</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">Bitches Brew</div>
<div class="sub">1970 · 7 tracks</div>
</div>
</div>
<div class="media-card">
<div class="media-cover cover-4">
<span class="index">A4</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">In a Silent Way</div>
<div class="sub">1969 · 4 tracks</div>
</div>
</div>
<div class="media-card">
<div class="media-cover cover-3">
<span class="index">A5</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">Birth of the Cool</div>
<div class="sub">1957 · 11 tracks</div>
</div>
</div>
<div class="media-card">
<div class="media-cover cover-5">
<span class="index">A6</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">Porgy and Bess</div>
<div class="sub">1959 · 13 tracks</div>
</div>
</div>
<div class="media-card">
<div class="media-cover cover-7">
<span class="index">A7</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">Milestones</div>
<div class="sub">1958 · 7 tracks</div>
</div>
</div>
<div class="media-card">
<div class="media-cover cover-8">
<span class="index">A8</span>
<div class="play-overlay"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div>
</div>
<div class="media-meta">
<div class="title">'Round About Midnight</div>
<div class="sub">1957 · 6 tracks</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════
QUICK ACCESS
═══════════════════════════════════════════════════════ -->
<div class="section-divider">
<div class="label"><span class="num">§ 04</span><em>Quick Access</em></div>
<div class="rule"></div>
<div class="meta">Scripts · Shortcuts · Links</div>
</div>
<div class="actions-rail">
<button class="action">
<div class="action-icon"></div>
<div class="action-label">Shutdown</div>
<div class="action-key">PWR · 01</div>
</button>
<button class="action">
<div class="action-icon"></div>
<div class="action-label">Restart</div>
<div class="action-key">PWR · 02</div>
</button>
<button class="action">
<div class="action-icon"></div>
<div class="action-label">Sleep</div>
<div class="action-key">PWR · 03</div>
</button>
<button class="action">
<div class="action-icon"></div>
<div class="action-label">Lock</div>
<div class="action-key">SEC · 01</div>
</button>
<button class="action">
<div class="action-icon"></div>
<div class="action-label">Spotify</div>
<div class="action-key">APP · 01</div>
</button>
<button class="action">
<div class="action-icon"></div>
<div class="action-label">VLC</div>
<div class="action-key">APP · 02</div>
</button>
<button class="action">
<div class="action-icon"></div>
<div class="action-label">Home Asst.</div>
<div class="action-key">LNK · 01</div>
</button>
<button class="action">
<div class="action-icon"></div>
<div class="action-label">Router</div>
<div class="action-key">LNK · 02</div>
</button>
</div>
<!-- ═══════════════════════════════════════════════════════
SETTINGS preview
═══════════════════════════════════════════════════════ -->
<div class="section-divider">
<div class="label"><span class="num">§ 05</span><em>Settings</em></div>
<div class="rule"></div>
<div class="meta">System · Audio · Folders · Callbacks</div>
</div>
<div class="settings-stack">
<article class="settings-card">
<span class="card-num">5.01</span>
<h3><em>Audio</em> Output</h3>
<p class="desc">Loopback device captured by the visualizer & VU meter. Auto-detection picks the system default.</p>
<div class="stat-line">
<span class="k">Device</span>
<span class="v">Realtek HD</span>
</div>
<div class="stat-line">
<span class="k">Status</span>
<span class="v ok">CONNECTED</span>
</div>
<div class="stat-line">
<span class="k">Latency</span>
<span class="v">12 ms</span>
</div>
<button class="cta">Configure</button>
</article>
<article class="settings-card">
<span class="card-num">5.02</span>
<h3><em>Media</em> Folders</h3>
<p class="desc">Library roots scanned for browsing. Network shares show availability status.</p>
<div class="stat-line">
<span class="k">Music</span>
<span class="v ok">D:\Music</span>
</div>
<div class="stat-line">
<span class="k">Movies</span>
<span class="v ok">\\NAS\Films</span>
</div>
<div class="stat-line">
<span class="k">Podcasts</span>
<span class="v warn">UNREACHABLE</span>
</div>
<button class="cta">Manage 4 folders</button>
</article>
<article class="settings-card">
<span class="card-num">5.03</span>
<h3><em>Scripts</em> &amp; Hooks</h3>
<p class="desc">Custom commands that run on demand or trigger on playback events.</p>
<div class="stat-line">
<span class="k">Scripts</span>
<span class="v">7 active</span>
</div>
<div class="stat-line">
<span class="k">Callbacks</span>
<span class="v">3 wired</span>
</div>
<div class="stat-line">
<span class="k">Last run</span>
<span class="v ok">OK · 14:22</span>
</div>
<button class="cta">Edit scripts</button>
</article>
<article class="settings-card">
<span class="card-num">5.04</span>
<h3><em>System</em> Health</h3>
<p class="desc">Server diagnostics, websocket state, and update channel.</p>
<div class="stat-line">
<span class="k">Version</span>
<span class="v">v0.2.0</span>
</div>
<div class="stat-line">
<span class="k">Uptime</span>
<span class="v">04 d · 11 h</span>
</div>
<div class="stat-line">
<span class="k">Update</span>
<span class="v ok">UP TO DATE</span>
</div>
<button class="cta">Open diagnostics</button>
</article>
</div>
<!-- Colophon -->
<footer class="colophon">
<div>Built · Anno 2026</div>
<div class="center">A. Dolgolyov · <a href="mailto:dolgolyov.alexei@gmail.com">dolgolyov.alexei@gmail.com</a></div>
<div class="right">Source on Gitea</div>
</footer>
</div>
<!-- ═══════════════════════════════════════════════════════
STICKY MINI PLAYER — console strip
═══════════════════════════════════════════════════════ -->
<div class="mini-player">
<div class="mp-track">
<div class="mp-cover"></div>
<div class="mp-info">
<div class="t">So What — Take One</div>
<div class="a">Miles Davis · Kind of Blue</div>
</div>
</div>
<div class="mp-controls">
<button class="mp-btn" title="Previous">
<svg viewBox="0 0 24 24"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>
</button>
<button class="mp-btn primary" title="Pause">
<svg viewBox="0 0 24 24"><path d="M6 5h4v14H6zm8 0h4v14h-4z"/></svg>
</button>
<button class="mp-btn" title="Next">
<svg viewBox="0 0 24 24"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>
</button>
</div>
<div class="mp-end">
<span class="mp-time"><strong>03:42</strong> / 09:22</span>
<div class="mp-vol">
<svg width="14" height="14" viewBox="0 0 24 24" fill="#9C937F"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>
<div class="mp-vol-bar"></div>
</div>
</div>
</div>
<script>
// ─── Tab switcher (visual only) ─────────────────────────
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
});
});
// ─── Browser view toggle ────────────────────────────────
document.querySelectorAll('.browser-toolbar .pill').forEach(pill => {
pill.addEventListener('click', () => {
if (pill.textContent === '↻') return;
document.querySelectorAll('.browser-toolbar .pill').forEach(p => {
if (p.textContent !== '↻') p.classList.remove('active');
});
pill.classList.add('active');
});
});
// ─── Animate the VU needle and progress for life ────────
const needle = document.getElementById('vuNeedle');
const fill = document.querySelector('.progress-fill');
let pct = 38;
setInterval(() => {
const swing = -22 + Math.random() * 44;
if (needle) needle.style.transform = `rotate(${swing}deg)`;
}, 220);
setInterval(() => {
pct = (pct + 0.08) % 100;
if (fill) fill.style.width = pct + '%';
}, 250);
// ─── Progress bar click-to-seek ─────────────────────────
document.querySelector('.progress-track')?.addEventListener('click', (e) => {
const rect = e.currentTarget.getBoundingClientRect();
pct = ((e.clientX - rect.left) / rect.width) * 100;
if (fill) fill.style.width = pct + '%';
});
</script>
</body>
</html>