docs(phase-15): record visualizer controls landing

Move Phase 15 from PLAN to COMPLETED; fix DDIcons location to
DeepDrftShared.Client/Common; update WaveformVisualizerControls/Popover/State
descriptions for the three-row modal-overlay rework.
This commit is contained in:
daniel-c-harvey
2026-06-17 14:50:30 -04:00
parent 1fef60a7fb
commit e38678009e
4 changed files with 26 additions and 29 deletions
+6 -6
View File
@@ -25,8 +25,8 @@ All interactive UI for the site. Blazor WebAssembly. Pages, controls, the stream
- `SpectrumVisualizer.razor`: Bar-graph spectrum display, driven by `getSpectrumData` JS callback.
- `ReleaseHeroOverlay.razor`: Shared presentational overlay shell consumed by both `SessionDetail` and `MixDetail`. Renders a background-image hero region with genre/date/share overlaid at the top and title/artist/play at the bottom. Parameters: `HeroImageKey`, `PlaceholderIcon`, `CoverThumbKey` (optional cover thumb in bottom row), `Title`, `Artist`, `Genre`, `ReleaseDate`, `ShareContent` (slot), `PlayContent` (slot), `Class` (per-page aspect/sizing override). Owns no player logic or data fetch; each consuming page passes its own play and share slots. Overlay shell is plain `<div>`s; background-image surface is a `<div class="release-hero-img">` (no `MudPaper`).
- `WaveformVisualizer.razor`: The single WebGL2 lava-lamp visualizer engine. Hosts the waveform of whatever track is currently playing/selected. Three hosting modes: mode A (Mix detail — full-bleed centerpiece), mode B (Cut/Session detail — ambient layer behind hero+content via `ReleaseDetailScaffold`'s `Ambient` slot), mode C (NowPlaying hero panel — full-bleed background for the home hero's right side, mounted by `NowPlaying.razor` inside `.np-visualizer-bg`). `[Parameter] bool Fill` switches from fixed-viewport positioning to container-relative sizing (CSS-only; the renderer is identical in both modes). The bridge resolves the current track's `EntryKey` and re-fetches the high-res datum on track change. Subscribes to `WaveformVisualizerControlState.Changed` and pushes each updated dial to the WebGL module via JS interop. Follows the live playing track (keys on host `TrackId` match OR shared host `ReleaseEntryKey`).
- `WaveformVisualizerControls.razor`: Eight-knob RadialKnob control panel (the panel content hosted by `WaveformVisualizerControlPopover`). Controls (in order): waveform scroll speed, color gradient rotation speed, lava gravity, lava heat, fluid amount, fluid viscosity (cohesion), collision strength, waveform width. `[Parameter] bool PanelChrome` scopes panel chrome (title bar, background) to the popover mount — set `true` when placed in a popover, `false` when embedded directly. Owns no JS interop: mutates the injected `WaveformVisualizerControlState` and raises `Changed`. No control is a seek surface (read-only contract).
- `WaveformVisualizerControlPopover.razor`: Pairs the lava-lamp icon button with `WaveformVisualizerControls` as `MudPopover` overlay content. This is the unit every host places — one icon anywhere gives the full eight-knob panel on demand. Styled to the NowPlaying Hero look from `deepdrft-tokens.css` (no hardcoded hex). Placed identically on Mix, Cut, Session, and the NowPlaying hero panel (full parity; in NowPlaying it sits in `.np-visualizer-controls` at the panel's top-right corner, not inside `NowPlayingCard`).
- `WaveformVisualizerControls.razor`: The waveform visualizer control panel (content hosted by `WaveformVisualizerControlPopover`). Phase 15 re-layout: a deterministic **three-row sectioned layout** encoding the visualizer's two subsystems. Row 1 (MODE, always visible): two iconographic lamp toggles (lava on/off, waveform on/off) left-aligned + collisions knob (conditional — only when both subsystems on) + color knob pinned far-right. Row 2 (LAVA, visible only when `LavaEnabled`): "LAVA:" section label + Gravity / Heat / FluidAmount / FluidViscosity knobs. Row 3 (WAVE, visible only when `WaveformEnabled`): "WAVE:" section label + scroll-speed `MudSlider` (not a knob) + width knob pinned far-right. Total: two lamp toggles, seven `RadialKnob`s, one `MudSlider`. Colour principle: lamp toggles / knob arcs / slider are green (`Color.Primary` — interactive); section labels / knob caption icons are light (static). Each control has a playful `MudTooltip`. `[Parameter] bool PanelChrome` scopes panel chrome (NowPlayingCard look — square corners, lighter-navy, thin border) to the popover mount; chrome classes live in the global `deepdrft-styles.css` (CSS isolation cannot reach portaled overlay content). `[Parameter] bool Visible` gates the rows via `@if` while the container holds reserved min-height. Owns no JS interop: mutates the injected `WaveformVisualizerControlState` and raises `Changed`. No control is a seek surface (read-only contract).
- `WaveformVisualizerControlPopover.razor`: Pairs the lava-lamp icon button with `WaveformVisualizerControls` as a **screen-centered tinted modal** (Phase 15). The primitive is `MudOverlay` (`DarkBackground="true"`, `Modal="true"`) — **not** `MudPopover`; `AnchorOrigin`/`TransformOrigin` parameters do not exist (a centered modal has no anchor). Clicking the lava-lamp icon opens the overlay; clicking the scrim closes it (knob-drag-safe: `RadialKnob`'s `position:fixed` capture div sits above the scrim during a drag, so releasing outside the panel never fires the close handler). The panel stops click propagation so an inside click is not a dismissal. `[Parameter] Size IconSize` controls the trigger-icon size (default `Large`). This is the unit every host places — one icon anywhere gives the full control panel centered on screen, regardless of where the icon sits. Placed identically on Mix, Cut, Session, and the NowPlaying hero panel (full parity; in NowPlaying it sits in `.np-visualizer-controls` at the panel's top-right corner, not inside `NowPlayingCard`).
- `WaveformZoomMapping.cs`: Maps the `WaveformVisualizerControlState.Resolution` fraction to an integer zoom level for the WebGL renderer.
- `NowPlayingCard.razor`: Home-page text panel showing the currently playing track (label, title, sub-line). Renders label/"Now Playing" dot, track name, and artist·release sub-line from the cascaded `IStreamingPlayerService`. Subscribes to `IPlayerService.StateChanged` in `OnParametersSet` (reference-guarded, idempotent) and unsubscribes on dispose to re-render on track/state change. No visualizer or popover; those moved to `NowPlaying.razor`.
- `NowPlaying.razor`: Owns the home hero's right-side panel (`.now-playing-panel` — the outer wrapper formerly called `.hero-right` in `Home.razor`). Mounts `<WaveformVisualizer Fill="true">` as a full-bleed background inside `.np-visualizer-bg`, `<WaveformVisualizerControlPopover>` in `.np-visualizer-controls` (top-right corner), the three pulsing `.circle-deco` rings, and the content layer (hosts `<NowPlayingCard>` + `<NowPlayingStats>`). `Home.razor`'s `MudItem` renders `<NowPlaying />` directly with no wrapper. Subscribes to `IPlayerService.StateChanged` in `OnParametersSet` (reference-guarded, idempotent) and unsubscribes on dispose — needed because the player cascade is `IsFixed` (the provider's own re-render does not reach `NowPlaying`), so the subscription is the only way to re-render and re-propagate `ReleaseEntryKey`/`TrackId`/`TrackEntryKey` into `<WaveformVisualizer>` when the playing track changes.
@@ -39,7 +39,7 @@ All interactive UI for the site. Blazor WebAssembly. Pages, controls, the stream
- `StreamingAudioPlayerService`: Production implementation. Chunked stream from `TrackMediaClient`, adaptive 1664 KB buffer, early-playback, **seek-beyond-buffer** via offset request to the content API.
- `AudioInteropService`: JS interop wrapper over `window.DeepDrftAudio`. Manages `DotNetObjectReference` lifetimes for progress, end-of-playback, spectrum callbacks.
- Dark-mode services: `DarkModeServiceBase` (cookie name constant), `DarkModeCookieService` (JS cookie read/write).
- `WaveformVisualizerControlState`: Scoped session-persistent holder for the visualizer's **eight** control positions: `ScrollSpeed`, `GradientRotationSpeed`, `LavaGravity`, `LavaHeat`, `FluidAmount` (wax count/volume), `FluidViscosity` (cohesion — the second half of the Phase 10 "bubbles" split; `BlobDensity` is gone), `CollisionStrength`, `WaveformWidth`. Each has a matching `Default*` const. `Changed` event is the decoupling seam — controls mutate state + raise `Changed`; the bridge (`WaveformVisualizer`) subscribes and pushes the affected uniform. Scoped DI so state survives SPA nav within a session and resets on fresh page load.
- `WaveformVisualizerControlState`: Scoped session-persistent holder for the visualizer's **eight** continuous control positions plus **two subsystem on/off toggles** (Phase 15): `ScrollSpeed`, `GradientRotationSpeed`, `LavaGravity`, `LavaHeat`, `FluidAmount` (wax count/volume), `FluidViscosity` (cohesion — the second half of the Phase 10 "bubbles" split; `BlobDensity` is gone), `CollisionStrength`, `WaveformWidth`, `LavaEnabled` (bool, default `true`), `WaveformEnabled` (bool, default `true`). Each has a matching `Default*` const. `Changed` event is the decoupling seam — controls mutate state + raise `Changed`; the bridge (`WaveformVisualizer`) subscribes and pushes the affected uniform or subsystem-enable. Scoped DI so state survives SPA nav within a session and resets on fresh page load.
- `Clients/`: HTTP API clients (both target DeepDrftAPI).
- `TrackClient`: SQL metadata API. Uses named `IHttpClientFactory` client `"DeepDrft.API"`. Sends `page` param (not `pageNumber`). Deserializes response as bare `PagedResult<TrackDto>` (not wrapped in ApiResultDto envelope).
- `TrackMediaClient`: Content API. Uses named `IHttpClientFactory` client `"DeepDrft.Content"`. Methods like `GetAudioStreamAsync(trackId, byteOffset?)``Stream` with optional Range header support for seek-beyond-buffer.
@@ -49,7 +49,7 @@ All interactive UI for the site. Blazor WebAssembly. Pages, controls, the stream
- `TrackDetailViewModel`: Scoped. Holds loaded track, loading flag, not-found flag. `Load(entryKey)` fetches via `ITrackDataService` and resets all flags per call (prevents cross-navigation bleed). Registered in `Startup.ConfigureDomainServices`.
- `Common/`: Shared utilities.
- `DarkModeSettings.cs`: `[PersistentState]`-annotated class (single source of truth for dark mode in the client). Registered scoped.
- `DDIcons.cs`: Hand-rolled SVG icons (gas-lamp lit/unlit for dark mode toggle).
- `ReleaseRoutes.cs`: Static helper. `DetailHref(long id, ReleaseMedium)` returns the canonical public detail route for a release; consumed by Archive, AlbumsView, player bar, and TrackRedirect (11.B).
- `Program.cs`: WASM entry point. Calls `Startup.ConfigureApiHttpClient`, `ConfigureContentServices`, `ConfigureDomainServices`.
- `_Imports.razor`: Global using statements and component imports.
@@ -105,7 +105,7 @@ New modules in `DeepDrftPublic/Interop/audio/`:
- `DarkModeServiceBase`: Holds the cookie name constant (`"darkMode"`).
- `DarkModeCookieService`: Reads/writes the cookie via JS (`document.cookie` interop). Calls `DarkModeSettings.IsDarkMode = value` when the cookie changes or user toggles the button.
- Server-side `DarkModeService` (in `DeepDrftPublic`, **not here**): Reads the cookie during prerender, seeds the `DarkModeSettings` instance, rounds it through `PersistentComponentState` to the client.
- `MainLayout.razor`: Wraps entire layout in `CascadingValue` of `DarkModeSettings`, so all children see the current dark-mode state. The dark-mode toggle button (hand-rolled lit/unlit gas-lamp icon from `DDIcons.cs`) calls `DarkModeCookieService.ToggleDarkModeAsync()`.
- `MainLayout.razor`: Wraps entire layout in `CascadingValue` of `DarkModeSettings`, so all children see the current dark-mode state. The dark-mode toggle button (hand-rolled lit/unlit gas-lamp icon from `DeepDrftShared.Client/Common/DDIcons.cs`) calls `DarkModeCookieService.ToggleDarkModeAsync()`.
The flow ensures the first paint uses the correct theme (no flash), and toggling the button persists the setting to a 365-day cookie.
@@ -120,7 +120,7 @@ Component state lives in ViewModels (registered scoped in DI). Components render
- Bespoke `PaletteLight` / `PaletteDark` defined inline in `MainLayout.razor` (MudBlazor theme objects).
- CSS classes prefixed `deepdrft-` live in `DeepDrftPublic/wwwroot/styles/deepdrft-styles.css` (shared across server and client).
- Custom SVG icons: `Common/DDIcons.cs` (hand-rolled gas-lamp, etc.).
- Custom SVG icons: `DeepDrftShared.Client/Common/DDIcons.cs` (hand-rolled gas-lamp, lava-lamp, etc. — shared across public and CMS surfaces).
## Development commands