Merge p10-controls-inline into dev (Phase 10 reframe: in-flow controls container between back link and lava-lamp, TopRowCenter slot)

This commit is contained in:
daniel-c-harvey
2026-06-16 20:15:38 -04:00
6 changed files with 80 additions and 62 deletions
@@ -1,21 +1,21 @@
/* The seven-knob bar lives INLINE in the mix-detail controls area and animates open/closed in place /* The seven-knob container lives IN-FLOW in the scaffold's top-row center zone, between the back link
(lava reframe §7b) — NOT a popover or drawer. Collapsed, it has zero size and is fully transparent; and the lava-lamp toggle, and grows open/closed in place (lava reframe §7b) — NOT a popover, drawer,
the @Expanded flag (mirrored to the .is-expanded class) transitions it open. We animate max-width + or floating overlay. Collapsed, it has zero footprint and is fully transparent; the @Expanded flag
max-height + opacity + transform together so the bar reads as the controls growing in place rather (mirrored to the .is-expanded class) transitions it open by growing its width. The PRIMARY animated
than a panel popping in. Closed state is pointer-events:none + visibility:hidden so collapsed knobs axis is horizontal width — the controls area opening between the back link and the lamp; max-height is
are not focusable or hit-testable. */ a secondary axis for the wrap case. Closed state is pointer-events:none + visibility:hidden so
collapsed knobs are not focusable or hit-testable. */
.mix-visualizer-controls-bar { .mix-visualizer-controls-bar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-start; align-items: flex-start;
justify-content: flex-end; justify-content: flex-start;
gap: 0.85rem 1rem; gap: 0.85rem 1rem;
/* Collapsed: zero footprint, slid up toward the toggle, transparent. */ /* Collapsed: zero horizontal footprint, transparent, no layout space. */
max-width: 0; max-width: 0;
max-height: 0; max-height: 0;
opacity: 0; opacity: 0;
transform: translateY(-8px);
overflow: hidden; overflow: hidden;
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
@@ -33,20 +33,33 @@
max-width 0.32s cubic-bezier(0.22, 0.61, 0.36, 1), max-width 0.32s cubic-bezier(0.22, 0.61, 0.36, 1),
max-height 0.32s cubic-bezier(0.22, 0.61, 0.36, 1), max-height 0.32s cubic-bezier(0.22, 0.61, 0.36, 1),
opacity 0.24s ease, opacity 0.24s ease,
padding 0.32s cubic-bezier(0.22, 0.61, 0.36, 1), padding 0.32s cubic-bezier(0.22, 0.61, 0.36, 1);
transform 0.32s cubic-bezier(0.22, 0.61, 0.36, 1);
} }
/* Expanded: grows horizontally in the row's flow. Wide enough to hold all seven 64px knobs (with
captions and gaps) on one line where the row has room; on a narrower center zone the knobs flex-wrap
to a second in-flow line and max-height absorbs the taller stack — never clipped, never floating. */
.mix-visualizer-controls-bar.is-expanded { .mix-visualizer-controls-bar.is-expanded {
max-width: 640px; max-width: 720px;
max-height: 420px; max-height: 420px;
opacity: 1; opacity: 1;
transform: translateY(0);
visibility: visible; visibility: visible;
pointer-events: auto; pointer-events: auto;
padding: 0.85rem 1rem; padding: 0.85rem 1rem;
} }
/* Narrow rows: the controls container can't sit beside the back link and lamp on one line, so it takes
the full row width and the scaffold's flex-wrapped top row drops it to its own in-flow line below the
back/lamp pair (§7b-responsive). Still fully in-flow — never floats, never clips. The seven knobs get
the row's full width and wrap within it. */
@media (max-width: 959.98px) {
.mix-visualizer-controls-bar.is-expanded {
flex-basis: 100%;
max-width: 100%;
justify-content: center;
}
}
/* One control: a RadialKnob with its Material icon caption underneath. RadialKnob has no icon slot, so /* One control: a RadialKnob with its Material icon caption underneath. RadialKnob has no icon slot, so
the icon rides adjacent (§7d). Center the pair so the seven read as a tidy bar. */ the icon rides adjacent (§7d). Center the pair so the seven read as a tidy bar. */
.mix-visualizer-control { .mix-visualizer-control {
@@ -7,15 +7,23 @@
<div class="deepdrft-track-detail-container"> <div class="deepdrft-track-detail-container">
@* Back link top-left, optional medium action top-right, on one SpaceBetween row. The action slot @* Three-zone top row: back link (left) | optional center affordance | optional action (right), on
stays null for media that don't supply it (Track/Session), so they render the back link alone. *@ one SpaceBetween row. Both the center and action slots stay null for media that don't supply them
<MudStack Row AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween"> (Track/Cut/Session), so SpaceBetween collapses to the back link alone at the left edge. The Mix
<MudLink Href="@BackHref" Typo="Typo.body2" Class="deepdrft-track-detail-back"> detail page fills the center with its in-flow visualizer-controls container (§7b). The native
&larr; @BackLabel wrapper lets the row flex-wrap the center zone to its own line on narrow widths, keeping the
</MudLink> controls in-flow rather than clipping. *@
<div class="deepdrft-track-detail-top-row">
<MudStack Row AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="deepdrft-track-detail-top-row-stack">
<MudLink Href="@BackHref" Typo="Typo.body2" Class="deepdrft-track-detail-back">
&larr; @BackLabel
</MudLink>
@TopRightAction @TopRowCenter
</MudStack>
@TopRightAction
</MudStack>
</div>
@TopContent @TopContent
@@ -31,6 +31,15 @@ public partial class ReleaseDetailScaffold : ComponentBase
/// </summary> /// </summary>
[Parameter] public RenderFragment? TopContent { get; set; } [Parameter] public RenderFragment? TopContent { get; set; }
/// <summary>
/// Optional affordance rendered in the center of the top row, between the back link (left) and
/// <see cref="TopRightAction"/> (right). The Mix detail page uses it for the in-flow visualizer-
/// controls container that grows in place between the back link and the lava-lamp toggle (§7b);
/// other media leave it null, so the SpaceBetween row collapses the center and renders the back
/// link alone. Variance rides the slot, never a flag (Phase 9 §5.3).
/// </summary>
[Parameter] public RenderFragment? TopRowCenter { get; set; }
/// <summary> /// <summary>
/// Optional action rendered at the top-right of the container, on the same SpaceBetween row as the /// Optional action rendered at the top-right of the container, on the same SpaceBetween row as the
/// back link (back link left, action right). The Mix detail page uses it for the lava-lamp /// back link (back link left, action right). The Mix detail page uses it for the lava-lamp
@@ -3,3 +3,12 @@
justify-content: center; justify-content: center;
margin-top: 1.5rem; margin-top: 1.5rem;
} }
/* The three-zone top row: back link | center affordance | action. The center zone (the Mix
visualizer-controls container) grows in-flow between the two pinned ends. On narrow widths the row
flex-wraps so the center zone drops to its own line below the back/action pair — keeping the seven
knobs in-flow with the full row width rather than clipping. The MudStack output is a child Razor
component's native div, so ::deep is required to reach it. */
::deep .deepdrft-track-detail-top-row-stack {
flex-wrap: wrap;
}
+20 -19
View File
@@ -48,26 +48,27 @@ else
BackLabel="All mixes" BackLabel="All mixes"
ShowMeta="@(hasGenre || hasDate)" ShowMeta="@(hasGenre || hasDate)"
ShowShareRow="false"> ShowShareRow="false">
<TopRowCenter>
@* In-flow seven-knob control container, between the back link and the lava-lamp toggle.
It grows in place (width/opacity transition) when expanded and collapses to zero
footprint when closed — never a popover, drawer, or floating overlay (§7b). The
container mutates the shared MixVisualizerControlState; the backdrop bridge pushes the
dials. A knob drag does not collapse it — the toggle flips only on the lamp's click. *@
<MixVisualizerControls Expanded="@_controlsExpanded" />
</TopRowCenter>
<TopRightAction> <TopRightAction>
@* Lava-lamp button top-right, across from the back link. Toggles the INLINE seven-knob @* Lava-lamp button top-right, across from the back link. Toggles the in-flow control
control bar that animates open/closed in place below it (lava reframe §7b) — not a container in the center zone. The icon swaps to its FILLED variant while the
popover or drawer. The icon swaps to its FILLED variant while the bar is expanded container is expanded (§7f / Part B). *@
(§7f / Part B). The controls bar mutates the shared MixVisualizerControlState; the <MudTooltip Text="Visualizer settings">
backdrop bridge pushes the dials. A knob drag does not collapse the bar — the toggle <MudIconButton Icon="@(_controlsExpanded ? DDIcons.LavaLampFilled : DDIcons.LavaLamp)"
only flips on this button's click, never on a drag landing in the bar. *@ Size="Size.Large"
<div class="mix-visualizer-controls-anchor"> Color="Color.Secondary"
<MudTooltip Text="Visualizer settings"> Disabled="@(!RendererInfo.IsInteractive)"
<MudIconButton Icon="@(_controlsExpanded ? DDIcons.LavaLampFilled : DDIcons.LavaLamp)" OnClick="@ToggleSettings"
Size="Size.Large" aria-label="Visualizer settings"
Color="Color.Secondary" aria-expanded="@_controlsExpanded" />
Disabled="@(!RendererInfo.IsInteractive)" </MudTooltip>
OnClick="@ToggleSettings"
aria-label="Visualizer settings"
aria-expanded="@_controlsExpanded" />
</MudTooltip>
<MixVisualizerControls Expanded="@_controlsExpanded" />
</div>
</TopRightAction> </TopRightAction>
<Hero> <Hero>
<div class="mix-detail-cover"> <div class="mix-detail-cover">
@@ -4,28 +4,6 @@
z-index: 1; z-index: 1;
} }
/* The lava-lamp toggle + its inline knob-bar. The anchor stacks the button over the bar and lets the
bar grow downward/leftward in place when expanded, without shoving the masthead. The bar itself is
absolutely positioned under the button (top-right of the detail body), so its open/close animation
reads as the controls growing out from the icon rather than reflowing the page (lava reframe §7b). */
.mix-visualizer-controls-anchor {
position: relative;
display: flex;
flex-direction: column;
align-items: flex-end;
}
/* MixVisualizerControls renders the .mix-visualizer-controls-bar as its single root. It is a child
Razor component, so its scope attribute is not stamped here — reach the bar with ::deep to position
it as a floating-but-inline element anchored to the toggle's bottom-right. */
.mix-visualizer-controls-anchor ::deep .mix-visualizer-controls-bar {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
z-index: 3;
transform-origin: top right;
}
/* Medium square cover — deliberately smaller than the 360px cut cover so the /* Medium square cover — deliberately smaller than the 360px cut cover so the
waveform backdrop keeps room. The placeholder/art MudPaper fills this frame. */ waveform backdrop keeps room. The placeholder/art MudPaper fills this frame. */
.mix-detail-cover { .mix-detail-cover {