diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor b/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor index e7289a2..f979e0a 100644 --- a/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor +++ b/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor @@ -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 5–9). 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 diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor b/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor index 8f7bb35..e8ada3f 100644 --- a/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor +++ b/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor @@ -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 @@
-
+
-
- + } - @* ── 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) {
-
- +
+
diff --git a/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css b/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css index c1986f3..b6248b4 100644 --- a/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css +++ b/DeepDrftPublic/wwwroot/styles/deepdrft-styles.css @@ -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 diff --git a/DeepDrftShared.Client/Common/DDIcons.cs b/DeepDrftShared.Client/Common/DDIcons.cs index b25eb55..b083a53 100644 --- a/DeepDrftShared.Client/Common/DDIcons.cs +++ b/DeepDrftShared.Client/Common/DDIcons.cs @@ -23,6 +23,40 @@ public static class DDIcons """; + /// + /// 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 <svg> wrapper; MudBlazor supplies viewBox="0 0 24 24". + /// + public const string Waveform = """ + + + + + + + + + """; + + /// + /// Audio waveform — filled variant, shown when the waveform subsystem is ON. + /// Same bar layout as but bars are solid currentColor, + /// giving a strong visual contrast for the active (ON) state. + /// Inner markup only — no outer <svg> wrapper; MudBlazor supplies viewBox="0 0 24 24". + /// + public const string WaveformFilled = """ + + + + + + + + + """; + /// /// 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 diff --git a/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css b/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css index e6c453f..1484705 100644 --- a/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css +++ b/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css @@ -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;