fix(player): wire accent picker to editorial copper palette + visual polish
Lint & Test / test (push) Successful in 9s

The accent picker only mutated --accent / --accent-hover, but the redesign
reads everything off --copper, --copper-hi, --copper-lo, --copper-glow.
--accent was a one-way alias of --copper, so picking a color did nothing.

Frontend (player.js):
- applyAccentColor now drives --copper, --copper-hi, --copper-lo, and a
  new --copper-rgb triplet (used by every soft tint / glow on both themes)
- darkenColor / hexToRgbTriple helpers added beside lightenColor

CSS (styles.css):
- introduce --copper-rgb tokens for both themes; --copper-glow now derives
  from rgba(var(--copper-rgb), 0.35) so it follows the picker
- replace 21 hardcoded rgba(224,128,56,...) / rgba(31,78,61,...) literals
  across hover bgs, focus halos, glows, vinyl-label gradients with
  rgba(var(--copper-rgb), ...)
- replace the light-theme vinyl-label gradient hexes with
  var(--copper) / var(--copper-lo)

Other player polish in this changeset:
- track-masthead: padding-right clamp(12px, 1.5vw, 24px) so VU meter,
  spectrum tail, end timecode and controls sit inset from the panel edge
  (zeroed on the single-column mobile breakpoint to keep symmetry)
- VU meter 140→120 px, volume slider 80→64 px to free up row width so
  the cluster stays inline with prev/play/next instead of wrapping
- light-theme VU meter override: cream gauge face, dark-ink scale ticks,
  hunter-emerald needle (replaces the hardcoded black gauge)
- fullscreen meta-cell labels: var(--ink-faint) → var(--copper) so STATE
  and SOURCE read as part of the same editorial system as the kicker
This commit is contained in:
2026-04-25 18:19:19 +03:00
parent 51ec1503f4
commit f4be2bdb89
2 changed files with 55 additions and 27 deletions
+27 -25
View File
@@ -113,7 +113,8 @@
--copper: #E08038; --copper: #E08038;
--copper-hi: #F4A064; --copper-hi: #F4A064;
--copper-lo: #B0561F; --copper-lo: #B0561F;
--copper-glow: rgba(224, 128, 56, 0.35); --copper-rgb: 224, 128, 56;
--copper-glow: rgba(var(--copper-rgb), 0.35);
--amber: #F5C26B; --amber: #F5C26B;
--jade: #7AB294; --jade: #7AB294;
@@ -167,7 +168,8 @@
--copper: #1F4E3D; /* hunter emerald in light mode */ --copper: #1F4E3D; /* hunter emerald in light mode */
--copper-hi: #2D6A53; --copper-hi: #2D6A53;
--copper-lo: #143E2F; --copper-lo: #143E2F;
--copper-glow: rgba(31, 78, 61, 0.18); --copper-rgb: 31, 78, 61;
--copper-glow: rgba(var(--copper-rgb), 0.18);
--amber: #C29D31; --amber: #C29D31;
--jade: #4D8C6F; --jade: #4D8C6F;
@@ -4751,7 +4753,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
justify-content: center; justify-content: center;
} }
:root[data-theme="light"] .vinyl-stage .vinyl-wrap .vinyl-label { :root[data-theme="light"] .vinyl-stage .vinyl-wrap .vinyl-label {
background: linear-gradient(135deg, #1F4E3D 0%, #143E2F 100%); background: linear-gradient(135deg, var(--copper) 0%, var(--copper-lo) 100%);
box-shadow: box-shadow:
inset 0 0 18px rgba(0, 0, 0, 0.4), inset 0 0 18px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.06),
@@ -4946,7 +4948,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
border: 1px solid var(--rule-strong); border: 1px solid var(--rule-strong);
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
overflow: hidden; overflow: hidden;
box-shadow: inset 0 2px 6px rgba(0,0,0,0.5), inset 0 0 30px rgba(224,128,56,0.08); box-shadow: inset 0 2px 6px rgba(0,0,0,0.5), inset 0 0 30px rgba(var(--copper-rgb), 0.08);
} }
.vu-meter::before { .vu-meter::before {
content: ""; content: "";
@@ -5128,7 +5130,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
height: 10px; height: 10px;
background: var(--copper); background: var(--copper);
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 14px var(--copper-glow), 0 0 0 4px rgba(224, 128, 56, 0.12); box-shadow: 0 0 14px var(--copper-glow), 0 0 0 4px rgba(var(--copper-rgb), 0.12);
transition: none; transition: none;
} }
@@ -5169,7 +5171,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
transition: all 220ms var(--ease); transition: all 220ms var(--ease);
} }
.controls button:hover { .controls button:hover {
background: rgba(224, 128, 56, 0.06); background: rgba(var(--copper-rgb), 0.06);
border-color: var(--copper); border-color: var(--copper);
color: var(--copper); color: var(--copper);
} }
@@ -5213,7 +5215,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
.mute-btn:hover { .mute-btn:hover {
border-color: var(--copper); border-color: var(--copper);
color: var(--copper); color: var(--copper);
background: rgba(224, 128, 56, 0.06); background: rgba(var(--copper-rgb), 0.06);
} }
#volume-slider { #volume-slider {
@@ -5232,7 +5234,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
height: 14px; height: 14px;
background: var(--copper); background: var(--copper);
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 12px var(--copper-glow), 0 0 0 4px rgba(224, 128, 56, 0.12); box-shadow: 0 0 12px var(--copper-glow), 0 0 0 4px rgba(var(--copper-rgb), 0.12);
border: 0; border: 0;
cursor: grab; cursor: grab;
} }
@@ -5297,7 +5299,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
.vinyl-toggle-btn.active { .vinyl-toggle-btn.active {
border-color: var(--copper); border-color: var(--copper);
color: var(--copper); color: var(--copper);
background: rgba(224, 128, 56, 0.06); background: rgba(var(--copper-rgb), 0.06);
} }
/* ═══════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════
@@ -5581,7 +5583,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
.view-toggle-btn:last-child { border-right: 0; } .view-toggle-btn:last-child { border-right: 0; }
.view-toggle-btn:hover { .view-toggle-btn:hover {
color: var(--copper); color: var(--copper);
background: rgba(224, 128, 56, 0.04); background: rgba(var(--copper-rgb), 0.04);
} }
.view-toggle-btn.active { .view-toggle-btn.active {
background: var(--ink); background: var(--ink);
@@ -5987,7 +5989,7 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
border-color: var(--copper); border-color: var(--copper);
border-style: solid; border-style: solid;
color: var(--copper); color: var(--copper);
background: rgba(224, 128, 56, 0.04); background: rgba(var(--copper-rgb), 0.04);
} }
.add-card-icon { .add-card-icon {
font-family: var(--serif); font-family: var(--serif);
@@ -6356,7 +6358,7 @@ dialog::backdrop {
font-size: 10px; font-size: 10px;
} }
.icon-select-cell:hover { .icon-select-cell:hover {
background: rgba(224, 128, 56, 0.06); background: rgba(var(--copper-rgb), 0.06);
color: var(--copper); color: var(--copper);
} }
@@ -6848,7 +6850,7 @@ body.audio-spectrum-live .now-playing .spectrum span {
width: 10px; height: 10px; width: 10px; height: 10px;
background: var(--copper); background: var(--copper);
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 14px var(--copper-glow), 0 0 0 4px rgba(224, 128, 56, 0.12); box-shadow: 0 0 14px var(--copper-glow), 0 0 0 4px rgba(var(--copper-rgb), 0.12);
transition: none; transition: none;
} }
@@ -6879,7 +6881,7 @@ body.audio-spectrum-live .now-playing .spectrum span {
.now-playing .btn-trans:hover { .now-playing .btn-trans:hover {
border-color: var(--copper); border-color: var(--copper);
color: var(--copper); color: var(--copper);
background: rgba(224, 128, 56, 0.06); background: rgba(var(--copper-rgb), 0.06);
} }
.now-playing .btn-trans:disabled { .now-playing .btn-trans:disabled {
opacity: 0.35; opacity: 0.35;
@@ -6955,7 +6957,7 @@ body.audio-spectrum-live .now-playing .spectrum span {
.now-playing .vu-volume .mute-btn:hover { .now-playing .vu-volume .mute-btn:hover {
border-color: var(--copper); border-color: var(--copper);
color: var(--copper); color: var(--copper);
background: rgba(224, 128, 56, 0.06); background: rgba(var(--copper-rgb), 0.06);
} }
.now-playing .vu-volume .mute-btn svg { .now-playing .vu-volume .mute-btn svg {
width: 14px; width: 14px;
@@ -7007,7 +7009,7 @@ body.audio-spectrum-live .now-playing .spectrum span {
border: 1px solid var(--rule-strong); border: 1px solid var(--rule-strong);
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
overflow: hidden; overflow: hidden;
box-shadow: inset 0 2px 6px rgba(0,0,0,0.5), inset 0 0 30px rgba(224,128,56,0.08); box-shadow: inset 0 2px 6px rgba(0,0,0,0.5), inset 0 0 30px rgba(var(--copper-rgb), 0.08);
flex-shrink: 0; flex-shrink: 0;
} }
.now-playing .vu-meter::before { .now-playing .vu-meter::before {
@@ -7066,7 +7068,7 @@ body.audio-spectrum-live .now-playing .spectrum span {
border-color: var(--rule-strong); border-color: var(--rule-strong);
box-shadow: box-shadow:
inset 0 1px 2px rgba(26, 23, 21, 0.08), inset 0 1px 2px rgba(26, 23, 21, 0.08),
inset 0 0 24px rgba(31, 78, 61, 0.05); inset 0 0 24px rgba(var(--copper-rgb), 0.05);
} }
:root[data-theme="light"] .now-playing .vu-meter::before { :root[data-theme="light"] .now-playing .vu-meter::before {
background: repeating-conic-gradient(from 195deg at 50% 100%, background: repeating-conic-gradient(from 195deg at 50% 100%,
@@ -7079,7 +7081,7 @@ body.audio-spectrum-live .now-playing .spectrum span {
} }
:root[data-theme="light"] .now-playing .vu-needle { :root[data-theme="light"] .now-playing .vu-needle {
background: linear-gradient(to top, var(--copper) 0%, var(--copper-lo) 70%, var(--ink) 100%); background: linear-gradient(to top, var(--copper) 0%, var(--copper-lo) 70%, var(--ink) 100%);
box-shadow: 0 0 6px rgba(31, 78, 61, 0.25); box-shadow: 0 0 6px rgba(var(--copper-rgb), 0.25);
} }
/* Mobile VU cluster: stack below controls */ /* Mobile VU cluster: stack below controls */
@@ -7233,7 +7235,7 @@ body.audio-spectrum-live .now-playing .spectrum span {
.browser-container .browser-refresh-btn:hover, .browser-container .browser-refresh-btn:hover,
.browser-container .browser-play-all-btn:hover { .browser-container .browser-play-all-btn:hover {
color: var(--copper) !important; color: var(--copper) !important;
background: rgba(224, 128, 56, 0.06) !important; background: rgba(var(--copper-rgb), 0.06) !important;
} }
.browser-container .view-toggle-btn.active { .browser-container .view-toggle-btn.active {
background: var(--ink) !important; background: var(--ink) !important;
@@ -7752,7 +7754,7 @@ select option {
border-color: var(--copper) !important; border-color: var(--copper) !important;
border-style: solid !important; border-style: solid !important;
color: var(--copper) !important; color: var(--copper) !important;
background: rgba(224, 128, 56, 0.04) !important; background: rgba(var(--copper-rgb), 0.04) !important;
} }
.settings-container .add-card-icon { .settings-container .add-card-icon {
font-family: var(--serif); font-family: var(--serif);
@@ -7987,7 +7989,7 @@ select option {
.display-container .display-power-btn:hover { .display-container .display-power-btn:hover {
color: var(--copper) !important; color: var(--copper) !important;
border-color: var(--copper) !important; border-color: var(--copper) !important;
background: rgba(224, 128, 56, 0.06) !important; background: rgba(var(--copper-rgb), 0.06) !important;
} }
/* Brightness control row */ /* Brightness control row */
@@ -8138,7 +8140,7 @@ select option {
font-size: 10px; font-size: 10px;
} }
.icon-select-cell:hover { .icon-select-cell:hover {
background: rgba(224, 128, 56, 0.06) !important; background: rgba(var(--copper-rgb), 0.06) !important;
color: var(--copper) !important; color: var(--copper) !important;
} }
@@ -8396,7 +8398,7 @@ select option {
} }
.mini-control-btn:hover { .mini-control-btn:hover {
border-color: var(--copper) !important; border-color: var(--copper) !important;
background: rgba(224, 128, 56, 0.08) !important; background: rgba(var(--copper-rgb), 0.08) !important;
color: var(--copper) !important; color: var(--copper) !important;
} }
.mini-control-btn svg { .mini-control-btn svg {
@@ -8768,7 +8770,7 @@ body.is-fullscreen-player .player-container {
margin: 0; margin: 0;
padding: 0; padding: 0;
background: background:
radial-gradient(ellipse at 30% 35%, rgba(224, 128, 56, 0.05) 0%, transparent 55%), radial-gradient(ellipse at 30% 35%, rgba(var(--copper-rgb), 0.05) 0%, transparent 55%),
radial-gradient(ellipse at center, var(--bg-paper) 0%, var(--bg-deep) 75%); radial-gradient(ellipse at center, var(--bg-paper) 0%, var(--bg-deep) 75%);
display: grid; display: grid;
place-items: stretch; place-items: stretch;
@@ -9069,7 +9071,7 @@ body.is-fullscreen-player .now-playing .meta-cell .label {
font-family: var(--mono); font-family: var(--mono);
font-size: 9px; font-size: 9px;
letter-spacing: 0.32em; letter-spacing: 0.32em;
color: var(--ink-faint); color: var(--copper);
} }
body.is-fullscreen-player .now-playing .meta-cell .value { body.is-fullscreen-player .now-playing .meta-cell .value {
font-family: var(--mono); font-family: var(--mono);
+28 -2
View File
@@ -145,6 +145,22 @@ export function lightenColor(hex, percent) {
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
} }
function darkenColor(hex, percent) {
const num = parseInt(hex.replace('#', ''), 16);
const r = Math.max(0, (num >> 16) - Math.round(255 * percent / 100));
const g = Math.max(0, ((num >> 8) & 0xff) - Math.round(255 * percent / 100));
const b = Math.max(0, (num & 0xff) - Math.round(255 * percent / 100));
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}
function hexToRgbTriple(hex) {
const num = parseInt(hex.replace('#', ''), 16);
const r = (num >> 16) & 0xff;
const g = (num >> 8) & 0xff;
const b = num & 0xff;
return `${r}, ${g}, ${b}`;
}
export function initAccentColor() { export function initAccentColor() {
const saved = localStorage.getItem('accentColor'); const saved = localStorage.getItem('accentColor');
if (saved) { if (saved) {
@@ -159,8 +175,18 @@ export function initAccentColor() {
} }
export function applyAccentColor(color, hover) { export function applyAccentColor(color, hover) {
document.documentElement.style.setProperty('--accent', color); const root = document.documentElement.style;
document.documentElement.style.setProperty('--accent-hover', hover); root.setProperty('--accent', color);
root.setProperty('--accent-hover', hover);
// Editorial palette tokens — the redesign reads these directly,
// so the picker must drive them too (the --accent alias alone has
// no effect once components moved off it).
root.setProperty('--copper', color);
root.setProperty('--copper-hi', hover);
root.setProperty('--copper-lo', darkenColor(color, 12));
root.setProperty('--copper-rgb', hexToRgbTriple(color));
// --copper-glow inherits the rgba(var(--copper-rgb), 0.35) formula
// declared in styles.css, so it picks up the new RGB automatically.
localStorage.setItem('accentColor', color); localStorage.setItem('accentColor', color);
const dot = document.getElementById('accentDot'); const dot = document.getElementById('accentDot');
if (dot) dot.style.background = color; if (dot) dot.style.background = color;