@namespace DeepDrftPublic.Client.Controls @using DeepDrftShared.Client.Common @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): 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 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. 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). 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, Blazor CSS isolation does not reach the rendered panel — so panel chrome AND the row/section layout live in the GLOBAL deepdrft-styles.css (.waveform-visualizer-control-panel*), not the scoped .razor.css. The 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 aria capture, so each control's Material icon rides beside its knob as a caption and the accessible name rides on the wrapping group div (§7); the playful MudTooltip rides alongside for sighted hover. *@
@if (Visible) { @* ── Row 1 — MODE (always visible). Toggles + collisions group left; color pinned right. ── *@
@* Collisions are the interaction BETWEEN the two subsystems — meaningless with only one present, so visible only when BOTH are on (§3 truth table). *@ @if (ControlState.LavaEnabled && ControlState.WaveformEnabled) {
}
@* Color applies to the whole field regardless of which subsystems are on, so it is pinned far-right of row 1 and never reflows when collisions hides (§3). *@
@* ── Row 2 — LAVA section (only when lava on). ── *@ @if (ControlState.LavaEnabled) {
} @* ── Row 3 — WAVE section (only when waveform on). Scroll is a SLIDER (§8); width pinned right. ── *@ @if (ControlState.WaveformEnabled) {
} }
@code { /// /// Whether the control deck is shown. The overlay host shows the panel whenever it is open, so the /// default is true. Mix's legacy inline mount (if it survives) still feeds its lava-lamp toggle /// into this — that mount always renders the component, and THIS component decides deck visibility /// (Phase 10 §4): when false the rows are @if-gated out but the container holds its reserved height /// (CSS min-height) so content below the inline bar never pops. Inside the overlay the host owns /// open/closed, so the default keeps the panel populated. /// [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 (NowPlayingCard chrome — square corners, lighter-navy /// ground, thin light border — plus the row/section layout and 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 / subsystem-enable. All dial values are already normalized [0,1]; the bridge maps // scroll speed to a visible time-span and routes the rest straight to the lava/colour dials. The two // toggles flip a boolean (no value), driving the genuine per-subsystem draw-skip in the module (§6). private void ToggleLava() { ControlState.LavaEnabled = !ControlState.LavaEnabled; ControlState.NotifyChanged(); } private void ToggleWaveform() { ControlState.WaveformEnabled = !ControlState.WaveformEnabled; ControlState.NotifyChanged(); } private void OnScrollSpeedChanged(double value) { ControlState.ScrollSpeed = value; ControlState.NotifyChanged(); } private void OnGradientRotationSpeedChanged(double value) { ControlState.GradientRotationSpeed = value; ControlState.NotifyChanged(); } private void OnLavaGravityChanged(double value) { ControlState.LavaGravity = value; ControlState.NotifyChanged(); } private void OnLavaHeatChanged(double value) { ControlState.LavaHeat = value; ControlState.NotifyChanged(); } private void OnFluidAmountChanged(double value) { ControlState.FluidAmount = value; ControlState.NotifyChanged(); } private void OnFluidViscosityChanged(double value) { ControlState.FluidViscosity = value; ControlState.NotifyChanged(); } private void OnCollisionStrengthChanged(double value) { ControlState.CollisionStrength = value; ControlState.NotifyChanged(); } private void OnWaveformWidthChanged(double value) { ControlState.WaveformWidth = value; ControlState.NotifyChanged(); } }