fix(ui): close gaps with Studio Reference mockup
Side-by-side comparison surfaced several layout regressions vs. the mockup. This commit lands all of them at once. Header - Restore centered "Media Server / Studio Reference Edition" wordmark in italic Fraunces - Move folio marks to fixed page corners (visible on every tab): TL = green pulse + "Connected · Local 8765" TR = "Vol. I — Studio Reference · v0.x.x" - Replace boxed version-label badge with copper mono inline in folio.tr - Reduce header-to-content gap (container padding-top 28→56 with the folio now anchored above) Player view - Spectrum bars: smaller height (32px), centered with max-width so they don't span the whole right column - Spectrogram canvas: hidden by default (opacity 0); reveals only when visualizer toggle is active. No more leaking into bottom-left. - VU cluster volume controls: strip legacy box (background, padding, border-radius); compact stacked layout with thin slider, small mute button, mono "VOL · XX%" readout - Disable legacy applyVinylMode() — the .vinyl class added a SECOND rotation animation on top of the structural .vinyl-stage spin, causing visible compounding. Vinyl is now purely structural. Toggles - Remove vinyl mode toggle button (vinyl is always on) - Keep audio visualizer (spectrum vis) toggle — still shown by JS when supported Mini player - Force always-visible on non-player tabs regardless of scroll, by short-circuiting setMiniPlayerVisible when activeTab !== 'player' i18n - New keys: header.connected, header.volume, header.edition, header.edition_sub - Removed unused: player.folio_left, player.folio_right - en.json + ru.json updated
This commit is contained in:
@@ -4112,52 +4112,121 @@ body.mini-player-visible footer {
|
||||
/* ─── Container & header ────────────────────────────────────── */
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
padding: 28px 48px 140px;
|
||||
padding: 56px 48px 140px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.container { padding: 18px 18px 140px; }
|
||||
.container { padding: 48px 18px 140px; }
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 0 0 22px 0;
|
||||
border-bottom: 1px solid var(--rule-strong);
|
||||
margin-bottom: 18px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Folio mark on left side of header */
|
||||
header > div:first-child {
|
||||
/* ─── Folio marks (page corners, all tabs) ────────────────── */
|
||||
body > .folio {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
z-index: 50;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.14em;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-mute);
|
||||
color: var(--ink-faint);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
body > .folio.tl { left: 24px; }
|
||||
body > .folio.tr { right: 24px; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
body > .folio { font-size: 9px; letter-spacing: 0.16em; }
|
||||
body > .folio.tl { left: 14px; }
|
||||
body > .folio.tr { right: 14px; }
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
/* The status-dot now lives inside the folio */
|
||||
body > .folio .status-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: var(--jade);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 0 rgba(122, 178, 148, 0.55);
|
||||
animation: sr-pulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes sr-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); }
|
||||
50% { box-shadow: 0 0 0 6px rgba(122, 178, 148, 0); }
|
||||
}
|
||||
|
||||
.version-label {
|
||||
font-family: var(--mono);
|
||||
/* Hide the old in-header status-dot if any rendering remnants exist */
|
||||
header .status-dot { display: none; }
|
||||
|
||||
/* The version-label now lives inside the folio.tr — remove the old badge styling */
|
||||
.version-label,
|
||||
body > .folio #version-label {
|
||||
background: transparent;
|
||||
color: var(--ink-mute);
|
||||
border: 1px solid var(--rule-strong);
|
||||
border-radius: 0;
|
||||
padding: 2px 10px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
color: var(--copper);
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* ─── Header (3-column: brand center, toolbar right) ─────── */
|
||||
header {
|
||||
padding: 0 0 22px 0;
|
||||
border-bottom: 1px solid var(--rule-strong);
|
||||
margin-bottom: 28px;
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Brand wordmark (centered) */
|
||||
header .brand {
|
||||
text-align: center;
|
||||
grid-column: 2;
|
||||
line-height: 1;
|
||||
}
|
||||
header .brand-name {
|
||||
font-family: var(--serif);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 30px;
|
||||
letter-spacing: -0.015em;
|
||||
color: var(--ink);
|
||||
font-variation-settings: 'opsz' 144;
|
||||
display: block;
|
||||
}
|
||||
header .brand-sub {
|
||||
display: block;
|
||||
font-family: var(--mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.32em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-mute);
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
header { grid-template-columns: 1fr; gap: 14px; }
|
||||
header .brand { grid-column: 1; }
|
||||
header .brand-name { font-size: 24px; }
|
||||
header .header-toolbar { justify-self: center; }
|
||||
}
|
||||
|
||||
.header-toolbar {
|
||||
grid-column: 3;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.header-toolbar { grid-column: 1; }
|
||||
}
|
||||
|
||||
.header-toolbar {
|
||||
@@ -4478,15 +4547,22 @@ header > div:first-child {
|
||||
|
||||
@keyframes sr-vinyl-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* Spectrogram canvas hidden by default — visualizer toggle reveals it */
|
||||
.vinyl-stage .spectrogram-canvas {
|
||||
position: absolute;
|
||||
bottom: -52px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
bottom: -56px;
|
||||
left: 7%;
|
||||
right: 7%;
|
||||
width: 86%;
|
||||
height: 38px;
|
||||
border-radius: 0;
|
||||
opacity: 0.7;
|
||||
opacity: 0;
|
||||
transition: opacity 240ms var(--ease);
|
||||
pointer-events: none;
|
||||
}
|
||||
.vinyl-stage.visualizer-active .spectrogram-canvas,
|
||||
body.visualizer-active .vinyl-stage .spectrogram-canvas {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* ─── Player details (right column / masthead) ──────────────── */
|
||||
@@ -4616,17 +4692,20 @@ header > div:first-child {
|
||||
/* Hide the legacy .playback-state container (its data is now in meta-grid) */
|
||||
.track-info > .playback-state { display: none; }
|
||||
|
||||
/* Spectrum decorative bars */
|
||||
/* Spectrum decorative bars (centered, compact) */
|
||||
.spectrum {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
height: 38px;
|
||||
margin-top: 28px;
|
||||
margin-bottom: 8px;
|
||||
height: 32px;
|
||||
margin: 28px auto 8px;
|
||||
max-width: 360px;
|
||||
}
|
||||
.spectrum span {
|
||||
flex: 1;
|
||||
display: block;
|
||||
width: 3px;
|
||||
flex: 0 0 3px;
|
||||
background: linear-gradient(to top, var(--copper-lo), var(--copper-hi));
|
||||
opacity: 0.85;
|
||||
border-radius: 99px 99px 0 0;
|
||||
@@ -4747,32 +4826,94 @@ header > div:first-child {
|
||||
box-shadow: 0 0 8px var(--copper-glow);
|
||||
}
|
||||
|
||||
/* Volume container nested inside vu-cluster — compact stacked controls */
|
||||
/* Volume container nested inside vu-cluster — strip legacy box & make compact */
|
||||
.vu-cluster .volume-container {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-top: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
min-width: 140px;
|
||||
min-width: 0;
|
||||
width: 110px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.vu-cluster .volume-container > .mute-btn {
|
||||
align-self: flex-start;
|
||||
align-self: flex-end;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
border: 1px solid var(--rule-strong);
|
||||
color: var(--ink-soft);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.vu-cluster .volume-container > .mute-btn:hover {
|
||||
border-color: var(--copper);
|
||||
color: var(--copper);
|
||||
}
|
||||
.vu-cluster .volume-container > .mute-btn svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
fill: currentColor;
|
||||
}
|
||||
.vu-cluster .volume-container > #volume-slider {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
flex: none;
|
||||
background: var(--rule-strong);
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
.vu-cluster .volume-container > #volume-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--copper);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 8px var(--copper-glow);
|
||||
border: 0;
|
||||
}
|
||||
.vu-cluster .volume-container > .volume-display {
|
||||
text-align: right;
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-mute);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
.vu-cluster .volume-container > .volume-display::before {
|
||||
content: "VOL · ";
|
||||
color: var(--ink-faint);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Make the player view source-info row tighter (visualizer toggle row) */
|
||||
.player-details > .source-info {
|
||||
margin-top: 18px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--rule);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* ─── Progress (hairline editorial) ─────────────────────────── */
|
||||
|
||||
@@ -75,11 +75,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Folio marks at page corners -->
|
||||
<span class="folio tl"><span class="status-dot" id="status-dot" aria-live="polite"></span><span data-i18n="header.connected">Connected</span> · <span id="folio-host">Local 8765</span></span>
|
||||
<span class="folio tr"><span data-i18n="header.volume">Vol. I</span> — <span data-i18n="header.edition">Studio Reference</span> · <span id="version-label"></span></span>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span class="status-dot" id="status-dot" aria-live="polite"></span>
|
||||
<span class="version-label" id="version-label"></span>
|
||||
<div class="brand">
|
||||
<span class="brand-name" data-i18n="app.title">Media Server</span>
|
||||
<span class="brand-sub" data-i18n="header.edition_sub">Studio Reference Edition</span>
|
||||
</div>
|
||||
<div class="header-toolbar">
|
||||
<div id="headerLinks" class="header-links"></div>
|
||||
@@ -153,9 +157,6 @@
|
||||
</div>
|
||||
|
||||
<div class="player-container" data-tab-content="player" role="tabpanel" id="panel-player">
|
||||
<span class="folio tl"><span data-i18n="player.folio_left">Now Spinning</span> · <span id="folio-version">v—</span></span>
|
||||
<span class="folio tr" data-i18n="player.folio_right">Vol. I — Studio Reference</span>
|
||||
|
||||
<div class="player-layout now-playing">
|
||||
|
||||
<!-- Vinyl stage with album art as label -->
|
||||
@@ -280,15 +281,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player toggles -->
|
||||
<!-- Audio visualizer toggle (button shown by JS only when supported) -->
|
||||
<div class="source-info">
|
||||
<span class="source-label">
|
||||
<span class="vinyl-mode-label" data-i18n="player.modes">Modes</span>
|
||||
</span>
|
||||
<div class="player-toggles">
|
||||
<button class="vinyl-toggle-btn" onclick="toggleVinylMode()" id="vinylToggle" data-i18n-title="player.vinyl" title="Vinyl mode">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="12" cy="12" r="3" fill="currentColor"/><circle cx="12" cy="12" r="6.5" fill="none" stroke="currentColor" stroke-width="0.5" opacity="0.5"/></svg>
|
||||
</button>
|
||||
<button class="vinyl-toggle-btn" onclick="toggleVisualizer()" id="visualizerToggle" data-i18n-title="player.visualizer" title="Audio visualizer" style="display:none">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M3 18h2v-8H3v8zm4 0h2V6H7v12zm4 0h2V2h-2v16zm4 0h2v-6h-2v6zm4 0h2V9h-2v9z"/></svg>
|
||||
</button>
|
||||
|
||||
@@ -163,8 +163,8 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
navigator.serviceWorker.register('/sw.js').catch(() => {});
|
||||
}
|
||||
|
||||
// Initialize vinyl mode
|
||||
applyVinylMode();
|
||||
// Vinyl is now structural / always-on via CSS — no init call needed.
|
||||
// applyVinylMode();
|
||||
|
||||
// Initialize audio visualizer
|
||||
checkVisualizerAvailability().then(() => {
|
||||
|
||||
@@ -19,6 +19,8 @@ import { IconSelect } from './icon-select.js';
|
||||
export let activeTab = 'player';
|
||||
|
||||
export function setMiniPlayerVisible(visible) {
|
||||
// On any non-player tab the mini player must stay visible regardless of scroll.
|
||||
if (activeTab !== 'player') visible = true;
|
||||
const miniPlayer = document.getElementById('mini-player');
|
||||
if (visible) {
|
||||
miniPlayer.classList.remove('hidden');
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
"player.no_media": "No media playing",
|
||||
"player.kicker": "Now Playing",
|
||||
"player.modes": "Modes",
|
||||
"player.folio_left": "Now Spinning",
|
||||
"player.folio_right": "Vol. I — Studio Reference",
|
||||
"header.connected": "Connected",
|
||||
"header.volume": "Vol. I",
|
||||
"header.edition": "Studio Reference",
|
||||
"header.edition_sub": "Studio Reference Edition",
|
||||
"meta.state": "State",
|
||||
"meta.source": "Source",
|
||||
"meta.elapsed": "Elapsed",
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
"player.no_media": "Медиа не воспроизводится",
|
||||
"player.kicker": "Сейчас играет",
|
||||
"player.modes": "Режимы",
|
||||
"player.folio_left": "Сейчас играет",
|
||||
"player.folio_right": "Том I — Studio Reference",
|
||||
"header.connected": "Подключено",
|
||||
"header.volume": "Том I",
|
||||
"header.edition": "Studio Reference",
|
||||
"header.edition_sub": "Studio Reference Edition",
|
||||
"meta.state": "Состояние",
|
||||
"meta.source": "Источник",
|
||||
"meta.elapsed": "Прошло",
|
||||
|
||||
Reference in New Issue
Block a user