From 05486a61afd44ef9455ab4aa9da19b18d2d54d96 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Wed, 17 Jun 2026 12:15:49 -0400 Subject: [PATCH] feat(now-playing): mount real waveform visualizer in NowPlaying card (mode C) + Fill container-sizing mode Replace the 20 synthetic bars with a contained WaveformVisualizer driven by the live player, pointed at the current track; add a Fill mode (CSS-only, defaults off) sizing the canvas to its container; place the lava-lamp icon to popover on the card. --- .../Controls/NowPlayingCard.razor | 44 +++++++------------ .../Controls/NowPlayingCard.razor.css | 38 ++++++++-------- .../Controls/WaveformVisualizer.razor | 2 +- .../Controls/WaveformVisualizer.razor.cs | 11 +++++ .../Controls/WaveformVisualizer.razor.css | 13 ++++++ 5 files changed, 58 insertions(+), 50 deletions(-) diff --git a/DeepDrftPublic.Client/Controls/NowPlayingCard.razor b/DeepDrftPublic.Client/Controls/NowPlayingCard.razor index 39e8e04..1c06266 100644 --- a/DeepDrftPublic.Client/Controls/NowPlayingCard.razor +++ b/DeepDrftPublic.Client/Controls/NowPlayingCard.razor @@ -8,36 +8,22 @@ : "Select a track to begin") - @if (Player?.IsLoaded == true) - { -
- @* 20 bars - approximate the wireframe's varied animation timings *@ -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ @* Mode C (§3f, §6c): the real waveform visualizer, contained to this card and driven by the live + cascaded player. Fill="true" sizes the canvas to this positioned box instead of the viewport. + The bridge follows whatever is playing — keyed on the current track via ReleaseEntryKey/TrackId/ + TrackEntryKey — so it scrolls to the real signal and sits at-rest when nothing plays. Read-only: + the card visualizes, it never seeks. The lava-lamp popover sits in the corner (full parity, §8e). *@ +
+ +
+
- } - else - { -
- } +
diff --git a/DeepDrftPublic.Client/Controls/NowPlayingCard.razor.css b/DeepDrftPublic.Client/Controls/NowPlayingCard.razor.css index 666032f..3561096 100644 --- a/DeepDrftPublic.Client/Controls/NowPlayingCard.razor.css +++ b/DeepDrftPublic.Client/Controls/NowPlayingCard.razor.css @@ -1,8 +1,3 @@ -@keyframes wave-dance { - from { height: var(--h-lo, 4px); } - to { height: var(--h-hi, 20px); } -} - @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } @@ -51,22 +46,25 @@ letter-spacing: 0.08em; } -.waveform-bars { - display: flex; - align-items: center; - gap: 3px; +/* Contained visualizer region (Phase 12 mode C). The visualizer mounts with Fill="true", whose + layer is `position: absolute; inset: 0` — so this box must be a positioned, sized ancestor for the + canvas to fill. `overflow: hidden` keeps the lava inside the card's rounded chrome. The height is the + card's bounded live readout — taller than the old synthetic-bar strip so the real waveform reads, but + still compact for a home-page hero panel. */ +.np-visualizer { + position: relative; + height: 120px; margin-top: 1.2rem; -} - -.waveform-bar { - width: 3px; - background: var(--deepdrft-green-accent); + overflow: hidden; border-radius: 2px; - animation: wave-dance var(--dur, 1s) ease-in-out infinite alternate; } -.waveform-placeholder { - margin-top: 1.2rem; - height: 1px; - background: rgba(250, 250, 248, 0.12); -} \ No newline at end of file +/* The lava-lamp popover trigger overlays the visualizer's top-right corner (full parity, §8e). Above + the canvas (z-index) and pointer-enabled so the icon is clickable even though the canvas layer below + it is pointer-events:none. */ +.np-visualizer-controls { + position: absolute; + top: 0.25rem; + right: 0.25rem; + z-index: 1; +} diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor b/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor index a1a15fd..b457b31 100644 --- a/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor +++ b/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor @@ -7,7 +7,7 @@ scroll/zoom/compositing math live in the WaveformVisualizer.ts interop module; this component is a thin bridge that feeds it datum + playback + zoom + theme. Deliberately NOT the player-bar peak-bar idiom. *@ -
+
diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.cs b/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.cs index 8135448..f86171b 100644 --- a/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.cs +++ b/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.cs @@ -73,6 +73,17 @@ public partial class WaveformVisualizer : ComponentBase, IAsyncDisposable /// [Parameter] public double PlaybackPosition { get; set; } + /// + /// Container-sizing mode (phase-12 §6c). Default false keeps the full-viewport mount the + /// engine has always used (fixed, inset 0, clipped above the player bar) — Mix's mode-A full-bleed + /// and the ambient mode-B mounts are unchanged. Set true for a contained mount (mode C, the + /// NowPlaying card): the canvas fills its nearest positioned ancestor instead of the viewport, with + /// no footer clip. This is a CSS/layout toggle only — the renderer already sizes the backing store to + /// the canvas's own box (a ResizeObserver on the canvas, never window), so the JS module is + /// identical in both modes; Fill only changes which box that canvas occupies. + /// + [Parameter] public bool Fill { get; set; } + // Bridge-level diagnostics. Mirrors the JS-side DEBUG flag in WaveformVisualizer.ts: when true the // datum-fetch / subscription / playback-coupling seams log to the browser console (prefixed // `[WaveformVisualizer]`, same as the JS logs so the two interleave into one timeline). These pinpoint diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.css b/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.css index e1c5f4c..6a5ff6e 100644 --- a/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.css +++ b/DeepDrftPublic.Client/Controls/WaveformVisualizer.razor.css @@ -19,6 +19,19 @@ overflow: hidden; } +/* Fill / container-sizing mode (Phase 12 §6c, mode C). The contained hosts (the NowPlaying card) + set Fill="true": the canvas fills its nearest positioned ancestor instead of the viewport, and the + footer clip drops (there is no player bar to clear — the bounding box IS the clip). The host is + responsible for giving this layer a positioned, sized parent. `inset: 0` over `position: absolute` + makes the box exactly that parent's content box; `overflow: hidden` still clips the canvas to it. + The canvas backing-store sizing in WaveformVisualizer.ts is unchanged — it already measures the + canvas's own box, so this purely re-parents the box from the viewport to the container. */ +.mix-waveform-bg--fill { + position: absolute; + inset: 0; + z-index: 0; +} + /* The canvas fills the viewport. All ribbon shading (luminous depth, soft edges) is drawn inside the canvas by the WebGL2 fragment shader. NO CSS backdrop-filter: it was a confirmed per-frame perf killer on the Canvas predecessor and is exactly the cost the GPU move exists to eliminate (spec §2, §5.2);