+
-
+
}
@@ -105,12 +112,24 @@
@code {
///
- /// Whether the knob band is shown. The host wires its lava-lamp toggle straight into this — the host
- /// always renders this component, and THIS component decides knob visibility (Phase 10 §4). When
+ /// Whether the knob band is shown. The popover host shows the panel whenever it is open, so the
+ /// default is true. Mix's legacy inline mount still feeds its lava-lamp toggle into this — that
+ /// mount always renders the component, and THIS component decides knob visibility (Phase 10 §4): when
/// false the knobs are @if-gated out but the container holds its reserved height (CSS min-height), so
- /// content below the bar never pops as the lamp toggles.
+ /// content below the inline bar never pops as the lamp toggles. Inside the popover the host owns
+ /// open/closed, so the default keeps the panel populated.
///
- [Parameter] public bool Visible { get; set; }
+ [Parameter] public bool Visible { get; set; } = true;
+
+ ///
+ /// When true, applies the waveform-visualizer-control-panel class to the root element,
+ /// enabling the global panel-chrome rules (dark-navy ground, border, max-width cap, pinned palette
+ /// tokens). Set by ; Mix's inline mount leaves this
+ /// false so the chrome never leaks onto the inline bar.
+ ///
+ [Parameter] public bool PanelChrome { get; set; } = false;
+
+ private string _panelChromeClass => PanelChrome ? "waveform-visualizer-control-panel" : string.Empty;
// Each handler mutates its own dedicated property then raises Changed — the bridge re-reads and
// pushes the affected dial. All values are already normalized [0,1]; the bridge maps scroll speed
diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor.css b/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor.css
index 9fe3da1..9f10fb2 100644
--- a/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor.css
+++ b/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor.css
@@ -1,13 +1,16 @@
-/* The eight-knob band. Phase 10 §4: the host ALWAYS renders this component and the component @if-gates
- the knobs on its Visible parameter. So the container is permanent and reserves its height whether or
- not the knobs are present — content below the bar never pops on toggle. No collapse machinery, no
- transitions, no glass surface. A plain transparent horizontal flex row of the eight knobs that wraps
- to a second line only if the band is genuinely too narrow.
+/* SCOPED fallback for the LEGACY inline mount only (Mix's TopRowCenter bar). The popover-hosted panel's
+ chrome lives in the GLOBAL deepdrft-styles.css (.waveform-visualizer-control-panel*) because MudPopover
+ portals its content out of this component's DOM subtree, where Blazor CSS isolation cannot reach. These
+ scoped rules apply only when the panel is mounted inline (not portaled) — i.e. Mix's existing bar.
- min-height reserves one knob-row's worth of space (knob Size=64 + icon caption + gaps + margins) so
- the empty (hidden) state occupies the same vertical box the populated single-row state does. On very
- narrow viewports a populated band may wrap to a second row and exceed this floor — the no-pop
- guarantee is exact for the common single-row (desktop) layout. */
+ Phase 10 §4: the inline host ALWAYS renders this component and the component @if-gates the knobs on its
+ Visible parameter. So the container is permanent and reserves its height whether or not the knobs are
+ present — content below the bar never pops on toggle. A plain transparent horizontal flex row of the
+ eight knobs that wraps to a second line only if the band is genuinely too narrow.
+
+ min-height reserves one knob-row's worth of space (knob Size=64 + icon caption + gaps + margins) so the
+ empty (hidden) state occupies the same vertical box the populated single-row state does. The
+ popover-panel rule in the global sheet overrides this min-height (a popover does not reserve height). */
.mix-visualizer-controls-bar {
display: flex;
flex-wrap: wrap;
@@ -19,7 +22,7 @@
}
/* 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 eight read as a tidy bar. */
.mix-visualizer-control {
display: flex;
flex-direction: column;
@@ -28,8 +31,9 @@
}
/* The caption icon is a MudIcon (a Razor component), so Blazor CSS isolation does not stamp the scope
- attribute onto its element — reach it with ::deep. Tinted to the secondary accent and the
- overlay-label opacity so it matches the session-hero NowPlaying captions (§7e). */
+ attribute onto its element — reach it with ::deep. Tinted to the primary accent and the overlay-label
+ opacity so it matches the session-hero NowPlaying captions (§7e). The portaled popover panel tints the
+ same icons via the global sheet instead. */
.mix-visualizer-control ::deep .mix-visualizer-control-icon {
color: var(--mud-palette-primary);
opacity: 0.78;
diff --git a/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css b/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css
index 5767715..3ee6e94 100644
--- a/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css
+++ b/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css
@@ -375,6 +375,50 @@ h2, h3, h4, h5, h6,
word-break: break-all;
}
+/* =============================================================================
+ WAVEFORM VISUALIZER CONTROL PANEL (Phase 12 §3d-revised / §3g)
+ The eight-knob panel hosted inside WaveformVisualizerControlPopover. MudPopover
+ PORTALS its content out of the component's DOM subtree, so Blazor CSS isolation
+ never reaches the rendered panel — its chrome must live here in the global sheet,
+ not in the scoped WaveformVisualizerControls.razor.css. (The scoped file keeps only
+ the inline-bar fallback Mix's legacy TopRowCenter mount uses, which is not portaled.)
+
+ The waveform-visualizer-control-panel class is applied ONLY when the component's
+ PanelChrome="true" parameter is set — which WaveformVisualizerControlPopover does
+ and Mix's inline mount does NOT. This prevents the chrome from leaking onto Mix's
+ inline controls bar.
+
+ The NowPlaying Hero look (§3g): dark-navy ground, green-accent knobs, light icons,
+ muted-navy filler — all from the deepdrft-* token source of truth, no hardcoded hex.
+ The RadialKnob reads --mud-palette-* for its arc/track/center/label colours; we pin
+ those palette vars to the Hero tokens ON THE PANEL so the panel reads the same
+ navy/green/off-white regardless of the page's light/dark theme.
+ ============================================================================= */
+.waveform-visualizer-control-panel.mix-visualizer-controls-bar {
+ /* Dark-navy elevated panel ground (§3g: navy-mid for the elevated surface). */
+ background: var(--deepdrft-navy-mid);
+ border: 1px solid var(--deepdrft-border-green);
+ border-radius: 8px;
+ padding: 1rem 1.25rem;
+ /* Popover panel: cap width so eight 64px knobs wrap to a tidy grid rather than one long bar.
+ This OVERRIDES the inline-bar min-height reserve (which only matters for Mix's non-popover mount). */
+ min-height: 0;
+ max-width: 340px;
+ /* Pin the MudBlazor palette vars the portaled RadialKnob consumes to the Hero tokens. */
+ --mud-palette-primary: var(--deepdrft-green-accent); /* knob value arc / pointer / center stroke */
+ --mud-palette-surface: var(--deepdrft-navy); /* knob center fill — darkest navy reads against the panel */
+ --mud-palette-surface-variant: var(--deepdrft-muted); /* knob background track — muted-navy filler (§3g) */
+ --mud-palette-text-primary: var(--deepdrft-white); /* knob value label — light (§3g) */
+}
+
+/* Green-accent caption icons (§3g: light/green icons). MudIcon is portaled here too, so this is a
+ plain global descendant selector — no ::deep, no scope attribute (CSS isolation does not reach
+ inside the popover). */
+.waveform-visualizer-control-panel .waveform-visualizer-control-icon {
+ color: var(--deepdrft-green-accent);
+ opacity: 0.85;
+}
+
@media (max-width: 419.98px) {
.deepdrft-track-detail-meta {
flex-direction: column;