diff --git a/PLAN.md b/PLAN.md index f201054..2e4fa8a 100644 --- a/PLAN.md +++ b/PLAN.md @@ -209,7 +209,7 @@ A **major reframe of the Mix visualizer's effects, controls, and color model**, **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 ~16–32 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). **Refined by Daniel's Wave R2 eval:** the fluid must read **flat** (an evenly-lit unified body, NOT blobs with bright pointed centers / cone-like radial gradients) and **melt into one fluid** (low viscosity / strong coalescence — "behave more like a fluid," not stiff globs in contact; the slow wax-like *motion* damping and the freely-coalescing *surface* are independent). **Energy-coupled dynamics:** higher heat/energy → **smaller bubbles + more turbulence** (many small lively turbulent bubbles at high heat; fewer, larger, calmer wax at low heat) — the lamp should feel dynamic and fun. At heat 0 the wax rests on the floor and only collision moves it (collision always on, independent of heat); at heat max many small turbulent bubbles rise/morph per second. **Tuning anchor: Daniel's sweet spot is ~20% gravity / ~100% heat** — calibrate defaults/ranges so that combination lands lively. **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 → seven 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 mush→hard up-and-out throw), (7) **waveform width** [added in the Wave R2 eval — narrows the waveform band so the lava fluid has more room to move on loud/busy songs; scales the waveform's amplitude→width mapping and its collision boundary]. **NOT a popover or drawer — an inline collapse/expand knob-bar** (decided 2026-06-16): an `@if`-guarded **animated flex row of the seven `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. +- **Controls → seven 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 mush→hard up-and-out throw), (7) **waveform width** [added in the Wave R2 eval — narrows the waveform band so the lava fluid has more room to move on loud/busy songs; scales the waveform's amplitude→width mapping and its collision boundary]. **NOT a popover or drawer — an in-flow controls container BETWEEN the back link (left) and the lava-lamp toggle (right)** on the detail top row (redesigned 2026-06-16, superseding the first realization). The first implementation read "inline collapse/expand" as an `position:absolute` bar floating under the lamp (`.mix-visualizer-controls-anchor`) — it clipped to a vertical sliver and read as a detached popover; **Daniel rejected it.** The redesign: `ReleaseDetailScaffold` gains a new optional **`TopRowCenter`** slot so the top row is `back | center-controls | lamp` (Track/Cut/Session supply neither → back-link-alone, reusability preserved); the seven `RadialKnob`s live in that center slot and **expand/collapse in the layout flow** (CSS width/opacity transition, container grows horizontally between back and lamp — **no `position:absolute`, no float, no overlay, no clipping, never overlaps the masthead/hero**). On narrow widths the container wraps in-flow to a second line / drops to the scaffold's `TopContent` band — never edge-clips. 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 `` wrapper; 24×24 viewBox); body silhouette `currentColor`, the two accent fills are commented literals traced to their `DeepDrftPalettes` source (SVG cannot resolve `var()`). diff --git a/product-notes/phase-10-mix-visualizer-lava-reframe.md b/product-notes/phase-10-mix-visualizer-lava-reframe.md index df10432..fe140fd 100644 --- a/product-notes/phase-10-mix-visualizer-lava-reframe.md +++ b/product-notes/phase-10-mix-visualizer-lava-reframe.md @@ -66,8 +66,12 @@ Cross-references (read these before implementing): - `DeepDrftPublic.Client/Controls/MixZoomMapping.cs` — reused unchanged for the scroll-speed control. - `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). -- `DeepDrftPublic.Client/Pages/MixDetail.razor[.css]` — the page; the controls area gains the inline - collapse/expand knob-bar (§7). +- `DeepDrftPublic.Client/Pages/MixDetail.razor[.css]` — the page; the controls move into an **in-flow + container between the back link and the lava-lamp** on the detail top row (§7b). The old + `.mix-visualizer-controls-anchor` + `position:absolute` floating structure is **removed**. +- `DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor[.cs]` — the detail chrome; gains a new + optional **`TopRowCenter`** slot so the top row hosts `back | center-controls | action` (§7b). The slot + stays null for Track/Cut/Session, which render the back link alone — reusability preserved. - `DeepDrftPublic.Client/Pages/SessionDetail.razor[.css]` — the **NowPlaying / hero aesthetic** the knob-bar must match (§7e). - `DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor[.css]` — the footer/player bar @@ -85,8 +89,9 @@ Cross-references (read these before implementing): 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 **three-color, OKLab-interpolated, per-segment-baked gradient** that animates along three combined -motions. Replace the four-knob popover with a **seven-knob inline collapse/expand knob-bar** styled to -match the hero NowPlaying aesthetic. Remove the static noise texture that makes the screen look dirty. +motions. Replace the four-knob popover with a **seven-knob in-flow controls container that sits between +the back link and the lava-lamp toggle** on the detail top row (reflowing the layout in place — not a +floating/absolutely-positioned bar), styled to 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.** @@ -100,8 +105,8 @@ Redraw the lava-lamp trigger glyph to the classic 1970s silhouette (§7f). height (§6). - **Seven controls** replacing the four: scroll speed, gradient rotation speed, lava gravity, lava heat, blob density/size, collision strength, waveform width (§7). -- The **inline collapse/expand knob-bar** (replacing the popover) styled to the NowPlaying hero - aesthetic (§7). +- The **in-flow controls container between the back link and the lava-lamp** (replacing the popover, and + superseding the rejected `position:absolute` floating bar) styled to the NowPlaying hero aesthetic (§7b). - A **redrawn `DDIcons.LavaLamp`** glyph — classic 1970s lava-lamp silhouette (§7f). - **Overflow clipping** to the dynamic footer height (§2). - **Removing the static noise texture** (§3). @@ -133,7 +138,7 @@ Redraw the lava-lamp trigger glyph to the classic 1970s silhouette (§7f). | Color model (HSL navy↔moss) | — | OKLab three-color gradient, 3 motions (§6) | | Glass treatment | — | folded into the blob shading (§4f); no separate glass dials | | Static noise/frost texture | — | **removed** (§3) | -| Controls (4 knobs, popover) | — | 7 knobs in an inline collapse/expand bar (§7) | +| Controls (4 knobs, popover) | — | 7 knobs in an in-flow container between back & lamp (§7) | | Lava-lamp trigger glyph | trigger placement kept | glyph **redrawn** (§7f) | --- @@ -534,41 +539,109 @@ live). Recommended starting points: scroll speed ~mid, rotation ~0.3, gravity ~0 ~0.4, collision ~0.5, width ~0.6. The **~20% gravity / ~100% heat** anchor reflects Daniel's stated sweet spot (§4c). These are feel-anchors, not commitments. -### 7b. The inline collapse/expand knob-bar (decided — NOT a popover or drawer) +### 7b. The in-flow controls container — between the back link and the lava-lamp (redesigned 2026-06-16) -**Decision (Daniel, 2026-06-16): the controls are an inline collapse/expand knob-bar, NOT a floating -popover or a drawer.** The seven 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. +**Decision (Daniel, 2026-06-16): the controls are an in-flow container that lives BETWEEN the back link +(left) and the lava-lamp toggle (right), on the detail top row. It is NOT a floating popover, NOT a +drawer, and NOT a `position:absolute` element anchored under the lamp.** Expanding it **reflows the +layout in place** — the container grows in the row's flow between back and lamp — it never overlays the +page, never clips, and never overlaps the masthead/hero. -**The mechanism (no MudPopover, no MudDrawer):** +> **This redesign supersedes the first realization of §7b.** The first implementation read the +> "inline collapse/expand" intent as an *absolutely-positioned floating bar anchored under the lamp* +> (`.mix-visualizer-controls-anchor` stacked the lamp over the bar; the bar was +> `position:absolute; top:calc(100% + 0.5rem); right:0; z-index:3`). That is a "floating-but-inline +> popover" — Daniel rejects it: it clipped to a vertical sliver (only ~4 of 7 knobs visible) and read as +> a detached window hanging off the icon. **Remove the `position:absolute` rule and the +> `.mix-visualizer-controls-anchor` floating structure entirely.** The container must take real layout +> space in the row, not float over it. -- 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 - collapse/expand toggle instead of a popover anchor. -- When toggled open, the flex row of seven knobs **animates open** via **CSS transition** — width / opacity - / transform — **expanding out from the icon / its predecessor element**, so it reads as the controls - growing in place. When toggled closed it collapses back the same way. (Seven knobs is wider than six — - see the wrap note below.) -- **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. +**Placement — restructure the scaffold's top row into three zones (recommended).** -**Why this over the Phase 10 popover.** The popover read as a detached panel hanging off the icon; Daniel -wants the controls to *be* the controls — collapsing and expanding in place. The inline animated flex row -keeps the seven knobs in the page's flow, makes the open/close a property of the controls themselves, and -avoids the popover/drawer overlay machinery entirely. (Prior-art touchstone: an inline "expand for -advanced settings" disclosure row — e.g. a toolbar that grows a secondary row of controls in place — -rather than a flyout/menu.) +`ReleaseDetailScaffold`'s top row is today a two-zone `MudStack Row Justify="SpaceBetween"`: +`back-link (left) | @TopRightAction (right)`. Restructure it into **three zones**: -**Layout note.** Seven knobs in a flex row may wrap on narrow viewports (e.g. 4×3 / 3×4 / 2-row) — a -layout call, but all seven must remain reachable, and the wrap must still read as part of the inline -collapse/expand (the whole block grows/shrinks), not as a separate surface. +``` +back-link (left) | @TopRowCenter (the controls container) | @TopRightAction (the lamp) +``` + +- **Add a new optional `RenderFragment? TopRowCenter` slot** to the scaffold, rendered between the back + link and `@TopRightAction` on the same row. The row becomes a three-child flex row: back link pinned + left, lamp pinned right, the center slot occupying the space between (and growing/shrinking with the + container's expand/collapse — see motion below). +- **Mix** supplies the seven-knob `MixVisualizerControls` to `TopRowCenter` and keeps the lava-lamp + `MudIconButton` in `TopRightAction`. The lamp toggles the container; the container reveals in the + center zone. +- **Reusability holds.** Track / Cut / Session supply neither `TopRowCenter` nor `TopRightAction`, so the + row degrades to **back-link-alone**, exactly as today. The center slot is a generic "affordance between + back and action" seam — it follows the scaffold's existing "variance rides a slot, never a flag" + convention (Phase 9 §5.3), the same way `TopRightAction` and `TopContent` already do. With + `Justify="SpaceBetween"` an absent center slot collapses to nothing and back/action sit at the two + edges unchanged. + +**Why a scaffold center slot over a MixDetail-composed row.** Composing the whole `back | controls | lamp` +row inside MixDetail (and suppressing the scaffold's own back row) would duplicate the back-link markup, +break the scaffold's "owns the back link" invariant, and fork the one place every medium's back +navigation lives. The center slot keeps the scaffold the single owner of the row and adds Mix's piece as +data, not as a structural fork. **Recommended: the scaffold gains the `TopRowCenter` slot.** +(`TopContent` — the existing below-the-row band — is the *responsive fallback target*, not the primary +home; see the responsive note in §7b-responsive below.) + +**The expand/collapse mechanism — in-flow, no float, no overlay (no MudPopover, no MudDrawer):** + +- A **bound `bool`** (`_controlsExpanded`) gates the container. The **lava-lamp icon button toggles it** — + the same `DDIcons.LavaLamp` trigger kept from Phase 10 §7c, now redrawn (§7f), now an in-flow + expand/collapse toggle. The icon swaps to its FILLED variant while expanded (§7f / Part B); a knob drag + never collapses the container (the toggle flips only on the lamp's click). +- **Collapsed:** the container occupies **no or minimal space** in the center zone — `max-width: 0` (or + `width: 0`) + `opacity: 0` + `overflow: hidden`, and `visibility: hidden` + `pointer-events: none` so + the collapsed knobs are not focusable or hit-testable. Back and lamp sit at the row's two edges with the + center collapsed between them. +- **Expanded:** the container **grows horizontally in place** between back and lamp — a `max-width` (or + `width`) + `opacity` (+ optional slight `transform`) CSS transition — so the seven knobs reveal **in the + row's flow**, pushing nothing over the page. **No `position: absolute`, no `float`, no `z-index` stacking + hack, no anchored panel.** The row reflows to accommodate the container; when the container is wider than + the available center space it wraps in-flow (see responsive, below) — it never clips to a sliver. +- **Motion intent:** a smooth horizontal grow/shrink of a real in-flow element, reading as **the controls + area opening between the back link and the lamp** — not a panel appearing over the page. Reuse the + existing transition vocabulary (the `cubic-bezier(0.22, 0.61, 0.36, 1)` easing already in + `MixVisualizerControls.razor.css`) so the motion feels native. The `max-height` collapse can stay as a + secondary axis for the wrap case, but the **primary** animated axis is horizontal width in the row. + +**Must not overlap the masthead/hero.** Because the container lives in the top row (above the masthead, +which the scaffold renders below this row) and reflows in-flow, an expanded container **pushes the row's +own height** — it must never paint over the masthead, the hero/cover, or the waveform backdrop. If the +expanded container is tall (wrapped to multiple knob rows on narrow widths), the top row grows and the +masthead moves down with normal document flow; no overlap, no clipping. + +**Why this over the rejected floating bar.** The floating `position:absolute` bar read as a detached +window hanging off the lamp and clipped because its width was constrained by the lamp's narrow column. +An in-flow container between back and lamp *is* the controls sitting in the layout — the open/close is a +property of the row reflowing, the seven knobs get the row's full horizontal space, and there is zero +overlay machinery. (Prior-art touchstone: a toolbar that grows a secondary in-flow region of controls in +place between two pinned end-affordances — e.g. a browser address bar revealing inline controls between +the back button and the menu — not a flyout anchored to an icon.) + +### 7b-responsive. Narrow-width behavior — stay in-flow, never clip + +The seven knobs are wide; on a narrow row the center zone cannot hold them on one line. **Recommended: +the container wraps to a second in-flow line under the back/lamp row** rather than clipping or scrolling +off-edge. Two acceptable realizations (staff-engineer's call, both in-flow): + +1. **Flex-wrap in place.** The seven-knob flex row `flex-wrap: wrap`s within the center zone; when the + center zone is too narrow the whole top row grows taller and the knobs reflow to 4×3 / 3×4 / two rows. + The row's height grows in-flow; the masthead moves down. All seven stay reachable. +2. **Drop to the `TopContent` band when narrow.** Below a breakpoint, the container renders in the + scaffold's existing **`TopContent` slot** (the full-width in-flow band below the back/lamp row, above + the masthead) instead of the center zone — giving it the full container width to lay the seven knobs + out without crowding the back/lamp. This is still fully in-flow (it is literally the `TopContent` + position) and never floats. Use the center zone on wide rows, the `TopContent` band on narrow rows. + +**Either way: in-flow, no overlay, no edge-clipping, no horizontal-scroll-that-hides-knobs.** A +horizontally-scrolling inline strip is a *last-resort* fallback only if wrapping proves visually worse — +and even then it must be a real in-flow scroll region with a visible affordance, never a clipped sliver. +All seven knobs must always be reachable. The wrap/drop must still read as part of the controls +container opening, not as a separate surface. ### 7c. State — widen to seven properties @@ -746,9 +819,13 @@ convention, and the theme-alignment discipline — not the exact coordinates.) 19. **Waveform width.** Dragging the waveform-width control narrows/widens the waveform band across its range; narrowing it visibly clears horizontal space for the lava (and shrinks the collision boundary the fluid parts around). The datum/scroll geometry is otherwise unchanged. -20. **Inline collapse/expand.** Clicking the lava-lamp icon expands the seven-knob flex row **in place** - (animated open/closed via CSS transition), reading as the controls collapsing/expanding — **not** a - floating popover or drawer. Clicking again collapses it; dragging a knob does not collapse it. +20. **In-flow container between back and lamp.** The controls container sits **inline on the detail top + row, between the back link (left) and the lava-lamp toggle (right)**. Clicking the lava-lamp icon + expands it **in the layout flow** (animated open/closed via CSS width/opacity transition), reflowing + the row — **not** a floating popover, drawer, or `position:absolute` bar, and **not** clipped to a + sliver. Collapsed it takes no/minimal space (back and lamp at the row's two edges). All seven knobs are + visible and horizontal when expanded; the expansion never overlaps the masthead/hero. Clicking again + collapses it; dragging a knob does not collapse it. 21. **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). 22. **Persistence + read-only.** All seven positions survive SPA nav within a session, reset on fresh @@ -795,13 +872,15 @@ sourced from `DeepDrftPalettes` with no hardcoded duplicates. **Acceptance:** § per-segment-bake requirement (§6b motion 2) uses the mix-time-keyed realization so it travels with the segment by construction (decided — §6b). -### Wave R4 — Seven controls + the NowPlaying-styled inline knob-bar +### Wave R4 — Seven controls + the NowPlaying-styled in-flow controls container Widen `MixVisualizerControlState` to **seven** properties (including the new **waveform width** control, -§7a); replace the four-knob popover with the seven-knob **inline collapse/expand knob-bar** (§7b — -`@if`-guarded animated flex row, lava-lamp icon toggles it, no popover/drawer); wire the waveform-width -control to scale the waveform-band extent and its collision boundary (§7a); style it to the session-hero -aesthetic; extend the bridge handle with the new setters (including `setWaveformWidth`). **Acceptance:** -§8 #18–#22. +§7a); add the scaffold's **`TopRowCenter`** slot and move the seven-knob `MixVisualizerControls` into an +**in-flow container between the back link and the lava-lamp** (§7b — animated horizontal grow/shrink in +the row's flow, lava-lamp icon toggles it, **no popover/drawer, no `position:absolute`**; **remove** the +old `.mix-visualizer-controls-anchor` + `position:absolute` floating structure); handle the narrow-width +in-flow wrap/drop (§7b-responsive); wire the waveform-width control to scale the waveform-band extent and +its collision boundary (§7a); style it to the session-hero aesthetic; extend the bridge handle with the +new setters (including `setWaveformWidth`). **Acceptance:** §8 #18–#22. **Dependency shape:** `Wave R1 → Wave R2 → (Wave R3 ‖ Wave R4)`. Wave R1 is a quick unblock (and folds in the independent icon redraw). Wave R2 is the prerequisite for everything visual. Waves R3 (color) and R4 @@ -814,8 +893,9 @@ ranges/transfer-functions by hand once on screen throughout (his standing prefer ## 10. Open items Tuning knobs, one undecided behavior call, and one deferred future idea — the controls-UI fork and the -per-segment-storage fork are both now **decided** (§7b inline knob-bar; §6b mix-time-keyed). None of the -items below block starting Wave R1. +per-segment-storage fork are both now **decided** (§7b in-flow controls container between back & lamp — +redesigned 2026-06-16, superseding the rejected `position:absolute` floating bar; §6b mix-time-keyed). +None of the items below block starting Wave R1. - **§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