diff --git a/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor b/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor index 48001b0..175cd0a 100644 --- a/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor +++ b/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor @@ -5,7 +5,17 @@ hero visual and metadata block. The Cut/Session/Mix detail pages all compose this; per-medium variance rides the Hero and MetaContent render fragments. *@ -
+@* Ambient environment layer (Phase 12 §3c/§3f mode B): an optional full-bleed layer rendered BEHIND + the scaffold content. The visualizer mounted here positions itself fixed/inset:0 (its own CSS), so + this slot's only job is to render it before the content and put the content into a foreground + stacking context above it. Absent slot = no ambient layer and no foreground promotion → today's + plain background, byte-for-byte (Liskov). *@ +@if (Ambient is not null) +{ + @Ambient +} + +
@* Two-end top row: back link (left) | optional action (right), on one SpaceBetween row. The action slot stays null for media that don't supply it (Track/Cut/Session), so SpaceBetween collapses to diff --git a/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.cs b/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.cs index e5dd727..567165f 100644 --- a/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.cs +++ b/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.cs @@ -51,6 +51,16 @@ public partial class ReleaseDetailScaffold : ComponentBase /// Medium-specific hero visual (cover art, hero image, or waveform background). [Parameter] public RenderFragment? Hero { get; set; } + /// + /// Optional full-bleed ambient layer rendered BEHIND the scaffold content (Phase 12 §3c/§3f mode B). + /// A host that wants a living environment behind hero+content — e.g. Cut supplying a + /// WaveformVisualizer — places it here. The mounted layer positions itself fixed/inset:0 + /// (its own CSS), so the scaffold only promotes its content into a foreground stacking context above + /// it. Absent = today's plain background, no regression (Liskov). Mode A (Mix) and mode C (the + /// NowPlaying card) mount the engine without this slot — see §3f. + /// + [Parameter] public RenderFragment? Ambient { get; set; } + /// /// Optional body region rendered below the meta block — the Cut album's multi-track listing. /// Single-track media leave it null. diff --git a/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.css b/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.css index 2c5ff4f..2d93fc4 100644 --- a/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.css +++ b/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.css @@ -3,3 +3,13 @@ justify-content: center; margin-top: 1.5rem; } + +/* Foreground stacking context — applied only when an Ambient layer is present. Lifts the scaffold + content above the fixed full-bleed visualizer (z-index: 0) so hero + meta + body render over the + living waveform field (Phase 12 §3c — promotes the former Mix-bespoke .mix-detail-foreground into + the shared scaffold). Without an Ambient slot this class is absent and the container keeps its + default flow, so a slot-less host renders exactly as before. */ +.deepdrft-track-detail-foreground { + position: relative; + z-index: 1; +} diff --git a/DeepDrftPublic.Client/Pages/CutDetail.razor b/DeepDrftPublic.Client/Pages/CutDetail.razor index 0a6fff9..51bb7aa 100644 --- a/DeepDrftPublic.Client/Pages/CutDetail.razor +++ b/DeepDrftPublic.Client/Pages/CutDetail.razor @@ -43,6 +43,21 @@ else BackHref="/cuts" BackLabel="All cuts" ShowShareRow="false"> + + @* Ambient living waveform behind the album hero + track list (Phase 12 §3c/§3f mode B). + Cut is multi-track: anchor to the release's EntryKey and default to the first track by + TrackNumber. The bridge follows the live playing track within the release automatically + (keys on TrackId match OR shared ReleaseEntryKey), so the field re-renders to whichever + track the listener starts; TrackEntryKey is the at-rest datum before playback. *@ + + + + @* Lava-lamp icon → popover panel (full parity, §3d-revised). Sits top-right across from the + back link, clear of the header's own Play/Share affordances below. *@ + +
@* Header split: meta + Play/Share on the LEFT, bordered cover on the RIGHT (spec §3.1). *@
diff --git a/DeepDrftPublic.Client/Pages/MixDetail.razor b/DeepDrftPublic.Client/Pages/MixDetail.razor index 454658f..acc6f1e 100644 --- a/DeepDrftPublic.Client/Pages/MixDetail.razor +++ b/DeepDrftPublic.Client/Pages/MixDetail.razor @@ -55,27 +55,12 @@ else ShowHeader="false" ShowMeta="false" ShowShareRow="false"> - - @* The eight-knob band lives in its own full-width area below the back/lamp top row. - Phase 10 §4: the control is ALWAYS rendered; the lava-lamp toggle feeds its Visible - parameter, and the control itself @if-gates the knobs while holding the container's - reserved height — so content below never pops on toggle. The band mutates the shared - WaveformVisualizerControlState; the backdrop bridge pushes the dials. A knob drag does not - toggle it — the lamp's click does. *@ - - - @* Lava-lamp button top-right, across from the back link. Toggles the knob band below the - row. The icon swaps to its FILLED variant while the band is shown (§7f / Part B). *@ - - - + @* Lava-lamp icon → popover panel, top-right across from the back link (Phase 12 + §3d-revised). Replaces the former inline TopContent knob-bar: the icon IS the toggle + and the popover IS the panel. Mix takes the cleanest anchor case (§8e) — the popover's + default bottom-right anchor opens down over the full-bleed field. *@ + @* Cover-as-background hero with all metadata overlaid, square `mix-hero` sizing. The @@ -128,11 +113,4 @@ else await PlayerService.SelectTrackStreaming(track); } } - - // Lava-lamp knob-band visibility. Pure presentation over WaveformVisualizerControlState — gates whether - // the seven-knob WaveformVisualizerControls is rendered into the TopContent band; toggling it touches no - // control value or bridge push. The lava-lamp button's filled/outline glyph is driven off this flag. - private bool _controlsExpanded; - - private void ToggleSettings() => _controlsExpanded = !_controlsExpanded; } diff --git a/DeepDrftPublic.Client/Pages/SessionDetail.razor b/DeepDrftPublic.Client/Pages/SessionDetail.razor index f48a90c..77d3e10 100644 --- a/DeepDrftPublic.Client/Pages/SessionDetail.razor +++ b/DeepDrftPublic.Client/Pages/SessionDetail.razor @@ -40,11 +40,26 @@ else // Hero image precedence: the session's dedicated hero, then the release cover, then a placeholder. var heroImage = !string.IsNullOrEmpty(heroKey) ? heroKey : release.ImagePath; - + @* Ambient living waveform behind the hero overlay (Phase 12 §3e option b / §3f mode B). Session does + NOT compose ReleaseDetailScaffold, so it mounts the shared engine directly with its own thin + full-bleed wrapper — the engine is single-source either way, only the mount differs (§3b). The + visualizer positions itself fixed/inset:0; the session-detail-foreground class lifts the content + above it. The bridge follows the live playing track; TrackEntryKey is the at-rest datum. *@ + - - ← All sessions - + + +
+ + ← All sessions + + + @* Lava-lamp icon → popover panel (full parity, §3e/§3d-revised). Anchored top-right, clear of + the hero overlay and the share/play affordances overlaid on the hero below. *@ + +
@* The overlay shows the cover thumbnail only when it differs from the resolved hero image — when there is no dedicated hero, heroImage already falls back to release.ImagePath, so the diff --git a/DeepDrftPublic.Client/Pages/SessionDetail.razor.css b/DeepDrftPublic.Client/Pages/SessionDetail.razor.css index 53b2c50..d7e9f9d 100644 --- a/DeepDrftPublic.Client/Pages/SessionDetail.razor.css +++ b/DeepDrftPublic.Client/Pages/SessionDetail.razor.css @@ -6,3 +6,20 @@ padding-top: 2rem; padding-bottom: 4rem; } + +/* Lifts the session content above the fixed full-bleed waveform layer (z-index: 0). Session mounts the + visualizer directly (it does not compose ReleaseDetailScaffold), so the foreground stacking context + lives here rather than on the scaffold (Phase 12 §3e option b). The class lands on the MudContainer's + rendered root, so ::deep is required to reach it. */ +::deep .session-detail-foreground { + position: relative; + z-index: 1; +} + +/* Back link (left) | lava-lamp popover trigger (right) on one row, mirroring the scaffold's top row. + The popover icon clears the hero overlay below and the share/play affordances overlaid on it. */ +.session-detail-top-row { + display: flex; + align-items: center; + justify-content: space-between; +}