polish(p15): mute panel, revert scroll to knob, waveform icon + strong toggle state

Mute --deepdrft-panel-ground; WAVE scroll MudSlider back to RadialKnob; new DDIcons Waveform/WaveformFilled glyph for the waveform toggle; strong green ON-state chip vs dim OFF; refresh popover pointer-capture comment.
This commit is contained in:
daniel-c-harvey
2026-06-17 18:03:16 -04:00
parent 412b96ba16
commit 6ecc7f1f37
5 changed files with 80 additions and 50 deletions
@@ -15,13 +15,15 @@
mild modal tint (alpha from the single --deepdrft-modal-scrim-alpha token, §10.5). There is therefore
no AnchorOrigin/TransformOrigin: a centered modal has no anchor (Phase 15 drops those parameters).
KNOB-DRAG SAFETY (Phase 15 §4, highest-risk detail): RadialKnob mounts its own full-viewport
position:fixed; z-index:9999 mouse-capture div WHILE dragging (RadialKnob.razor lines 59). That capture
div sits ABOVE the overlay scrim, so a knob drag's pointer-up lands on the capture div, never the scrim
— the overlay's OnClick does not fire mid-drag, so releasing the mouse outside the panel does NOT close
the modal. AutoClose is left OFF (the default) and dismissal is via the explicit scrim OnClick only,
carrying the SharePopover idiom forward under the new primitive. The panel stops click propagation so a
click INSIDE it never bubbles to the scrim's close handler.
KNOB-DRAG SAFETY (Phase 15 §4, highest-risk detail): RadialKnob calls setPointerCapture on its own
knob element when a drag begins, so all pointermove/pointerup/pointercancel events for that pointer are
delivered to the knob element regardless of where the cursor moves. The synthesised click on pointerup
is also retargeted to the knob, not to whatever element is under the cursor — so releasing outside the
panel never fires the scrim's close handler. The full-viewport position:fixed; z-index:9999 div
RadialKnob renders while dragging is a belt-and-suspenders UX guard (blocks stray clicks on underlying
content) but is no longer the load-bearing anti-dismiss mechanism. AutoClose is left OFF (the default)
and dismissal is via the explicit scrim OnClick only. The panel stops click propagation so a click
INSIDE it never bubbles to the scrim's close handler.
The host owns NO control state and NO JS interop. The hosted WaveformVisualizerControls panel mutates
the shared WaveformVisualizerControlState and raises Changed; the visualizer bridge subscribes. This
@@ -3,19 +3,19 @@
@using DeepDrftPublic.Client.Services
@inject WaveformVisualizerControlState ControlState
@* The waveform visualizer control PANEL (Phase 12 §3d-revised → Phase 15 re-layout). The control deck for
the lava-lamp visualizer: a deterministic THREE-ROW, sectioned layout that encodes what the visualizer
composes — a LAVA field and a WAVEFORM ribbon, each independently toggleable (Phase 15 §3):
@* The waveform visualizer control PANEL (Phase 12 §3d-revised → Phase 15 re-layout → Phase 15 polish).
The control deck for the lava-lamp visualizer: a deterministic THREE-ROW, sectioned layout that encodes
what the visualizer composes — a LAVA field and a WAVEFORM ribbon, each independently toggleable (§3):
Row 1 (MODE, always): lava lamp-toggle, waveform lamp-toggle, [collisions knob — only when BOTH on],
then the color knob pinned far-right (applies to the whole field, so always visible).
Row 1 (MODE, always): lava lamp-toggle, waveform toggle (new waveform glyph), [collisions knob — only
when BOTH on], then the color knob pinned far-right (applies to the whole field, always visible).
Row 2 (LAVA, only when lava on): "LAVA:" label + gravity / heat / fluid-amount / fluid-viscosity.
Row 3 (WAVE, only when waveform on): "WAVE:" label + scroll SLIDER + width knob pinned far-right.
Row 3 (WAVE, only when waveform on): "WAVE:" label + scroll RadialKnob + width RadialKnob (far-right).
The two lamp toggles are iconographic (lit LavaLampFilled / unlit LavaLamp glyph), green because they
are INTERACTIVE (the §5 colour principle: green = interactive, light = non-interactive). The eight
continuous dials are unchanged in tuning; the one widget-type change is scroll/zoom → a MudSlider (§8,
bound to ScrollSpeed alone). None of these is a seek surface (read-only contract §D).
The two toggles have STRONG ACTIVE-STATE styling: when ON the toggle chip has a green-accent background
(unmistakably active); when OFF it is muted/dim. The lava toggle keeps the lava-lamp glyph; the waveform
toggle uses a new distinct waveform-bars glyph (DDIcons.Waveform / WaveformFilled). Green = interactive
(§5 colour principle); light = non-interactive. All colours are token-sourced — no hardcoded hex.
This is the PANEL CONTENT hosted inside WaveformVisualizerControlPopover, now a screen-centered tinted
MudOverlay (Phase 15 §4). Because the overlay PORTALS its content out of this component's DOM subtree,
@@ -24,10 +24,6 @@
scoped .razor.css carries only the legacy inline-bar fallback (Mix's old non-portaled mount), which may
now be dead post-Phase-12 but is left in place — flagged, not cut (out of Phase 15 scope).
COLOUR PRINCIPLE (§5): the lamp toggles + knob arcs/pointers + the slider track/thumb are green-accent
(interactive); the "LAVA:"/"WAVE:" section labels and the knob caption icons are LIGHT (static). All
colours are token-sourced (deepdrft-tokens.css) — no hardcoded hex.
It owns NO JS interop: it mutates the shared, session-scoped WaveformVisualizerControlState and raises
its Changed event. The visualizer bridge (WaveformVisualizer) subscribes and pushes the affected dial /
subsystem-enable to the WebGL module. RadialKnob has no icon slot (its Label renders as SVG text) and no
@@ -43,7 +39,7 @@
<div class="wvc-row-left">
<MudTooltip Text="Light the lamp — or let it go cold.">
<div class="wvc-toggle" role="group" aria-label="Lava field on or off">
<div class="wvc-toggle @(ControlState.LavaEnabled ? "wvc-toggle-on" : "wvc-toggle-off")" role="group" aria-label="Lava field on or off">
<MudIconButton Icon="@(ControlState.LavaEnabled ? DDIcons.LavaLampFilled : DDIcons.LavaLamp)"
Color="Color.Primary"
OnClick="@ToggleLava"
@@ -53,8 +49,8 @@
</MudTooltip>
<MudTooltip Text="Show the sound, or hide the ribbon.">
<div class="wvc-toggle" role="group" aria-label="Waveform ribbon on or off">
<MudIconButton Icon="@(ControlState.WaveformEnabled ? DDIcons.LavaLampFilled : DDIcons.LavaLamp)"
<div class="wvc-toggle @(ControlState.WaveformEnabled ? "wvc-toggle-on" : "wvc-toggle-off")" role="group" aria-label="Waveform ribbon on or off">
<MudIconButton Icon="@(ControlState.WaveformEnabled ? DDIcons.WaveformFilled : DDIcons.Waveform)"
Color="Color.Primary"
OnClick="@ToggleWaveform"
aria-label="Waveform ribbon on or off"
@@ -145,19 +141,20 @@
</div>
}
@* ── Row 3 — WAVE section (only when waveform on). Scroll is a SLIDER (§8); width pinned right. ── *@
@* ── Row 3 — WAVE section (only when waveform on). Both controls are RadialKnobs (scroll reverted
from MudSlider per Phase 15 polish); width pinned far-right via wvc-row-wave space-between. ── *@
@if (ControlState.WaveformEnabled)
{
<div class="wvc-row wvc-row-section wvc-row-wave">
<span class="wvc-section-label">WAVE:</span>
<MudTooltip Text="How fast the sound rolls by.">
<div class="wvc-slider" role="group" aria-label="Waveform scroll speed">
<MudSlider T="double"
Value="@ControlState.ScrollSpeed"
ValueChanged="@OnScrollSpeedChanged"
Min="0" Max="1" Step="0.001"
Color="Color.Primary" />
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Waveform scroll speed">
<RadialKnob Value="@ControlState.ScrollSpeed"
ValueChanged="@OnScrollSpeedChanged"
Min="0" Max="1" Step="0.001"
Size="64"
Color="Color.Primary" />
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
</div>
</MudTooltip>
@@ -426,7 +426,7 @@ h2, h3, h4, h5, h6,
/* ── 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.
align-items: center so the WAVE slider and section label vertically center with each other (defect #4). ── */
align-items: center so the section label and knobs vertically center with each other. ── */
.waveform-visualizer-control-panel .wvc-row {
display: flex;
flex-direction: row;
@@ -473,29 +473,26 @@ h2, h3, h4, h5, h6,
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. ── */
/* ── The toggles (§3 row 1). Two state classes control the active-state chip treatment:
ON (.wvc-toggle-on): green-accent filled chip — unmistakably active at a glance.
OFF (.wvc-toggle-off): fully transparent background, glyph at low opacity — clearly inactive.
The MudIconButton glyph is already driven green (Color.Primary → pinned green accent, interactive §5).
The chip background reinforces state without recolouring the glyph further. ── */
.waveform-visualizer-control-panel .wvc-toggle {
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: background 0.15s ease;
}
/* ── 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-toggle-on {
background: color-mix(in srgb, var(--deepdrft-green-accent) 28%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--deepdrft-green-accent) 55%, transparent);
}
.waveform-visualizer-control-panel .wvc-slider .mud-slider {
width: 100%;
.waveform-visualizer-control-panel .wvc-toggle-off .mud-icon-button {
opacity: 0.38;
}
/* Caption icons render LIGHT (§5/§9: static/decorative = light). !important beats the scoped
+34
View File
@@ -23,6 +23,40 @@ public static class DDIcons
</svg>
""";
/// <summary>
/// Audio waveform — outline variant, shown when the waveform subsystem is OFF.
/// Seven vertical bars of varying height centred on a 24×24 viewBox, evoking a
/// classic sound-wave / spectrum display. Uses currentColor so it themes freely.
/// Inner markup only — no outer &lt;svg&gt; wrapper; MudBlazor supplies viewBox="0 0 24 24".
/// </summary>
public const string Waveform = """
<g>
<rect x="1" y="9" width="2" height="6" rx="1" fill="none" stroke="currentColor" stroke-width="1.2"/>
<rect x="5" y="5" width="2" height="14" rx="1" fill="none" stroke="currentColor" stroke-width="1.2"/>
<rect x="9" y="2" width="2" height="20" rx="1" fill="none" stroke="currentColor" stroke-width="1.2"/>
<rect x="13" y="6" width="2" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.2"/>
<rect x="17" y="4" width="2" height="16" rx="1" fill="none" stroke="currentColor" stroke-width="1.2"/>
<rect x="21" y="9" width="2" height="6" rx="1" fill="none" stroke="currentColor" stroke-width="1.2"/>
</g>
""";
/// <summary>
/// Audio waveform — filled variant, shown when the waveform subsystem is ON.
/// Same bar layout as <see cref="Waveform"/> but bars are solid currentColor,
/// giving a strong visual contrast for the active (ON) state.
/// Inner markup only — no outer &lt;svg&gt; wrapper; MudBlazor supplies viewBox="0 0 24 24".
/// </summary>
public const string WaveformFilled = """
<g>
<rect x="1" y="9" width="2" height="6" rx="1" fill="currentColor"/>
<rect x="5" y="5" width="2" height="14" rx="1" fill="currentColor"/>
<rect x="9" y="2" width="2" height="20" rx="1" fill="currentColor"/>
<rect x="13" y="6" width="2" height="12" rx="1" fill="currentColor"/>
<rect x="17" y="4" width="2" height="16" rx="1" fill="currentColor"/>
<rect x="21" y="9" width="2" height="6" rx="1" fill="currentColor"/>
</g>
""";
/// <summary>
/// Lava lamp - the Mix visualizer settings glyph. Sourced from lava-lamp-svgrepo-com.svg
/// (SVG Repo, viewBox="0 0 50 50"). Wrapped in a scale(0.48) transform to fit MudBlazor's
@@ -27,9 +27,9 @@
/* Modal scrim opacity the SINGLE point of truth for the visualizer-controls overlay tint
(Phase 15 §4/§10.5). Mild so the panel reads as modal without a blackout. Change here once. */
--deepdrft-modal-scrim-alpha: 0.15;
/* Panel ground desaturated from navy-mid toward charcoal so the blue slider stands out.
Tunable: increase blue channel (e.g. #1e2235) to recover warmth, lower (e.g. #1a1d22) to go darker. */
--deepdrft-panel-ground: #1e2028;
/* Panel ground muted, desaturated charcoal beneath the controls panel.
Tunable: increase blue channel (e.g. #1e2235) to recover warmth, lower (e.g. #191b20) to go darker. */
--deepdrft-panel-ground: #1a1c22;
/* Wireframe font stack */
--deepdrft-font-display: "Cormorant Garamond", Georgia, serif;