docs(product): fold Mix Visualizer lava reframe under Phase 10 (Waves R1-R4); inline knob-bar + icon redraw

This commit is contained in:
daniel-c-harvey
2026-06-16 11:16:03 -04:00
parent d98ead97c3
commit f08b412772
3 changed files with 220 additions and 128 deletions
+17 -17
View File
@@ -200,6 +200,23 @@ Full design, renderer architecture, the four effects, acceptance criteria, and p
**Wave 4 — detail-page polish + controls rework (presentation only; the final wave).** A UI/placement pass over the Mix detail page — **no renderer, state, bridge, or mapping change.** (1) The four controls move out of the always-visible row into a **popover** (`MudPopover`, `SharePopover`-idiom) opened by a new bespoke **lava-lamp icon button** anchored **top-right of the body, across from the `← Back` link** (recommend a new `TopRightAction` slot on `ReleaseDetailScaffold`, laid as a SpaceBetween row with the back link). (2) The lava-lamp SVG lives in `DeepDrftShared.Client/Common/DDIcons.cs` in the hand-rolled gas-lamp style (`currentColor`, 24×24 viewBox, raw-string const) — a recognizable lamp with two-three suspended blobs. (3) The four `MudSlider`s become four **`RadialKnob`s** (`DeepDrftShared.Client/Components/RadialKnob.razor`) **in a row in the popover**, each carrying its existing Material icon (`ZoomIn`/`BubbleChart`/`Air`/`Palette`) **as an adjacent `MudIcon` caption** — RadialKnob has **no icon slot** (its `Label` is SVG text), so icons sit beside each knob. Knobs bind `Value`/`ValueChanged` to the **unchanged** `MixVisualizerControlState` via the **same `OnXChanged` handlers + `NotifyChanged()` seam** the sliders use today (resolution via `MixZoomMapping` fraction; other three normalized [0,1]; `HoldValue=false` for live feel). (4) **Widen the Mix body** to match the Sessions detail page — `MudContainer MaxWidth="Large"` (~1280px, up from the scaffold's 760px), Mix-scoped so Track detail is unaffected. **Depends on Wave 3 merged** (the knobs drive the Wave 3 effects) and **supersedes the controls-row design** (`product-notes/mix-visualizer-webgl-renderer.md` §3 → §7). Read-only contract intact; no knob is a seek surface. Full design + acceptance: that spec's **§7**. **Wave 4 — detail-page polish + controls rework (presentation only; the final wave).** A UI/placement pass over the Mix detail page — **no renderer, state, bridge, or mapping change.** (1) The four controls move out of the always-visible row into a **popover** (`MudPopover`, `SharePopover`-idiom) opened by a new bespoke **lava-lamp icon button** anchored **top-right of the body, across from the `← Back` link** (recommend a new `TopRightAction` slot on `ReleaseDetailScaffold`, laid as a SpaceBetween row with the back link). (2) The lava-lamp SVG lives in `DeepDrftShared.Client/Common/DDIcons.cs` in the hand-rolled gas-lamp style (`currentColor`, 24×24 viewBox, raw-string const) — a recognizable lamp with two-three suspended blobs. (3) The four `MudSlider`s become four **`RadialKnob`s** (`DeepDrftShared.Client/Components/RadialKnob.razor`) **in a row in the popover**, each carrying its existing Material icon (`ZoomIn`/`BubbleChart`/`Air`/`Palette`) **as an adjacent `MudIcon` caption** — RadialKnob has **no icon slot** (its `Label` is SVG text), so icons sit beside each knob. Knobs bind `Value`/`ValueChanged` to the **unchanged** `MixVisualizerControlState` via the **same `OnXChanged` handlers + `NotifyChanged()` seam** the sliders use today (resolution via `MixZoomMapping` fraction; other three normalized [0,1]; `HoldValue=false` for live feel). (4) **Widen the Mix body** to match the Sessions detail page — `MudContainer MaxWidth="Large"` (~1280px, up from the scaffold's 760px), Mix-scoped so Track detail is unaffected. **Depends on Wave 3 merged** (the knobs drive the Wave 3 effects) and **supersedes the controls-row design** (`product-notes/mix-visualizer-webgl-renderer.md` §3 → §7). Read-only contract intact; no knob is a seek surface. Full design + acceptance: that spec's **§7**.
### Phase 10 — Reframe (Lava): Waves R1R4
A **major reframe of the Mix visualizer's effects, controls, and color model**, folded **under this same Phase 10** as a reframe wave-set (**Waves R1R4** — labelled to stay unambiguous against the landed Waves 14 above). It builds on the landed Phase 10 renderer infrastructure (pipeline, datum texture, playhead interp, bridge, widened body, lava-lamp trigger) but **replaces what it paints**. Daniel tested the Phase 10 effects end-to-end and rejected the visual result: the lava read as "giant disconnected circles," the colors drifted to cyan (an HSL saturation-boost artifact), and the waveform and lava read as two unrelated things sharing a canvas. The diagnosis (staff-engineer research pass) is that the rejected look is **structural to the effect approach, not a tuning miss**.
**This supersedes the original Phase 10 (Waves 14) effects/controls/color design**`product-notes/mix-visualizer-webgl-renderer.md` §4 (effects) and §7 (popover-controls) are marked superseded with a pointer to the reframe spec. The renderer *infrastructure* carries forward unchanged.
**The three reframes:**
- **Lava → CPU-physics wax blobs.** Keep the single-pass WebGL2 fragment renderer; add a small CPU-side per-frame physics step modeling ~1632 Lagrangian "wax blobs" (position/velocity/temperature/radius) uploaded as uniforms and blended with `smin` SDF metaballs. The waveform and lava share **the same plane WITH real 2D elastic collision** (blob↔waveform-boundary + blob↔blob) — the waveform pushes the fluid out of its way (read-only authority preserved; the fluid never deforms the waveform). At heat 0 the wax rests at the bottom and only collision moves it (collision always on, independent of heat); at heat max many bubbles rise/morph per second. **Rejected: a full ping-pong FBO Navier-Stokes fluid sim** — a lava lamp is high-viscosity/low-turbulence, the opposite regime; large rewrite for unwanted realism. Deliberate later upgrade only.
- **Color → three-color OKLab gradient with three motions.** One source of truth (`DeepDrftPalettes`), no hardcoded hexes. Always A→B linear from the center line outward. Three combined motions: (1) anchors A/B **rotate among three theme colors X/Y/Z** at the rotation-speed control's rate — **OKLab interpolation, never through the rainbow** (the cyan fix is structural, not a tuning dial); (2) per-bar sinusoidal variation **baked at segment entry and fixed as the segment scrolls** — realized by **keying the sinusoid to mix-time** so it travels with the segment by construction (decided 2026-06-16; the explicit ring-buffer alternative is rejected for maintainability); (3) per-bar gradient curve shifts with scroll height (mostly A at bottom → mostly B at top). The static noise/frost texture is **removed** (Daniel: makes the screen look dirty).
- **Controls → six knobs in an inline collapse/expand knob-bar.** Replaces the four: (1) waveform scroll speed [replaces resolution/zoom as a standalone control], (2) gradient rotation speed, (3) lava gravity, (4) lava heat, (5) blob density/size, (6) collision strength (soft→hard). **NOT a popover or drawer — an inline collapse/expand knob-bar** (decided 2026-06-16): an `@if`-guarded **animated flex row of the six `RadialKnob`s** living inline in the controls area, expanding/collapsing **in place** (CSS width/opacity/transform transition) so it reads as the controls collapsing/expanding, not a floating surface. The lava-lamp icon button toggles a bound bool — no MudPopover/MudDrawer. Styled to **match the NowPlaying hero aesthetic** (the session-detail hero overlay — translucent dark glass, overlay-label typography, `Color.Secondary`). Also: overflow-clip the visualizer to the **dynamic footer height** (the player bar changes height minimized/expanded) so visuals stop cleanly above it; the clip line is also the lava rest line.
**Plus: redraw the lava-lamp glyph.** The current `DDIcons.LavaLamp` is rejected (Daniel: "form is shit, colors are shit"). Redraw to the classic 1970s silhouette — a wide truncated-cone metal **base**, a bulbous→roundedly-pointed teardrop **glass body**, a small cone **cap** ("offset cones") — with **navy fluid + moss blobs** (the theme's blue+green, faithful to the reference and on-theme) and a neutral/metallic base+cap. Authored in `DeepDrftShared.Client/Common/DDIcons.cs` as inner SVG markup (no `<svg>` wrapper; 24×24 viewBox); body silhouette `currentColor`, the two accent fills are commented literals traced to their `DeepDrftPalettes` source (SVG cannot resolve `var()`).
Heat→intensity and collision soft↔hard transfer functions are **staff-engineer tuning tasks** (endpoints fixed in the spec, formulas not). Full design, the wax-blob model, the collision model, the three-motion color model, the inline knob-bar, the icon redraw, observable acceptance criteria, and phasing: `product-notes/phase-10-mix-visualizer-lava-reframe.md`.
**Sequenced as four reframe waves.** `Wave R1 → Wave R2 → (Wave R3 ‖ Wave R4)`. Wave R1 (de-noise + dynamic footer clip + icon redraw) is a cheap unblock for a clean substrate. Wave R2 (wax-blob physics + 2D collision) is the load-bearing prerequisite — prove the lava before the color and the UI. Wave R3 (OKLab three-color gradient, the three motions) and Wave R4 (six controls + NowPlaying-styled inline knob-bar + widened state to six properties + extended bridge handle) both depend on Wave R2 but are independent of each other. **Both prior open Daniel calls are now decided:** controls-UI is an inline collapse/expand knob-bar (not popover/drawer); per-segment color is mix-time-keyed.
--- ---
## Phase 11 — Public Site Enhancements ## Phase 11 — Public Site Enhancements
@@ -234,23 +251,6 @@ Sequenced as **seven waves**; the critical path is `11.A → 11.B → 11.C`, wit
--- ---
## Phase 12 — Mix Visualizer Lava Reframe
A **major reframe of the Mix visualizer's effects, controls, and color model**, building on the landed Phase 10 WebGL2 renderer infrastructure (pipeline, datum texture, playhead interp, bridge, widened body, lava-lamp trigger) but **replacing what it paints**. Daniel tested the Phase 10 effects end-to-end and rejected the visual result: the lava read as "giant disconnected circles," the colors drifted to cyan (an HSL saturation-boost artifact), and the waveform and lava read as two unrelated things sharing a canvas. The diagnosis (staff-engineer research pass) is that the rejected look is **structural to the effect approach, not a tuning miss**.
**This supersedes the Phase 10 effects/controls/color design**`product-notes/mix-visualizer-webgl-renderer.md` §4 (effects) and §7 (popover-controls) are marked superseded with a pointer to the new spec. The renderer *infrastructure* carries forward unchanged.
**The three reframes:**
- **Lava → CPU-physics wax blobs.** Keep the single-pass WebGL2 fragment renderer; add a small CPU-side per-frame physics step modeling ~1632 Lagrangian "wax blobs" (position/velocity/temperature/radius) uploaded as uniforms and blended with `smin` SDF metaballs. The waveform and lava share **the same plane WITH real 2D elastic collision** (blob↔waveform-boundary + blob↔blob) — the waveform pushes the fluid out of its way (read-only authority preserved; the fluid never deforms the waveform). At heat 0 the wax rests at the bottom and only collision moves it (collision always on, independent of heat); at heat max many bubbles rise/morph per second. **Rejected: a full ping-pong FBO Navier-Stokes fluid sim** — a lava lamp is high-viscosity/low-turbulence, the opposite regime; large rewrite for unwanted realism. Deliberate later upgrade only.
- **Color → three-color OKLab gradient with three motions.** One source of truth (`DeepDrftPalettes`), no hardcoded hexes. Always A→B linear from the center line outward. Three combined motions: (1) anchors A/B **rotate among three theme colors X/Y/Z** at the rotation-speed control's rate — **OKLab interpolation, never through the rainbow** (the cyan fix is structural, not a tuning dial); (2) per-bar sinusoidal variation **baked at segment entry and fixed as the segment scrolls** (implies per-segment color state — cleanest realization: key the sinusoid to mix-time so it travels by construction); (3) per-bar gradient curve shifts with scroll height (mostly A at bottom → mostly B at top). The static noise/frost texture is **removed** (Daniel: makes the screen look dirty).
- **Controls → six knobs in a flyout.** Replaces the four: (1) waveform scroll speed [replaces resolution/zoom as a standalone control], (2) gradient rotation speed, (3) lava gravity, (4) lava heat, (5) blob density/size, (6) collision strength (soft→hard). **NOT a popover — an extended flyout menu bar** (MudBlazor survey: recommend a horizontal `MudPopover` styled as a knob bar, or `MudDrawer Anchor="Bottom"` if Daniel wants the edge-slide motion — the one Daniel call worth making up front). The six `RadialKnob`s live in the flyout, styled to **match the NowPlaying hero aesthetic** (the session-detail hero overlay — translucent dark glass, overlay-label typography, `Color.Secondary`). Also: overflow-clip the visualizer to the **dynamic footer height** (the player bar changes height minimized/expanded) so visuals stop cleanly above it; the clip line is also the lava rest line.
Heat→intensity and collision soft↔hard transfer functions are **staff-engineer tuning tasks** (endpoints fixed in the spec, formulas not). Full design, the wax-blob model, the collision model, the three-motion color model, the flyout survey, observable acceptance criteria, and phasing: `product-notes/phase-12-mix-visualizer-lava-reframe.md`.
**Sequenced as four waves.** `Wave 1 → Wave 2 → (Wave 3 ‖ Wave 4)`. Wave 1 (de-noise + dynamic footer clip) is a cheap unblock for a clean substrate. Wave 2 (wax-blob physics + 2D collision) is the load-bearing prerequisite — prove the lava before the color and the UI. Wave 3 (OKLab three-color gradient, the three motions) and Wave 4 (six controls + NowPlaying-styled flyout + widened state to six properties + extended bridge handle) both depend on Wave 2 but are independent of each other. **Open Daniel call:** flyout primitive (popover-flyout vs. bottom drawer — spec §7b/§10).
---
## Working with this file ## Working with this file
- **Add items by extending an existing phase first**; only create a new phase when the addition genuinely doesn't fit any of 15. Phase numbers are organisational, not sequencing. - **Add items by extending an existing phase first**; only create a new phase when the addition genuinely doesn't fit any of 15. Phase numbers are organisational, not sequencing.
+13 -8
View File
@@ -214,7 +214,8 @@ contract on both sides).
## 4. The four visual effects ## 4. The four visual effects
> **SUPERSEDED (2026-06-16) by `product-notes/phase-12-mix-visualizer-lava-reframe.md`.** Daniel tested > **SUPERSEDED (2026-06-16) by `product-notes/phase-10-mix-visualizer-lava-reframe.md` (the Phase 10
> reframe, Waves R1R4).** Daniel tested
> the landed effects end-to-end and rejected the visual result — the lava read as "giant disconnected > the landed effects end-to-end and rejected the visual result — the lava read as "giant disconnected
> circles," the color drifted to cyan (an HSL saturation-boost artifact), and the waveform and lava read > circles," the color drifted to cyan (an HSL saturation-boost artifact), and the waveform and lava read
> as two unrelated things. The reframe replaces this entire effects layer: the per-bar bulge + detach > as two unrelated things. The reframe replaces this entire effects layer: the per-bar bulge + detach
@@ -222,7 +223,7 @@ contract on both sides).
> of its way), the HSL navy↔moss treatment becomes a **three-color OKLab gradient with three combined > of its way), the HSL navy↔moss treatment becomes a **three-color OKLab gradient with three combined
> motions**, the separate "glass" effect folds into the blob shading, and the static noise/frost layer is > motions**, the separate "glass" effect folds into the blob shading, and the static noise/frost layer is
> **removed**. The renderer *infrastructure* (pipeline, datum texture, playhead interp, bridge) is reused; > **removed**. The renderer *infrastructure* (pipeline, datum texture, playhead interp, bridge) is reused;
> the *art* below is replaced. See the new spec §3–§6. The four-effect text below is retained as the > the *art* below is replaced. See the reframe spec §3–§6. The four-effect text below is retained as the
> record of what the rejected Wave 3 shipped. > record of what the rejected Wave 3 shipped.
Described as **intended look + shader-side approach in conceptual terms**. The exact GLSL is Described as **intended look + shader-side approach in conceptual terms**. The exact GLSL is
@@ -433,14 +434,18 @@ hand).
## 7. Wave 4 — Detail-page polish + controls rework (presentation only) ## 7. Wave 4 — Detail-page polish + controls rework (presentation only)
> **PARTIALLY SUPERSEDED (2026-06-16) by `product-notes/phase-12-mix-visualizer-lava-reframe.md`.** > **PARTIALLY SUPERSEDED (2026-06-16) by `product-notes/phase-10-mix-visualizer-lava-reframe.md` (the
> Phase 10 reframe, Waves R1R4).**
> **Kept:** the lava-lamp icon-button trigger top-right of the body across from the back link (§7c, §7f > **Kept:** the lava-lamp icon-button trigger top-right of the body across from the back link (§7c, §7f
> — `DDIcons.LavaLamp`, landed) and the widened Mix body (`MudContainer MaxWidth="Large"`, §7g, landed). > — `DDIcons.LavaLamp`, landed; the **glyph itself is redrawn** by the reframe §7f).
> **Superseded:** the four-knob **popover** becomes a six-knob **flyout** (the reframe adds two controls — > **Kept:** the widened Mix body (`MudContainer MaxWidth="Large"`, §7g, landed).
> **Superseded:** the four-knob **popover** becomes a six-knob **inline collapse/expand knob-bar** — NOT a
> popover or drawer (the reframe adds two controls —
> lava gravity, blob density/size, collision strength — and drops/recasts others: resolution/zoom is > lava gravity, blob density/size, collision strength — and drops/recasts others: resolution/zoom is
> removed in favor of scroll speed; bubblyness/detach are replaced by the physical lava model), and the > removed in favor of scroll speed; bubblyness/detach are replaced by the physical lava model). The
> popover primitive is reconsidered (`MudDrawer` vs. popover-as-flyout — see the new spec §7b). See the > controls become an `@if`-guarded animated flex row of six RadialKnobs inline in the controls area,
> new spec §7 for the six controls, the flyout survey, and the NowPlaying-hero aesthetic target. The §7 > toggled by the lava-lamp icon — see the reframe spec §7b. See the reframe spec §7 for the six controls,
> the inline knob-bar, and the NowPlaying-hero aesthetic target. The §7
> text below is retained as the record of what the four-knob popover shipped. > text below is retained as the record of what the four-knob popover shipped.
Status: **design-complete, implementation-ready.** Added 2026-06-15. **Depends on Wave 3 being merged** Status: **design-complete, implementation-ready.** Added 2026-06-15. **Depends on Wave 3 being merged**
@@ -1,14 +1,21 @@
# Mix Visualizer — Lava Reframe (Design Spec) # Mix Visualizer — Phase 10 Reframe (Lava) — Design Spec
Status: **design-complete, implementation-ready.** Author: product-designer. Date: 2026-06-16. Status: **design-complete, implementation-ready.** Author: product-designer. Date: 2026-06-16.
**No code has been written by this doc.** **No code has been written by this doc.**
This is a **major reframe of the Mix visualizer's effects layer, controls, and color model**. It This is a **major reframe of the Mix visualizer's effects layer, controls, and color model**, folded
builds on the landed Phase 10 WebGL2 renderer (Waves 14: the single-pass fragment-shader pipeline, the **under the still-open Phase 10** (the WebGL2 renderer) as a **reframe wave-set (Waves R1R4)**
loudness datum texture, the wall-clock playhead interpolation, the controls UI, the widened Mix body) distinct from Phase 10's original Waves 14 (renderer swap, controls row, the four effects, the
and **replaces what that pipeline paints** — the per-bar bulge, the analytic-metaball "lava," the glass popover/knob polish) which are **already merged**. It builds on that landed Phase 10 infrastructure (the
treatment, and the navy↔moss color treatment. The renderer *infrastructure* is reused; the *art and the single-pass fragment-shader pipeline, the loudness datum texture, the wall-clock playhead interpolation,
controls* are rebuilt. the controls UI, the widened Mix body) and **replaces what that pipeline paints** — the per-bar bulge,
the analytic-metaball "lava," the glass treatment, and the navy↔moss color treatment. The renderer
*infrastructure* is reused; the *art and the controls* are rebuilt.
> **Numbering note.** This reframe lives **under Phase 10**, not as a separate phase. Its waves are
> labelled **R1R4** to stay unambiguous against Phase 10's landed Waves 14. The filename
> `phase-10-mix-visualizer-lava-reframe.md` reflects that; an earlier draft of this doc was numbered
> "Phase 12" — that numbering is retired.
**Why a reframe, not an iteration.** Daniel tested the landed Phase 10 effects end-to-end and rejected **Why a reframe, not an iteration.** Daniel tested the landed Phase 10 effects end-to-end and rejected
the visual result: the lava reads as "giant disconnected circles," the colors drifted to cyan (an HSL the visual result: the lava reads as "giant disconnected circles," the colors drifted to cyan (an HSL
@@ -19,15 +26,15 @@ scripted blobs with no physics produce disconnected circles, and HSL interpolati
low-saturation theme tokens passes through hue regions that read as cyan. Both are fixed by changing the low-saturation theme tokens passes through hue regions that read as cyan. Both are fixed by changing the
*model*, not the dials. *model*, not the dials.
This spec **supersedes** the Phase 10 effects/controls/color design: This spec **supersedes** the original Phase 10 effects/controls/color design:
- `product-notes/mix-visualizer-webgl-renderer.md` **§4 (the four visual effects)** — superseded by - `product-notes/mix-visualizer-webgl-renderer.md` **§4 (the four visual effects)** — superseded by
§3–§6 here. §3–§6 here.
- `product-notes/mix-visualizer-webgl-renderer.md` **§7 (Wave 4 popover-controls rework)** — superseded - `product-notes/mix-visualizer-webgl-renderer.md` **§7 (Wave 4 popover-controls rework)** — superseded
by §7 here (the trigger and the widened body are kept; the four-knob popover becomes a six-knob by §7 here (the trigger and the widened body are kept; the four-knob popover becomes a six-knob
flyout, and the popover primitive is reconsidered in §7b). inline collapse/expand knob-bar — see §7).
What carries forward unchanged from Phase 10 (do **not** re-derive — reference it): What carries forward unchanged from Phase 10's landed waves (do **not** re-derive — reference it):
- The single-pass WebGL2 fragment renderer, the full-window quad, the trivial pass-through vertex - The single-pass WebGL2 fragment renderer, the full-window quad, the trivial pass-through vertex
shader (`mix-visualizer-webgl-renderer.md` §2a). shader (`mix-visualizer-webgl-renderer.md` §2a).
@@ -42,29 +49,32 @@ What carries forward unchanged from Phase 10 (do **not** re-derive — reference
`MixZoomMapping` log-space fraction↔seconds mapping. `MixZoomMapping` log-space fraction↔seconds mapping.
- The read-only contract (8.K §D): one-way playback input, no seek, no scrub, no write-back. - The read-only contract (8.K §D): one-way playback input, no seek, no scrub, no write-back.
- The widened Mix body (`MudContainer MaxWidth="Large"`) and the lava-lamp `DDIcons.LavaLamp` - The widened Mix body (`MudContainer MaxWidth="Large"`) and the lava-lamp `DDIcons.LavaLamp`
icon-button trigger top-right of the body, across from the back link (Phase 10 §7c, §7g — **kept**). icon-button trigger top-right of the body, across from the back link (Phase 10 §7c, §7g — **kept**;
the icon glyph itself is **redrawn** by this reframe, see §7f).
Cross-references (read these before implementing): Cross-references (read these before implementing):
- `product-notes/mix-visualizer-webgl-renderer.md` — the Phase 10 spec this reframes. §1/§2 (scope, - `product-notes/mix-visualizer-webgl-renderer.md` — the Phase 10 renderer spec this reframes. §1/§2
renderer architecture, bridge) carry forward; §4/§7 are superseded. (scope, renderer architecture, bridge) carry forward; §4/§7 are superseded.
- `DeepDrftPublic/Interop/visualizer/MixVisualizer.ts` — the landed renderer. The Wave 1 scroll/zoom - `DeepDrftPublic/Interop/visualizer/MixVisualizer.ts` — the landed renderer. The Wave 1 scroll/zoom
geometry, the datum texture, and the playhead machinery are reused; the §4-effect GLSL (the bubble geometry, the datum texture, and the playhead machinery are reused; the §4-effect GLSL (the bubble
SDF, the detach blobs, the HSL `mixHsl`/`vivify` color, the glass) is the part being replaced. SDF, the detach blobs, the HSL `mixHsl`/`vivify` color, the glass) is the part being replaced.
- `DeepDrftPublic.Client/Controls/MixVisualizerControls.razor[.cs]` — the four-knob control component; - `DeepDrftPublic.Client/Controls/MixVisualizerControls.razor[.cs]` — the four-knob control component;
becomes six knobs (§7). becomes six knobs in an inline collapse/expand bar (§7).
- `DeepDrftPublic.Client/Services/MixVisualizerControlState.cs` — the scoped four-property state holder; - `DeepDrftPublic.Client/Services/MixVisualizerControlState.cs` — the scoped four-property state holder;
widens to six properties (§7c). widens to six properties (§7c).
- `DeepDrftPublic.Client/Controls/MixZoomMapping.cs` — reused unchanged for the scroll-speed control. - `DeepDrftPublic.Client/Controls/MixZoomMapping.cs` — reused unchanged for the scroll-speed control.
- `DeepDrftPublic.Client/Controls/MixWaveformVisualizer.razor[.cs/.css]` — the bridge. Extend the handle - `DeepDrftPublic.Client/Controls/MixWaveformVisualizer.razor[.cs/.css]` — the bridge. Extend the handle
with the new control setters; the `.css` gains the overflow-clip work (§2). with the new control setters; the `.css` gains the overflow-clip work (§2).
- `DeepDrftPublic.Client/Pages/MixDetail.razor[.css]` — the page; the popover becomes the flyout (§7). - `DeepDrftPublic.Client/Pages/MixDetail.razor[.css]` — the page; the controls area gains the inline
collapse/expand knob-bar (§7).
- `DeepDrftPublic.Client/Pages/SessionDetail.razor[.css]` — the **NowPlaying / hero aesthetic** the - `DeepDrftPublic.Client/Pages/SessionDetail.razor[.css]` — the **NowPlaying / hero aesthetic** the
flyout must match (§7e). knob-bar must match (§7e).
- `DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor[.css]` — the footer/player bar - `DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor[.css]` — the footer/player bar
whose **dynamic height** the clip line must follow (§2c). whose **dynamic height** the clip line must follow (§2c).
- `DeepDrftShared.Client/Common/DeepDrftPalettes.cs` — the **single source of truth** for theme colors - `DeepDrftShared.Client/Common/DeepDrftPalettes.cs` — the **single source of truth** for theme colors
(§6a). `DeepDrftShared.Client/Components/RadialKnob.razor` — the knob, consumed unchanged (§7d). (§6a). `DeepDrftShared.Client/Components/RadialKnob.razor` — the knob, consumed unchanged (§7d).
- `DeepDrftShared.Client/Common/DDIcons.cs` — the lava-lamp glyph, **redrawn** by this reframe (§7f).
--- ---
@@ -75,8 +85,9 @@ Cross-references (read these before implementing):
metaballs in the existing fragment shader, sharing the *same plane* as the waveform **with real 2D metaballs in the existing fragment shader, sharing the *same plane* as the waveform **with real 2D
collision** — the waveform pushes the fluid out of its way. Replace the navy↔moss treatment with a collision** — the waveform pushes the fluid out of its way. Replace the navy↔moss treatment with a
**three-color, OKLab-interpolated, per-segment-baked gradient** that animates along three combined **three-color, OKLab-interpolated, per-segment-baked gradient** that animates along three combined
motions. Replace the four-knob popover with a **six-knob flyout** styled to match the hero NowPlaying motions. Replace the four-knob popover with a **six-knob inline collapse/expand knob-bar** styled to
aesthetic. Remove the static noise texture that makes the screen look dirty. match the hero NowPlaying aesthetic. Remove the static noise texture that makes the screen look dirty.
Redraw the lava-lamp trigger glyph to the classic 1970s silhouette (§7f).
**In scope.** **In scope.**
@@ -89,7 +100,9 @@ aesthetic. Remove the static noise texture that makes the screen look dirty.
height (§6). height (§6).
- **Six controls** replacing the four: scroll speed, gradient rotation speed, lava gravity, lava heat, - **Six controls** replacing the four: scroll speed, gradient rotation speed, lava gravity, lava heat,
blob density/size, collision strength (§7). blob density/size, collision strength (§7).
- The **flyout** (replacing the popover) styled to the NowPlaying hero aesthetic (§7). - The **inline collapse/expand knob-bar** (replacing the popover) styled to the NowPlaying hero
aesthetic (§7).
- A **redrawn `DDIcons.LavaLamp`** glyph — classic 1970s lava-lamp silhouette (§7f).
- **Overflow clipping** to the dynamic footer height (§2). - **Overflow clipping** to the dynamic footer height (§2).
- **Removing the static noise texture** (§3). - **Removing the static noise texture** (§3).
@@ -108,8 +121,8 @@ aesthetic. Remove the static noise texture that makes the screen look dirty.
**Reused vs. replaced — at a glance.** **Reused vs. replaced — at a glance.**
| Layer | Reused from Phase 10 | Replaced by this reframe | | Layer | Reused from Phase 10 (Waves 14) | Replaced by this reframe (R1R4) |
|-------|----------------------|--------------------------| |-------|----------------------------------|----------------------------------|
| WebGL2 pipeline / quad / vertex shader | ✅ as-is | — | | WebGL2 pipeline / quad / vertex shader | ✅ as-is | — |
| Datum texture + sampling | ✅ as-is | — | | Datum texture + sampling | ✅ as-is | — |
| Playhead interp + smoothing | ✅ as-is | — | | Playhead interp + smoothing | ✅ as-is | — |
@@ -120,7 +133,8 @@ aesthetic. Remove the static noise texture that makes the screen look dirty.
| Color model (HSL navy↔moss) | — | OKLab three-color gradient, 3 motions (§6) | | Color model (HSL navy↔moss) | — | OKLab three-color gradient, 3 motions (§6) |
| Glass treatment | — | folded into the blob shading (§4f); no separate glass dials | | Glass treatment | — | folded into the blob shading (§4f); no separate glass dials |
| Static noise/frost texture | — | **removed** (§3) | | Static noise/frost texture | — | **removed** (§3) |
| Controls (4 knobs, popover) | — | 6 knobs in a flyout (§7) | | Controls (4 knobs, popover) | — | 6 knobs in an inline collapse/expand bar (§7) |
| Lava-lamp trigger glyph | trigger placement kept | glyph **redrawn** (§7f) |
--- ---
@@ -383,19 +397,21 @@ Three theme colors X, Y, Z are in play. Over time, the gradient's two anchors **
rainbow** — interpolate in **OKLab** (§6c) so the blend stays faithful to the three theme colors with no rainbow** — interpolate in **OKLab** (§6c) so the blend stays faithful to the three theme colors with no
hue drift and no cyan excursion. hue drift and no cyan excursion.
**Motion 2 — per-bar sinusoidal variation, baked at segment entry.** **Motion 2 — per-bar sinusoidal variation, baked at segment entry (chosen realization: mix-time-keyed).**
Each bar's A and B vary slightly by a sinusoidal transfer, so the colors change in **"waves" across the Each bar's A and B vary slightly by a sinusoidal transfer, so the colors change in **"waves" across the
waveform** rather than one uniform gradient. **Critical implementation requirement:** a segment's colors waveform** rather than one uniform gradient. **Critical implementation requirement:** a segment's colors
are **chosen when it enters (incoming, at the bottom) and stay FIXED for that segment as the waveform are **chosen when it enters (incoming, at the bottom) and stay FIXED for that segment as the waveform
scrolls up, until it scrolls out of view.** Colors are **baked per-segment at entry, not recomputed per scrolls up, until it scrolls out of view.** Colors are **baked per-segment at entry, not recomputed per
frame.** This implies **per-segment color state tied to scroll position** — the renderer must track, for frame.**
each visible segment, the A/B colors assigned when it entered, and carry them with the segment as it
scrolls. (Flagged as a notable implementation requirement: this is per-segment state, not a stateless **Decided realization (Daniel, 2026-06-16): key the per-bar sinusoid to the segment's mix-time, not its
per-fragment function. Staff-engineer designs the storage — likely a ring buffer keyed to scroll screen-Y.** Daniel approved this approach explicitly ("whatever works and isn't a clusterfuck to
position, or baking the per-segment phase into the datum-time coordinate so the sinusoid is a pure maintain"). Because mix-time is fixed for a given segment, the sinusoid becomes a **pure function of
function of mix-time and therefore *automatically* travels with the segment. The mix-time approach is the mix-time** and is therefore **fixed-per-segment by construction** — it travels with the segment as it
cleaner realization: if the per-bar sinusoid is keyed to the segment's mix-time rather than its current scrolls, with **no explicit per-segment ring buffer to maintain.** This is the chosen approach.
screen-Y, it is fixed-per-segment *by construction* and scrolls correctly with no explicit buffer.) **Rejected for maintainability:** an explicit ring buffer keyed to scroll position that tracks each
visible segment's baked A/B colors — it is more moving parts and more state to keep coherent for the
same visual result. Do not build it; use the mix-time-keyed sinusoid.
**Motion 3 — per-bar gradient curve shifts with scroll height.** **Motion 3 — per-bar gradient curve shifts with scroll height.**
Each bar's gradient *curve* (the A→B mix profile along its own height) shifts as it scrolls up. At the Each bar's gradient *curve* (the A→B mix profile along its own height) shifts as it scrolls up. At the
@@ -405,8 +421,8 @@ scroll height (its screen-Y / scroll position), composed on top of the A/B color
for that segment. for that segment.
The three motions compose: Motion 1 sets *which* two theme colors A and B are right now (rotating among The three motions compose: Motion 1 sets *which* two theme colors A and B are right now (rotating among
X/Y/Z), Motion 2 perturbs A and B slightly per-segment in fixed-at-entry waves, and Motion 3 shifts the X/Y/Z), Motion 2 perturbs A and B slightly per-segment in fixed-at-entry waves (keyed to mix-time), and
A→B blend curve along each bar as it climbs. Motion 3 shifts the A→B blend curve along each bar as it climbs.
### 6c. OKLab, not HSL — and why ### 6c. OKLab, not HSL — and why
@@ -426,7 +442,7 @@ OKLab. Drop the `mixHsl` / `vivify` / `VIVID_*` machinery entirely.
--- ---
## 7. The six controls + the flyout ## 7. The six controls + the inline collapse/expand knob-bar
### 7a. The six controls (replacing the four) ### 7a. The six controls (replacing the four)
@@ -449,32 +465,40 @@ Defaults are Daniel's to tune on screen (his standing preference — he tunes ra
live). Recommended starting points: scroll speed ~mid, rotation ~0.3, gravity ~0.5, heat ~0.3, density live). Recommended starting points: scroll speed ~mid, rotation ~0.3, gravity ~0.5, heat ~0.3, density
~0.4, collision ~0.5. These are feel-anchors, not commitments. ~0.4, collision ~0.5. These are feel-anchors, not commitments.
### 7b. Flyout, not popover — survey of MudBlazor options ### 7b. The inline collapse/expand knob-bar (decided — NOT a popover or drawer)
**Reframe from Phase 10 §7:** the controls live in an **extended flyout menu bar**, not a popover. **Decision (Daniel, 2026-06-16): the controls are an inline collapse/expand knob-bar, NOT a floating
Clicking the lava-lamp icon makes the **RadialKnobs fly out** for editing. popover or a drawer.** The six RadialKnobs live **inline in the controls area** (where the controls sit
today) as an **`@if`-guarded flex row** that **animates open and closed in place**. It must read as
**part of the controls collapsing/expanding** — the knob row expanding out from its predecessor — **not**
a separate surface hanging off the icon.
MudBlazor options surveyed for "click an icon, knobs fly out into an editing strip": **The mechanism (no MudPopover, no MudDrawer):**
| Option | Fit | Verdict | - A **bound `bool`** (e.g. `_controlsExpanded`) gates the knob row. The **lava-lamp icon button toggles
|--------|-----|---------| it** — the same `DDIcons.LavaLamp` trigger kept from Phase 10 §7c, now redrawn (§7f), now a
| **`MudDrawer`** (`Anchor="Right"`/`Bottom"`, `Variant="Temporary"` or `"Mini"`) | A real drawer that slides in from an edge; `Open`-bound, overlay-dismissable, themeable. The "Mini" variant expands/collapses in place — close to a flyout bar. | **Recommended for an edge-anchored flyout strip.** A right or bottom temporary drawer reads as "the lava-lamp panel slid in," matches the "extended menu bar" language, and gives the six knobs room in a row/column. | collapse/expand toggle instead of a popover anchor.
| **`MudPopover` as a flyout** (the Phase 10 idiom, widened) | Anchored floating panel of arbitrary content; already in the codebase (`SharePopover`). Can be styled as a horizontal bar dropping from the icon. | **Acceptable, lighter-weight.** Closest to what Phase 10 shipped; if the flyout should *hang off the icon* rather than slide from a screen edge, a wide horizontal `MudPopover` styled as a knob bar is the smallest change. | - When toggled open, the flex row of six knobs **animates open** via **CSS transition** — width / opacity
| **`MudMenu`** | Built for actionable item lists, not a custom drag-interaction knob row. | **Rejected** — fights the knob drag/click model (same reasoning as Phase 10 §7d). | / transform — **expanding out from the icon / its predecessor element**, so it reads as the controls
| **A bespoke CSS expanding panel** (no MudBlazor primitive) | Full control of the "menu bar extends" animation; an absolutely-positioned strip that animates width/opacity from the icon. | **Fallback** if neither drawer nor popover gives the exact "menu bar flies out" motion Daniel wants. More CSS to own. | growing in place. When toggled closed it collapses back the same way.
- **Animation intent:** a smooth in-place expansion (expand-from-icon / slide-open flex row), reading as
one continuous collapse/expand of the controls area — not a panel popping into existence over the page.
Use the codebase's existing transition vocabulary (the existing controls/transition CSS) so the motion
feels native, not bolted on.
- **No floating surface.** There is no overlay, no anchored popover panel, no edge drawer. The knob row is
a real inline child of the controls area that occupies layout when open and collapses to nothing
(or to just the icon) when closed.
**Recommendation:** start with a **horizontal `MudPopover` styled as a flyout knob bar** anchored to the **Why this over the Phase 10 popover.** The popover read as a detached panel hanging off the icon; Daniel
lava-lamp icon (smallest delta from the landed §7 popover, keeps the icon-button trigger and the wants the controls to *be* the controls — collapsing and expanding in place. The inline animated flex row
outside-click/overlay idiom already wired in `MixDetail.razor`), **unless** Daniel wants the controls to keeps the six knobs in the page's flow, makes the open/close a property of the controls themselves, and
slide from a screen edge — in which case use a **`MudDrawer`** (`Anchor="Bottom"` reads most like an avoids the popover/drawer overlay machinery entirely. (Prior-art touchstone: an inline "expand for
"extended menu bar"). This is a genuine fork on the desired *motion*; both are cheap. Surfaced as the one advanced settings" disclosure row — e.g. a toolbar that grows a secondary row of controls in place —
open question worth a Daniel call (§9). rather than a flyout/menu.)
Either way: the six RadialKnobs live in the flyout, the lava-lamp icon button is the trigger (kept from **Layout note.** Six knobs in a flex row may wrap on narrow viewports (2×3 / 3×2) — a layout call, but
§7c — `DDIcons.LavaLamp`, top-right of the body across from the back link), the flyout stays open while all six must remain reachable, and the wrap must still read as part of the inline collapse/expand (the
dragging a knob (the knob's global mouse-capture overlay must not be read as an outside-click — verify; whole block grows/shrinks), not as a separate surface.
gate dismiss-on-outside-click off mid-drag if needed, as Phase 10 §7d already noted), and no flyout
element is a seek surface.
### 7c. State — widen to six properties ### 7c. State — widen to six properties
@@ -485,7 +509,7 @@ that maps to it — staff-engineer's call), `GradientRotationSpeed` (rename of `
`const` default mirrored to the TS tuning anchors (keep the C#↔TS default-sync discipline the existing `const` default mirrored to the TS tuning anchors (keep the C#↔TS default-sync discipline the existing
`Default*` consts have). Same scoped-DI persistence model: survives SPA nav within a session, resets on `Default*` consts have). Same scoped-DI persistence model: survives SPA nav within a session, resets on
fresh load. Same `Changed` event seam — the bridge subscribes and pushes the affected uniform; the fresh load. Same `Changed` event seam — the bridge subscribes and pushes the affected uniform; the
flyout component only mutates state and raises `Changed`. **This is the same architecture as today, just knob-bar component only mutates state and raises `Changed`. **This is the same architecture as today, just
six properties instead of four.** six properties instead of four.**
The bridge handle gains setters for the new controls (`setScrollSpeed`, `setGradientRotationSpeed`, The bridge handle gains setters for the new controls (`setScrollSpeed`, `setGradientRotationSpeed`,
@@ -496,7 +520,7 @@ mirroring how Phase 10 §2d extended the handle.
`RadialKnob` (`DeepDrftShared.Client/Components/RadialKnob.razor`) is consumed as-is (its API is fixed — `RadialKnob` (`DeepDrftShared.Client/Components/RadialKnob.razor`) is consumed as-is (its API is fixed —
`Value`/`ValueChanged`/`Min`/`Max`/`Step`/`Label`/`Size`/`Color`/`HoldValue`; no icon slot; `Label` is `Value`/`ValueChanged`/`Min`/`Max`/`Step`/`Label`/`Size`/`Color`/`HoldValue`; no icon slot; `Label` is
SVG text). Six knobs in the flyout, each with an adjacent `MudIcon` caption (the no-icon-slot SVG text). Six knobs in the inline bar, each with an adjacent `MudIcon` caption (the no-icon-slot
constraint from Phase 10 §7e still holds). `HoldValue=false` so they are live. `Step="0.001"` for constraint from Phase 10 §7e still holds). `HoldValue=false` so they are live. `Step="0.001"` for
continuous feel. Suggested Material icons per control (staff-engineer picks final glyphs): continuous feel. Suggested Material icons per control (staff-engineer picks final glyphs):
@@ -514,25 +538,78 @@ reachable.
### 7e. Aesthetic target — match the NowPlaying hero ### 7e. Aesthetic target — match the NowPlaying hero
**The flyout's color and structure must match the "NowPlaying" section in the hero.** The closest **The knob-bar's color and structure must match the "NowPlaying" section in the hero.** The closest
existing component to that aesthetic in the codebase is the **Session detail hero** existing component to that aesthetic in the codebase is the **Session detail hero**
(`DeepDrftPublic.Client/Pages/SessionDetail.razor` + `.css`) — the hero-dominant overlay composition: a (`DeepDrftPublic.Client/Pages/SessionDetail.razor` + `.css`) — the hero-dominant overlay composition: a
large background image with a darkening gradient shim (`.session-hero-shim`), and overlay rows large background image with a darkening gradient shim (`.session-hero-shim`), and overlay rows
(`.session-hero-top` / `.session-hero-bottom`) carrying title/artist, genre/date chips, and the play (`.session-hero-top` / `.session-hero-bottom`) carrying title/artist, genre/date chips, and the play
affordance with translucent surfaces over the image. The structural cues to borrow: affordance with translucent surfaces over the image. The structural cues to borrow:
- A **translucent dark surface** (the shim aesthetic) so the flyout reads as floating glass over the - A **translucent dark surface** (the shim aesthetic) so the knob-bar reads as floating glass over the
visualizer, not an opaque panel. visualizer, not an opaque panel.
- The **overlay-chip / overlay-label typography** (`.session-overlay-label` / `.session-overlay-value`) - The **overlay-chip / overlay-label typography** (`.session-overlay-label` / `.session-overlay-value`)
for the knob captions, so the flyout's labels match the hero's. for the knob captions, so the bar's labels match the hero's.
- `Color.Secondary` accents (the play affordance and the lava-lamp trigger both use `Color.Secondary`) — - `Color.Secondary` accents (the play affordance and the lava-lamp trigger both use `Color.Secondary`) —
keep the knobs/captions on the same accent so the flyout feels of-a-piece with the hero. keep the knobs/captions on the same accent so the bar feels of-a-piece with the hero.
Staff-engineer studies `SessionDetail.razor[.css]` and matches its surface color + structural rhythm. Staff-engineer studies `SessionDetail.razor[.css]` and matches its surface color + structural rhythm.
The intent: open the flyout and it looks like it belongs to the same design family as the session hero's The intent: expand the knob-bar and it looks like it belongs to the same design family as the session
now-playing overlay, not a generic MudBlazor panel. (If Daniel has a *specific* NowPlaying component in hero's now-playing overlay, not a generic MudBlazor panel — even though it is an inline collapse/expand
mind other than the session hero overlay, confirm — but the session hero is the strongest match in the element, not a floating one (§7b). (If Daniel has a *specific* NowPlaying component in mind other than
current tree.) the session hero overlay, confirm — but the session hero is the strongest match in the current tree.)
### 7f. Redraw the lava-lamp glyph — classic 1970s silhouette
**Decision (Daniel, 2026-06-16): the current `DDIcons.LavaLamp` is rejected ("form is shit, colors are
shit") and must be redrawn** to the classic 1970s "Lava" lamp silhouette (Daniel supplied a reference
image). The trigger placement (top-right, across from the back link) is unchanged; only the glyph art
changes.
**Home & authoring convention.** Authored in `DeepDrftShared.Client/Common/DDIcons.cs` as **inner SVG
markup only — no outer `<svg>` wrapper** (MudBlazor supplies the wrapper). A **24×24 viewBox** coordinate
space (matches the MudBlazor `viewBox="0 0 24 24"` wrapper and the existing `DDIcons` convention). A
`public const string` raw-string literal, same as the other icons in the file.
**Silhouette (bottom → top) — "offset cones":**
1. A **WIDE truncated-cone metal BASE** — widest at the very bottom, tapering *up* to a narrow waist.
2. A tall tapered **GLASS VESSEL** sitting on the base — **bulbous/rounded at the bottom**, tapering
**upward** to a **roundedly-POINTED top** (an elongated teardrop / bullet shape — pointy-ish but
**NOT sharp**).
3. A small **truncated-cone metal CAP** on top, **mirroring the base**.
The read is: cone base + tapered teardrop body + small cone cap. This is the iconic 1970s Lava lamp
profile — distinct from the current narrow straight-sided vessel, which is rejected.
**Colors (on-theme AND faithful to the reference).** The reference shows a silver metal base/cap, blue
fluid, and green blobs. Map to the app theme:
- **Fluid: navy. Blobs: moss.** Navy and moss **are** the theme's blue + green, so this is both faithful
to the reference and on-theme by construction.
- **Base + cap: neutral / metallic** (the silver of the reference).
- The **body silhouette (glass vessel outline) can be `currentColor`** so it tints with context
(`Color.Secondary`, light/dark) exactly as the existing icons do. The **fluid and blobs carry the
navy/moss accents** as the icon's color identity.
**Keeping the icon theme-aligned (the one source-of-truth wrinkle).** The single source of truth for
theme colors is `DeepDrftPalettes` (§6a), but an inline SVG `const string` in `DDIcons.cs` **cannot
resolve `var(--mud-palette-*)`** — a fill on an SVG path inside a raw-string literal must be a literal
value (or `currentColor`). So the navy/moss fluid/blob stops will need to be **literal hex stops** in the
SVG. To minimize the duplication-of-truth this creates:
- Prefer **`currentColor` for everything that can be** (the body silhouette, ideally the metal base/cap
via a neutral-tinted variant) so the bulk of the icon themes for free.
- For the two accent fills that genuinely must be literal (navy fluid, moss blobs), **use the exact
`DeepDrftPalettes` navy/moss hexes** (e.g. navy `#17283f`, moss `#3D7A68`) and **add a code comment in
`DDIcons.cs` naming the `DeepDrftPalettes` member each literal mirrors**, so the two literals are
explicitly traceable to the source and a future palette change has a documented sync point. This is the
same `currentColor`-where-possible + commented-literal-where-not discipline the existing gas-lamp flame
icon already uses (its `#FF9800`/`#FFCA28` flame stops are literals on an otherwise-`currentColor`
lamp). The hard requirement: **no silent second copy of the palette**`currentColor` first, commented
literal only where SVG forces it.
(The precise path data is staff-engineer's; this fixes the silhouette, the color mapping, the authoring
convention, and the theme-alignment discipline — not the exact coordinates.)
--- ---
@@ -572,86 +649,96 @@ current tree.)
blend stays faithful to the theme colors at all rotation phases. blend stays faithful to the theme colors at all rotation phases.
13. **Anchor rotation.** A and B rotate among X/Y/Z over time at the gradient-rotation-speed rate; 13. **Anchor rotation.** A and B rotate among X/Y/Z over time at the gradient-rotation-speed rate;
dragging the control visibly changes the rotation rate, never frozen at the slow end. dragging the control visibly changes the rotation rate, never frozen at the slow end.
14. **Per-segment bake.** A segment's colors are fixed when it enters at the bottom and travel with it 14. **Per-segment bake (mix-time-keyed).** A segment's colors are fixed when it enters at the bottom and
unchanged as it scrolls up and out — colors do not recompute under a stationary segment. travel with it unchanged as it scrolls up and out — colors do not recompute under a stationary
segment. (Realized via the mix-time-keyed sinusoid, §6b motion 2.)
15. **Per-bar curve shift.** A bar is mostly A at the bottom and mostly B by the top — color appears to 15. **Per-bar curve shift.** A bar is mostly A at the bottom and mostly B by the top — color appears to
move outward as the bar climbs. move outward as the bar climbs.
16. **One source of truth.** No hardcoded theme hexes in the visualizer; a `DeepDrftPalettes` edit or a 16. **One source of truth.** No hardcoded theme hexes in the visualizer; a `DeepDrftPalettes` edit or a
dark-mode toggle re-themes the field live with no duplicate to maintain. dark-mode toggle re-themes the field live with no duplicate to maintain. (The icon's two accent
literals are the documented, commented exception — §7f.)
**Controls / flyout** **Controls / knob-bar**
17. **Six controls.** Exactly six RadialKnobs — scroll speed, gradient rotation speed, gravity, heat, 17. **Six controls.** Exactly six RadialKnobs — scroll speed, gradient rotation speed, gravity, heat,
density/size, collision strength — each captioned with an icon; resolution/zoom as a standalone density/size, collision strength — each captioned with an icon; resolution/zoom as a standalone
control is gone. control is gone.
18. **Flyout.** Clicking the lava-lamp icon flies the knobs out (drawer or popover-flyout per the §7b 18. **Inline collapse/expand.** Clicking the lava-lamp icon expands the six-knob flex row **in place**
decision); clicking outside closes; dragging a knob does not close it. (animated open/closed via CSS transition), reading as the controls collapsing/expanding — **not** a
19. **NowPlaying aesthetic.** The flyout's surface color + structure match the session hero now-playing floating popover or drawer. Clicking again collapses it; dragging a knob does not collapse it.
19. **NowPlaying aesthetic.** The knob-bar's surface color + structure match the session hero now-playing
overlay (translucent dark glass, overlay-label typography, `Color.Secondary` accents). overlay (translucent dark glass, overlay-label typography, `Color.Secondary` accents).
20. **Persistence + read-only.** All six positions survive SPA nav within a session, reset on fresh 20. **Persistence + read-only.** All six positions survive SPA nav within a session, reset on fresh
load; no control and no flyout element is a seek/playback surface; the bridge and read-only contract load; no control and no knob-bar element is a seek/playback surface; the bridge and read-only contract
are intact. are intact.
**Icon**
21. **Redrawn lava-lamp glyph.** The trigger shows the classic 1970s silhouette — wide truncated-cone
base, bulbous→roundedly-pointed teardrop glass body, small cone cap — with navy fluid + moss blobs
and a neutral/metallic base+cap; body tints via `currentColor`; renders cleanly at ~24px.
**Performance** **Performance**
21. **60 FPS** on a mid-range desktop with heat, density, and collision at non-trivial values 22. **60 FPS** on a mid-range desktop with heat, density, and collision at non-trivial values
simultaneously; graceful degrade (drop internal resolution before frames) on weaker/mobile devices. simultaneously; graceful degrade (drop internal resolution before frames) on weaker/mobile devices.
--- ---
## 9. Suggested phasing / waves ## 9. Suggested phasing / waves (R1R4)
A **physics-and-collision first, color second, flyout third** sequence — the lava is the real work and A **physics-and-collision first, color second, knob-bar third** sequence — the lava is the real work and
the riskiest, so prove it before the gradient and the UI rework. the riskiest, so prove it before the gradient and the UI rework. **These are reframe waves R1R4, folded
under the open Phase 10 and distinct from its landed Waves 14.**
### Wave 1 — De-noise + footer clip (cheap, unblocks a clean canvas) ### Wave R1 — De-noise + footer clip + icon redraw (cheap, unblocks a clean canvas)
Remove the static noise/frost layer (§3) and implement the dynamic footer-height clip + lava-rest line Remove the static noise/frost layer (§3), implement the dynamic footer-height clip + lava-rest line
(§2c). Small, independent, and gives a clean substrate to build the lava on. **Acceptance:** §8 #3, #4. (§2c), and **redraw the `DDIcons.LavaLamp` glyph** (§7f — independent, can land anytime, grouped here as
cheap polish). Small, independent, and gives a clean substrate to build the lava on. **Acceptance:** §8
#3, #4, #21.
### Wave 2 — The wax-blob physics + collision (the load-bearing step) ### Wave R2 — The wax-blob physics + collision (the load-bearing step)
Stand up the CPU physics step (1632 blobs: position/velocity/temperature/radius), the per-frame uniform Stand up the CPU physics step (1632 blobs: position/velocity/temperature/radius), the per-frame uniform
upload, the `smin` metaball render, the heat/gravity/density mapping, and the 2D collision model (both upload, the `smin` metaball render, the heat/gravity/density mapping, and the 2D collision model (both
pairs, the soft↔hard blend). This is where the architecture is proven; it replaces the §4-effect GLSL. pairs, the soft↔hard blend). This is where the architecture is proven; it replaces the §4-effect GLSL.
**Acceptance:** §8 #1, #5#11, #21 (at this wave's workload). Controls can be temporary sliders/debug **Acceptance:** §8 #1, #5#11, #22 (at this wave's workload). Controls can be temporary sliders/debug
knobs here — the real flyout is Wave 4. knobs here — the real inline knob-bar is Wave R4.
### Wave 3 — The OKLab three-color gradient (the three motions) ### Wave R3 — The OKLab three-color gradient (the three motions)
Replace the HSL `mixHsl`/`vivify` color with OKLab interpolation; implement the three motions (anchor Replace the HSL `mixHsl`/`vivify` color with OKLab interpolation; implement the three motions (anchor
rotation among X/Y/Z, per-segment baked sinusoidal variation, per-bar curve shift), sourced from rotation among X/Y/Z, per-segment baked sinusoidal variation **keyed to mix-time**, per-bar curve shift),
`DeepDrftPalettes` with no hardcoded duplicates. **Acceptance:** §8 #12#16. The per-segment-bake sourced from `DeepDrftPalettes` with no hardcoded duplicates. **Acceptance:** §8 #12#16. The
requirement (§6b motion 2) is the subtle part — prefer the mix-time-keyed realization so it travels with per-segment-bake requirement (§6b motion 2) uses the mix-time-keyed realization so it travels with the
the segment by construction. segment by construction (decided — §6b).
### Wave 4 — Six controls + the NowPlaying-styled flyout ### Wave R4 — Six controls + the NowPlaying-styled inline knob-bar
Widen `MixVisualizerControlState` to six properties; replace the four-knob popover with the six-knob Widen `MixVisualizerControlState` to six properties; replace the four-knob popover with the six-knob
flyout (drawer or popover-flyout per §7b); style it to the session-hero aesthetic; extend the bridge **inline collapse/expand knob-bar** (§7b — `@if`-guarded animated flex row, lava-lamp icon toggles it, no
handle with the new setters. **Acceptance:** §8 #17#20. popover/drawer); style it to the session-hero aesthetic; extend the bridge handle with the new setters.
**Acceptance:** §8 #17#20.
**Dependency shape:** `Wave 1 → Wave 2 → (Wave 3 ‖ Wave 4)`. Wave 1 is a quick unblock. Wave 2 is the **Dependency shape:** `Wave R1 → Wave R2 → (Wave R3 ‖ Wave R4)`. Wave R1 is a quick unblock (and folds in
prerequisite for everything visual. Waves 3 (color) and 4 (controls/flyout) both depend on Wave 2 but are the independent icon redraw). Wave R2 is the prerequisite for everything visual. Waves R3 (color) and R4
independent of each other — the gradient can land before the flyout, or vice versa, and each control's (controls/knob-bar) both depend on Wave R2 but are independent of each other — the gradient can land
effect is independently tunable. Daniel tunes ranges/transfer-functions by hand once on screen before the knob-bar, or vice versa, and each control's effect is independently tunable. Daniel tunes
throughout (his standing preference). ranges/transfer-functions by hand once on screen throughout (his standing preference).
--- ---
## 10. Open items ## 10. Open items
Tuning knobs and one genuine fork. None block starting Wave 1. Tuning knobs only — the controls-UI fork and the per-segment-storage fork are both now **decided** (§7b
inline knob-bar; §6b mix-time-keyed). None block starting Wave R1.
- **§7b — flyout primitive (the one Daniel call worth making up front):** horizontal `MudPopover`
styled as a knob bar (smallest delta, hangs off the icon) **vs.** `MudDrawer Anchor="Bottom"` (slides
from a screen edge, reads most like an "extended menu bar"). Both cheap; the choice is about the
*motion* Daniel wants. Recommend popover-flyout unless he wants the edge-slide.
- **§4c, §5d — transfer functions:** heat 0..1 → rise/morph intensity; collision 0..1 → soft↔hard blend - **§4c, §5d — transfer functions:** heat 0..1 → rise/morph intensity; collision 0..1 → soft↔hard blend
shape; restitution coefficients; penetration-penalty curve. All staff-engineer tuning tasks with the shape; restitution coefficients; penetration-penalty curve. All staff-engineer tuning tasks with the
endpoints fixed here. endpoints fixed here.
- **§4g — blob count band** within 1632 and the density-control mapping to count vs. radius. - **§4g — blob count band** within 1632 and the density-control mapping to count vs. radius.
- **§6a — exact X/Y/Z theme-var bindings** per light/dark for the richest spread (the palette is the - **§6a — exact X/Y/Z theme-var bindings** per light/dark for the richest spread (the palette is the
source either way). source either way).
- **§6b motion 2 — per-segment color storage** (ring buffer vs. mix-time-keyed sinusoid — recommend
mix-time-keyed so it travels by construction).
- **§7a — scroll-speed mapping:** reuse `MixZoomMapping` as a scroll-rate map vs. a fresh linear map. - **§7a — scroll-speed mapping:** reuse `MixZoomMapping` as a scroll-rate map vs. a fresh linear map.
- **§7e — NowPlaying source:** confirm the session hero overlay is the intended "NowPlaying" aesthetic - **§7e — NowPlaying source:** confirm the session hero overlay is the intended "NowPlaying" aesthetic
(strongest match in the tree) or point to a different component. (strongest match in the tree) or point to a different component.
- **§7f — icon accent literals:** the two literal navy/moss stops the SVG forces (commented to their
`DeepDrftPalettes` source) — exact hexes are staff-engineer's to pull from the palette.
- **Defaults** for all six controls — Daniel tunes on screen. - **Defaults** for all six controls — Daniel tunes on screen.