Merge p15-w1-visualizer-controls into dev

Phase 15 — visualizer control-deck rework: screen-centered tinted MudOverlay
(NowPlayingCard chrome), deterministic three-row LAVA/WAVE layout, lava/waveform
lamp toggles backed by a genuine per-subsystem draw-skip, scroll/zoom slider,
playful tooltips, green=interactive/light=static colour principle.
This commit is contained in:
daniel-c-harvey
2026-06-17 14:44:52 -04:00
8 changed files with 481 additions and 188 deletions
+131 -29
View File
@@ -376,49 +376,151 @@ h2, h3, h4, h5, h6,
}
/* =============================================================================
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.)
WAVEFORM VISUALIZER CONTROL PANEL (Phase 12 §3d-revised / §3g → Phase 15 re-layout)
The control deck hosted inside WaveformVisualizerControlPopover, now a screen-centered
tinted MudOverlay (Phase 15 §4). MudOverlay — like the former MudPopover — PORTALS its
content out of the component's DOM subtree, so Blazor CSS isolation never reaches the
rendered panel: its chrome, the three-row/section LAYOUT, the section labels, the slider,
and the toggles all live here in the global sheet, not in the scoped
WaveformVisualizerControls.razor.css. (The scoped file keeps only the legacy inline-bar
fallback Mix's old TopRowCenter mount used, 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.
PanelChrome="true" parameter is set — which the popover host does and Mix's inline mount
does NOT — so the chrome never leaks onto an inline 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.
CHROME (Phase 15 §5 — NowPlayingCard treatment): SQUARE corners, lighter-navy ground
(navy-mid), a thin LIGHT border (--deepdrft-border-light, the NowPlayingCard 0.12-alpha
light-on-dark idiom as a token). All token-sourced; no hardcoded hex.
COLOUR PRINCIPLE (§5 — green = interactive, light = non-interactive): the RadialKnob reads
--mud-palette-* for its arc/pointer/center/label; we pin --mud-palette-primary to the green
accent (interactive arcs/pointers) and --mud-palette-text-primary to light. Caption icons and
section labels are LIGHT (static). The slider track/thumb and the lamp toggles are green.
============================================================================= */
.waveform-visualizer-control-panel.mix-visualizer-controls-bar {
/* Dark-navy elevated panel ground (§3g: navy-mid for the elevated surface). */
/* Lighter-navy elevated panel ground (§5: navy-mid). */
background: var(--deepdrft-navy-mid);
border: 1px solid var(--deepdrft-border-green);
border-radius: 8px;
/* Square corners + thin light border — NowPlayingCard chrome (§5). */
border: 1px solid var(--deepdrft-border-light);
border-radius: 0;
/* Optional backdrop blur — cheap on a small modal panel, nice over the visualizer (§5). */
backdrop-filter: blur(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). */
/* Three-row sectioned deck: stack the rows top-to-bottom; conditional rows reserve no permanent
height (§3 reflow discipline). This OVERRIDES the inline-bar min-height + flex-wrap (which only
matter for Mix's non-portaled legacy mount). */
display: flex;
flex-direction: column;
gap: 0.75rem;
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 */
max-width: 420px;
/* Pin the MudBlazor palette vars the portaled RadialKnob + slider consume. */
--mud-palette-primary: var(--deepdrft-green-accent); /* knob arc/pointer + slider track/thumb (interactive) */
--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) */
--mud-palette-surface-variant: var(--deepdrft-muted); /* knob background track — muted-navy filler */
--mud-palette-text-primary: var(--deepdrft-white); /* knob value label — light */
}
/* 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);
/* ── Row layout (§3). Each row is a horizontal band. Row 1 (MODE) and row 3 (WAVE) use
space-between so the right-pinned control (color / width) hugs the far edge. Row 2 (LAVA) uses
flex-start so its label + four knobs group left rather than spreading edge-to-edge. ── */
.waveform-visualizer-control-panel .wvc-row {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 0.85rem 1rem;
}
/* Row 1 (MODE): two direct flex children — the left toggle group and the color knob tooltip wrapper.
space-between pins the color knob to the far right and keeps it there when collisions hides. */
.waveform-visualizer-control-panel .wvc-row-mode {
justify-content: space-between;
}
/* Row 2 (LAVA): label + four knobs group left — no right-pinned control. */
.waveform-visualizer-control-panel .wvc-row-section {
justify-content: flex-start;
}
/* Row 3 (WAVE): label + scroll-slider + width-knob tooltip wrappers are direct flex children.
space-between pins the width knob to the far right while the label + slider sit left. */
.waveform-visualizer-control-panel .wvc-row-wave {
justify-content: space-between;
}
/* The left group of row 1 (toggles + conditional collisions) flows left; the color knob is the
space-between right sibling, so it stays put when collisions hides (§3). */
.waveform-visualizer-control-panel .wvc-row-left {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 0.85rem 1rem;
}
/* ── Section label "LAVA:" / "WAVE:" (§3, §5). NowPlayingCard .np-label TYPOGRAPHY (mono, uppercase,
tracked), recoloured LIGHT — labels are static, so light by the colour principle (§5, §10.3). ── */
.waveform-visualizer-control-panel .wvc-section-label {
font-family: var(--deepdrft-font-mono);
font-size: 0.6rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--deepdrft-white);
align-self: center;
flex: 0 0 auto;
opacity: 0.85;
}
/* ── The lamp toggles (§3 row 1). Iconographic lit/unlit lamp glyph, GREEN because interactive (§5).
Color="Color.Primary" already drives the glyph currentColor to the pinned green --mud-palette-primary;
this just sizes the hit-target to read as a row-1 peer of the knobs. ── */
.waveform-visualizer-control-panel .wvc-toggle {
display: flex;
align-items: center;
justify-content: center;
}
/* ── The scroll SLIDER (§8). Track/thumb green (the pinned --mud-palette-primary, interactive). Give it
a sensible width so it reads as "position along a continuum" next to the rotary width knob. ── */
.waveform-visualizer-control-panel .wvc-slider {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.3rem;
min-width: 160px;
flex: 1 1 auto;
align-self: center;
}
.waveform-visualizer-control-panel .wvc-slider .mud-slider {
width: 100%;
}
/* Caption icons + section labels render LIGHT (§5/§9 colour principle: static/decorative = light). 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 overlay). The knob arcs/pointers + slider stay green (interactive). */
.waveform-visualizer-control-panel .waveform-visualizer-control-icon {
color: var(--deepdrft-white);
opacity: 0.85;
}
/* ── The modal overlay (Phase 15 §4). MudOverlay is already a full-viewport flex scrim that centers its
content (.mud-overlay { display:flex; align-items:center; justify-content:center }), which gives the
screen-centered panel on every host for free — we do NOT fight that positioning. We only (a) set the
mild modal tint from the SINGLE --deepdrft-modal-scrim-alpha token (§10.5, one point of change) and
(b) cap the centered content's height so a tall both-on deck scrolls inside the modal rather than
overflowing the viewport. The overlay portals to the body, so these are plain global rules (no scope
attribute). The doubled .mud-overlay-scrim.mud-overlay-dark selector (0,2,0) outranks MudBlazor's own
.mud-overlay-dark (0,1,0), so the tint wins regardless of stylesheet load order. ── */
.waveform-visualizer-control-overlay .mud-overlay-scrim.mud-overlay-dark {
background-color: rgba(var(--deepdrft-scrim-rgb), var(--deepdrft-modal-scrim-alpha));
}
.waveform-visualizer-control-overlay .mud-overlay-content {
max-height: 90vh;
overflow-y: auto;
}
@media (max-width: 419.98px) {
.deepdrft-track-detail-meta {
flex-direction: column;