feat(ui): live VU + audio-driven spectrum, editorial banner, subtler dynamic bg
VU needle (animated) - Synthetic wobble bounded by current volume runs only while state==='playing'. Two combined sines + jitter make it look like a real analog needle reacting to peaks. - Settles back to the static volume-mapped position when paused. Spectrum (real audio) - Now driven by the same frequencyData the visualizer canvas uses. Each visual bar averages a chunk of frequency bins. - Spans are now JS-injected (60 bars) instead of hardcoded so the bar count is no longer baked in. - Spectrum spans full width of the masthead column, height bumped to 56px for presence. - CSS animation pauses (sets via `body.audio-spectrum-live`) when JS is driving heights so the keyframes don't fight. - Synthetic CSS animation remains as the fallback when audio capture isn't available. Visualizer auto-enable - On first install with loopback support, visualizer is enabled so the spectrum is alive out of the box. Dynamic background - Lower max opacity (1 → 0.45 dark, 0.35 light) - sepia + saturate filter + hue-rotate keep it palette-aligned with the copper editorial tones instead of fighting them - mix-blend-mode screen (dark) / multiply (light) blends into the page background instead of overlaying Update + connection banners - Fully restyled: glassy card with copper hairline accent, mono uppercase text, copper hairline-border CTA buttons, minimal close button. Matches the rest of the editorial palette instead of the old solid-green-bar look.
This commit is contained in:
@@ -216,7 +216,7 @@
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* Dynamic Background Canvas */
|
||||
/* Dynamic Background Canvas — editorial-toned (warm, subtle) */
|
||||
.bg-shader-canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -227,10 +227,23 @@
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.6s ease;
|
||||
/* Sepia + slight desaturation keeps the shader colors palette-aligned */
|
||||
filter: sepia(0.4) saturate(0.75) contrast(0.95) brightness(0.9) hue-rotate(-8deg);
|
||||
/* Multiply blends the bright shader into the warm dark page background */
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.bg-shader-canvas.visible {
|
||||
opacity: 1;
|
||||
/* Lower max opacity so it reads as atmosphere, not foreground */
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .bg-shader-canvas {
|
||||
filter: sepia(0.35) saturate(0.7) contrast(1.05) brightness(1.05) hue-rotate(-12deg);
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
:root[data-theme="light"] .bg-shader-canvas.visible {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
body.dynamic-bg-active {
|
||||
@@ -4368,31 +4381,101 @@ header .brand-sub {
|
||||
box-shadow: 0 0 12px var(--copper-glow);
|
||||
}
|
||||
|
||||
/* ─── Update + connection banners ───────────────────────────── */
|
||||
/* ─── Update + connection banners (editorial) ────────────────── */
|
||||
.update-banner,
|
||||
.connection-banner {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--copper);
|
||||
border-radius: 0;
|
||||
color: var(--ink-soft);
|
||||
box-shadow: 0 0 24px var(--copper-glow);
|
||||
font-family: var(--sans);
|
||||
font-size: 13px;
|
||||
/* Override legacy fixed-top + accent background */
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
z-index: 1001;
|
||||
background: rgba(33, 30, 24, 0.94) !important;
|
||||
color: var(--ink-soft) !important;
|
||||
border: 0 !important;
|
||||
border-bottom: 1px solid var(--copper) !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4) !important;
|
||||
padding: 12px 32px !important;
|
||||
font-family: var(--mono) !important;
|
||||
font-size: 11px !important;
|
||||
letter-spacing: 0.12em !important;
|
||||
text-transform: uppercase !important;
|
||||
font-weight: 400 !important;
|
||||
backdrop-filter: blur(20px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(160%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
}
|
||||
.update-banner a,
|
||||
.connection-banner-btn {
|
||||
/* Tiny copper hairline accent on the bottom edge */
|
||||
.update-banner::before,
|
||||
.connection-banner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 32px;
|
||||
right: 32px;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--copper), transparent);
|
||||
opacity: 0.7;
|
||||
}
|
||||
/* Brand prefix */
|
||||
.update-banner > span:first-child::before,
|
||||
.connection-banner > span:first-child::before {
|
||||
content: "● ";
|
||||
color: var(--copper);
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
border-color: var(--copper);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.update-banner a {
|
||||
color: var(--copper) !important;
|
||||
text-decoration: none !important;
|
||||
font-family: var(--mono) !important;
|
||||
font-size: 11px !important;
|
||||
letter-spacing: 0.18em !important;
|
||||
text-transform: uppercase !important;
|
||||
font-weight: 500 !important;
|
||||
border: 1px solid var(--copper);
|
||||
padding: 6px 14px;
|
||||
transition: all 180ms var(--ease);
|
||||
}
|
||||
.update-banner a:hover {
|
||||
background: var(--copper);
|
||||
color: var(--bg-deep) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.update-banner-close {
|
||||
background: transparent !important;
|
||||
color: var(--ink-mute) !important;
|
||||
border: 0 !important;
|
||||
font-size: 18px !important;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0 !important;
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
transition: color 180ms var(--ease);
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.update-banner-close:hover {
|
||||
color: var(--copper) !important;
|
||||
}
|
||||
.connection-banner-btn {
|
||||
color: var(--copper) !important;
|
||||
font-family: var(--mono) !important;
|
||||
font-size: 11px !important;
|
||||
letter-spacing: 0.18em !important;
|
||||
text-transform: uppercase !important;
|
||||
border: 1px solid var(--copper) !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
padding: 6px 14px !important;
|
||||
transition: all 180ms var(--ease);
|
||||
}
|
||||
.connection-banner-btn:hover {
|
||||
background: var(--copper);
|
||||
color: var(--bg-deep);
|
||||
background: var(--copper) !important;
|
||||
color: var(--bg-deep) !important;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
@@ -6753,61 +6836,39 @@ body.visualizer-active .now-playing .spectrogram-canvas {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* ─── Spectrum bars ───────────────────────────────────────── */
|
||||
/* ─── Spectrum bars (JS-injected; real audio when available) ─── */
|
||||
.now-playing .spectrum {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
height: 38px;
|
||||
margin: 36px auto 24px;
|
||||
max-width: 480px;
|
||||
justify-content: stretch;
|
||||
gap: 2px;
|
||||
height: 56px;
|
||||
margin: 36px 0 24px;
|
||||
width: 100%;
|
||||
}
|
||||
.now-playing .spectrum span {
|
||||
display: block;
|
||||
width: 3px;
|
||||
flex: 0 0 3px;
|
||||
flex: 1 1 0;
|
||||
min-width: 2px;
|
||||
background: linear-gradient(to top, var(--copper-lo), var(--copper-hi));
|
||||
opacity: 0.85;
|
||||
opacity: 0.9;
|
||||
transform-origin: bottom;
|
||||
border-radius: 99px 99px 0 0;
|
||||
height: var(--bar-h, 40%);
|
||||
animation: sr-snap-bar 1.1s ease-in-out infinite;
|
||||
animation-delay: var(--bar-delay, 0s);
|
||||
animation-play-state: paused;
|
||||
transition: height 80ms linear;
|
||||
}
|
||||
:root[data-playstate="playing"] .now-playing .spectrum span {
|
||||
animation-play-state: running;
|
||||
}
|
||||
.now-playing .spectrum span:nth-child(1) { animation-delay: -0.10s; height: 30%; }
|
||||
.now-playing .spectrum span:nth-child(2) { animation-delay: -0.45s; height: 60%; }
|
||||
.now-playing .spectrum span:nth-child(3) { animation-delay: -0.20s; height: 80%; }
|
||||
.now-playing .spectrum span:nth-child(4) { animation-delay: -0.55s; height: 50%; }
|
||||
.now-playing .spectrum span:nth-child(5) { animation-delay: -0.30s; height: 95%; }
|
||||
.now-playing .spectrum span:nth-child(6) { animation-delay: -0.05s; height: 70%; }
|
||||
.now-playing .spectrum span:nth-child(7) { animation-delay: -0.65s; height: 40%; }
|
||||
.now-playing .spectrum span:nth-child(8) { animation-delay: -0.25s; height: 85%; }
|
||||
.now-playing .spectrum span:nth-child(9) { animation-delay: -0.40s; height: 55%; }
|
||||
.now-playing .spectrum span:nth-child(10) { animation-delay: -0.10s; height: 75%; }
|
||||
.now-playing .spectrum span:nth-child(11) { animation-delay: -0.50s; height: 35%; }
|
||||
.now-playing .spectrum span:nth-child(12) { animation-delay: -0.15s; height: 90%; }
|
||||
.now-playing .spectrum span:nth-child(13) { animation-delay: -0.60s; height: 45%; }
|
||||
.now-playing .spectrum span:nth-child(14) { animation-delay: -0.30s; height: 65%; }
|
||||
.now-playing .spectrum span:nth-child(15) { animation-delay: -0.45s; height: 85%; }
|
||||
.now-playing .spectrum span:nth-child(16) { animation-delay: -0.20s; height: 55%; }
|
||||
.now-playing .spectrum span:nth-child(17) { animation-delay: -0.55s; height: 70%; }
|
||||
.now-playing .spectrum span:nth-child(18) { animation-delay: -0.10s; height: 30%; }
|
||||
.now-playing .spectrum span:nth-child(19) { animation-delay: -0.40s; height: 80%; }
|
||||
.now-playing .spectrum span:nth-child(20) { animation-delay: -0.25s; height: 60%; }
|
||||
.now-playing .spectrum span:nth-child(21) { animation-delay: -0.50s; height: 90%; }
|
||||
.now-playing .spectrum span:nth-child(22) { animation-delay: -0.15s; height: 50%; }
|
||||
.now-playing .spectrum span:nth-child(23) { animation-delay: -0.60s; height: 70%; }
|
||||
.now-playing .spectrum span:nth-child(24) { animation-delay: -0.30s; height: 40%; }
|
||||
.now-playing .spectrum span:nth-child(25) { animation-delay: -0.45s; height: 85%; }
|
||||
.now-playing .spectrum span:nth-child(26) { animation-delay: -0.20s; height: 55%; }
|
||||
.now-playing .spectrum span:nth-child(27) { animation-delay: -0.55s; height: 75%; }
|
||||
.now-playing .spectrum span:nth-child(28) { animation-delay: -0.10s; height: 35%; }
|
||||
.now-playing .spectrum span:nth-child(29) { animation-delay: -0.40s; height: 65%; }
|
||||
.now-playing .spectrum span:nth-child(30) { animation-delay: -0.25s; height: 95%; }
|
||||
|
||||
/* When real audio data is driving heights, freeze the CSS animation
|
||||
so JS-set heights aren't overridden by the keyframe. */
|
||||
body.audio-spectrum-live .now-playing .spectrum span {
|
||||
animation: none !important;
|
||||
transition: height 50ms linear;
|
||||
}
|
||||
@keyframes sr-snap-bar {
|
||||
0%, 100% { transform: scaleY(0.4); }
|
||||
50% { transform: scaleY(1); }
|
||||
|
||||
Reference in New Issue
Block a user