fix(ui): close more gaps with mockup (tabs, mini player, volume control)
Tabs - Replace SVG icons with mockup-style numeric badges (01..05) - Hide the legacy .tab-indicator (was rendering as a long copper bar above the strip); active tab gets its own copper underline via .tab-btn.active::after - Numbers turn copper on the active tab Player view - Volume control restored: mute button + slim copper slider live inside the VU cluster, on the right of the readout, separated by a hairline. Slider is the existing #volume-slider so all JS hooks (bidirectional sync, drag, etc.) keep working. - Track title font scaled down (clamp 34..64) and clamped to 3 lines with ellipsis so long YouTube-style titles don't dominate the masthead. Adds word-break + overflow-wrap. - #artist:empty and #album:empty are now display:none so blank rows don't leave a gap when the source provides no metadata. Mini player - Forced display: grid with 4 columns: track / controls / progress / volume. Was inheriting legacy display:flex which pushed elements into a single non-aligned row. - Position locked: position: fixed, bottom: 0, left/right: 0 with !important. The strip is firmly anchored to the viewport bottom. - Top edge progress (::before) painted in copper with glow. - Responsive collapse: hide progress at <=880px, hide volume at <=540px, leaving track + controls on phones. i18n - tab.player default text aligned to "Now Spinning" (matches existing en/ru values added earlier).
This commit is contained in:
@@ -4294,21 +4294,22 @@ header .brand-sub {
|
||||
margin: 14px 0 36px;
|
||||
border-radius: 0;
|
||||
gap: 0;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.tab-bar::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* Hide the legacy tab-indicator entirely — we use ::after on .tab-btn.active instead */
|
||||
.tab-indicator {
|
||||
background: var(--copper);
|
||||
box-shadow: 0 0 12px var(--copper-glow);
|
||||
height: 2px;
|
||||
bottom: -1px;
|
||||
border-radius: 0;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--ink-mute);
|
||||
padding: 18px 22px 16px;
|
||||
padding: 18px 26px 16px;
|
||||
border-radius: 0;
|
||||
font-family: var(--sans);
|
||||
font-size: 13px;
|
||||
@@ -4316,8 +4317,11 @@ header .brand-sub {
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 180ms var(--ease);
|
||||
position: relative;
|
||||
gap: 10px;
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tab-btn:hover { color: var(--ink-soft); background: transparent; }
|
||||
.tab-btn.active {
|
||||
@@ -4330,8 +4334,35 @@ header .brand-sub {
|
||||
font-variation-settings: 'opsz' 60;
|
||||
}
|
||||
|
||||
.tab-btn svg { opacity: 0.7; }
|
||||
.tab-btn.active svg { opacity: 1; color: var(--copper); }
|
||||
/* Tab number badge (replaces the icon) */
|
||||
.tab-btn .tab-num {
|
||||
font-family: var(--mono);
|
||||
font-style: normal;
|
||||
font-size: 10px;
|
||||
color: var(--ink-faint);
|
||||
letter-spacing: 0.15em;
|
||||
font-weight: 400;
|
||||
}
|
||||
.tab-btn.active .tab-num {
|
||||
color: var(--copper);
|
||||
}
|
||||
|
||||
/* Hide any remaining legacy SVG icons on tabs */
|
||||
.tab-btn svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Active tab underline — only under the active tab */
|
||||
.tab-btn.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--copper);
|
||||
box-shadow: 0 0 12px var(--copper-glow);
|
||||
}
|
||||
|
||||
/* ─── Update + connection banners ───────────────────────────── */
|
||||
.update-banner,
|
||||
@@ -5156,17 +5187,102 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
MINI PLAYER — console strip
|
||||
MINI PLAYER — sticky console strip (3-column grid)
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
.mini-player {
|
||||
background: linear-gradient(180deg, rgba(14, 13, 11, 0.7) 0%, rgba(14, 13, 11, 0.96) 30%);
|
||||
/* Override legacy display: flex with explicit grid */
|
||||
display: grid !important;
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto;
|
||||
column-gap: 24px;
|
||||
row-gap: 0;
|
||||
align-items: center;
|
||||
background: linear-gradient(180deg, rgba(14, 13, 11, 0.72) 0%, rgba(14, 13, 11, 0.96) 30%);
|
||||
backdrop-filter: blur(20px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(160%);
|
||||
border-top: 1px solid var(--rule-strong);
|
||||
box-shadow: none;
|
||||
padding: 14px 32px;
|
||||
gap: 28px;
|
||||
position: relative;
|
||||
padding: 12px 32px;
|
||||
/* Anchor firmly to the bottom of the viewport */
|
||||
position: fixed !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Top edge progress line in copper */
|
||||
.mini-player::before {
|
||||
background: var(--copper) !important;
|
||||
height: 2px !important;
|
||||
box-shadow: 0 0 8px var(--copper-glow);
|
||||
}
|
||||
|
||||
/* Column 1: track info */
|
||||
.mini-player .mini-player-info {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Column 2: transport (centered) */
|
||||
.mini-player .mini-controls {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
justify-self: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Column 3: progress (timecodes + bar) */
|
||||
.mini-player .mini-progress-container {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
justify-self: stretch;
|
||||
align-items: stretch;
|
||||
min-width: 180px;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.mini-player .mini-progress-container .mini-time-display {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Column 4: volume controls */
|
||||
.mini-player .mini-volume-container {
|
||||
grid-column: 4;
|
||||
grid-row: 1;
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 880px) {
|
||||
.mini-player {
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
column-gap: 14px;
|
||||
}
|
||||
.mini-player .mini-progress-container { display: none; }
|
||||
.mini-player .mini-volume-container { grid-column: 3; }
|
||||
}
|
||||
|
||||
@media (max-width: 540px) {
|
||||
.mini-player {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.mini-player .mini-volume-container { display: none; }
|
||||
}
|
||||
|
||||
.mini-player::before {
|
||||
@@ -6507,13 +6623,22 @@ body.visualizer-active .now-playing .spectrogram-canvas {
|
||||
.now-playing #track-title {
|
||||
font-family: var(--serif);
|
||||
font-weight: 400;
|
||||
font-size: clamp(42px, 5.6vw, 78px);
|
||||
line-height: 0.96;
|
||||
font-size: clamp(34px, 4.4vw, 64px);
|
||||
line-height: 0.98;
|
||||
letter-spacing: -0.02em;
|
||||
font-variation-settings: 'opsz' 144;
|
||||
margin-bottom: 18px;
|
||||
color: var(--ink);
|
||||
margin-top: 0;
|
||||
/* Long titles get clamped to 3 lines with ellipsis */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
hyphens: auto;
|
||||
}
|
||||
.now-playing .track-title em {
|
||||
font-style: italic;
|
||||
@@ -6531,7 +6656,6 @@ body.visualizer-active .now-playing .spectrogram-canvas {
|
||||
margin-bottom: 4px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.now-playing .track-album,
|
||||
.now-playing #album {
|
||||
font-family: var(--sans);
|
||||
@@ -6543,6 +6667,14 @@ body.visualizer-active .now-playing .spectrogram-canvas {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Hide byline/album rows when their content is empty so they
|
||||
don't leave a stretched blank gap. JS leaves them empty when
|
||||
the source provides no metadata. */
|
||||
.now-playing #artist:empty,
|
||||
.now-playing #album:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ─── 2-cell metadata grid ────────────────────────────────── */
|
||||
.now-playing .meta-grid {
|
||||
display: grid;
|
||||
@@ -6772,20 +6904,13 @@ body.visualizer-active .now-playing .spectrogram-canvas {
|
||||
width: 24px; height: 24px;
|
||||
}
|
||||
|
||||
/* VU cluster — display-only, click toggles mute */
|
||||
/* VU cluster — meter + readout + integrated volume control */
|
||||
.now-playing .vu-cluster {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: opacity 200ms var(--ease);
|
||||
}
|
||||
.now-playing .vu-cluster:hover { opacity: 0.85; }
|
||||
.now-playing .vu-cluster:focus-visible {
|
||||
outline: 1px solid var(--copper);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
.now-playing .vu-cluster.muted .vu-needle {
|
||||
background: linear-gradient(to top, var(--rust) 0%, var(--ink-mute) 100%);
|
||||
@@ -6793,6 +6918,78 @@ body.visualizer-active .now-playing .spectrogram-canvas {
|
||||
}
|
||||
.now-playing .vu-cluster.muted .vu-readout strong { color: var(--rust); }
|
||||
|
||||
/* Integrated volume control: tiny mute icon + slim copper slider */
|
||||
.now-playing .vu-volume {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding-left: 12px;
|
||||
border-left: 1px solid var(--rule);
|
||||
margin-left: 4px;
|
||||
}
|
||||
.now-playing .vu-volume .mute-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--rule-strong);
|
||||
color: var(--ink-soft);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 180ms var(--ease);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.now-playing .vu-volume .mute-btn:hover {
|
||||
border-color: var(--copper);
|
||||
color: var(--copper);
|
||||
background: rgba(224, 128, 56, 0.06);
|
||||
}
|
||||
.now-playing .vu-volume .mute-btn svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
.now-playing .vu-volume #volume-slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 80px;
|
||||
height: 2px;
|
||||
background: var(--rule-strong);
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
.now-playing .vu-volume #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;
|
||||
cursor: grab;
|
||||
}
|
||||
.now-playing .vu-volume #volume-slider::-moz-range-thumb {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--copper);
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
cursor: grab;
|
||||
}
|
||||
.now-playing .vu-volume #volume-slider::-moz-range-track {
|
||||
height: 2px;
|
||||
background: var(--rule-strong);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.now-playing .vu-meter {
|
||||
position: relative;
|
||||
width: 140px;
|
||||
|
||||
@@ -131,27 +131,27 @@
|
||||
<button class="connection-banner-btn" id="connectionBannerBtn" onclick="manualReconnect()" style="display: none;" data-i18n="connection.reconnect">Reconnect</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<!-- Tab Bar (editorial: numbered, italic active) -->
|
||||
<div class="tab-bar" id="tabBar" role="tablist">
|
||||
<div class="tab-indicator" id="tabIndicator"></div>
|
||||
<button class="tab-btn active" data-tab="player" onclick="switchTab('player')" role="tab" aria-selected="true" aria-controls="panel-player" tabindex="0">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>
|
||||
<span data-i18n="tab.player">Player</span>
|
||||
<span class="tab-num">01</span>
|
||||
<span data-i18n="tab.player">Now Spinning</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="display" onclick="switchTab('display')" role="tab" aria-selected="false" aria-controls="panel-display" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M20 3H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h6v2H8v2h8v-2h-2v-2h6c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12H4V5h16v10z"/></svg>
|
||||
<span class="tab-num">02</span>
|
||||
<span data-i18n="tab.display">Display</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="browser" onclick="switchTab('browser')" role="tab" aria-selected="false" aria-controls="panel-browser" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
|
||||
<span data-i18n="tab.browser">Browser</span>
|
||||
<span class="tab-num">03</span>
|
||||
<span data-i18n="tab.browser">Library</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="quick-actions" onclick="switchTab('quick-actions')" role="tab" aria-selected="false" aria-controls="panel-quick-actions" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 2v11h3v9l7-12h-4l4-8z"/></svg>
|
||||
<span class="tab-num">04</span>
|
||||
<span data-i18n="tab.quick_access">Quick Access</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="settings" onclick="switchTab('settings')" role="tab" aria-selected="false" aria-controls="panel-settings" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.488.488 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
|
||||
<span class="tab-num">05</span>
|
||||
<span data-i18n="tab.settings">Settings</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -247,7 +247,7 @@
|
||||
<svg viewBox="0 0 24 24"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>
|
||||
</button>
|
||||
|
||||
<div class="vu-cluster" onclick="toggleMute()" title="Click to mute / use mini player to adjust volume" role="button" tabindex="0">
|
||||
<div class="vu-cluster">
|
||||
<div class="vu-meter" aria-hidden="true">
|
||||
<div class="vu-needle" id="vuNeedle"></div>
|
||||
</div>
|
||||
@@ -255,19 +255,21 @@
|
||||
<span>OUT <strong id="vu-out">SYS</strong></span>
|
||||
<span>VOL <strong id="vu-vol">50%</strong></span>
|
||||
</div>
|
||||
<!-- Volume control: mute + slim slider, integrated -->
|
||||
<div class="vu-volume">
|
||||
<button class="mute-btn" onclick="toggleMute()" data-i18n-title="player.mute" title="Mute" id="btn-mute">
|
||||
<svg viewBox="0 0 24 24" id="mute-icon">
|
||||
<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>
|
||||
</button>
|
||||
<input type="range" id="volume-slider" min="0" max="100" value="50" aria-label="Volume">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden but functional: slider + mute + visualizer toggle.
|
||||
Adjustment happens via the always-visible mini player. -->
|
||||
<!-- Hidden but functional: legacy display + visualizer toggle. -->
|
||||
<div class="visually-hidden">
|
||||
<button class="mute-btn" onclick="toggleMute()" data-i18n-title="player.mute" title="Mute" id="btn-mute">
|
||||
<svg viewBox="0 0 24 24" id="mute-icon">
|
||||
<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>
|
||||
</button>
|
||||
<input type="range" id="volume-slider" min="0" max="100" value="50" aria-label="Volume">
|
||||
<div id="volume-display">50%</div>
|
||||
<button 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>
|
||||
|
||||
Reference in New Issue
Block a user