@namespace DeepDrftPublic.Client.Controls @using DeepDrftPublic.Client.Services @inject WaveformVisualizerControlState ControlState @* The waveform visualizer control PANEL (Phase 12 §3d-revised). EIGHT continuous RadialKnobs — scroll speed, gradient rotation speed, lava gravity, lava heat, fluid amount, fluid viscosity, collision strength, waveform width — each its own dedicated control with a Material-icon caption. The single "bubbles" knob is split into fluid-amount + fluid-viscosity (Phase 10 §5). This component is the PANEL CONTENT hosted inside WaveformVisualizerControlPopover. It no longer rides an inline TopRowCenter bar; it lays out as a wrapped grid sized for a popover, styled to the NowPlaying Hero look (§3g — dark-navy ground, green-accent knobs, light icons) from the deepdrft-* tokens. Because MudPopover PORTALS its content out of this component's DOM subtree, Blazor CSS isolation does not reach the rendered panel — so panel chrome lives in the GLOBAL deepdrft-styles.css (.waveform-visualizer-control-panel*), not in the scoped .razor.css. The scoped .razor.css carries only the inline-bar fallback (the .mix-visualizer-controls-bar reserved-height row) Mix's existing TopRowCenter mount still uses, which is NOT portaled and so resolves under isolation. Visibility: the popover host always shows the panel when open (Visible defaults true). Mix's legacy inline mount still feeds its lava-lamp toggle into Visible (Phase 10 §4): the knobs @if-gate while the container holds a reserved min-height so content below never pops on toggle. It owns NO JS interop: it mutates the shared, session-scoped WaveformVisualizerControlState and raises its Changed event. The visualizer bridge (WaveformVisualizer) subscribes to that event and pushes the affected dial to the WebGL module. That keeps the JS module handle single-owned by the bridge and this component purely presentational. None of these is a seek surface (read-only contract §D). RadialKnob has no icon slot (its Label renders as SVG text) and no aria attribute-capture, so each control's Material icon rides beside its knob as an adjacent MudIcon caption and the accessible name rides on the wrapping group div (§7d). HoldValue stays false so the knobs are live — ValueChanged fires continuously during drag, preserving the Changed/NotifyChanged seam. *@
@if (Visible) {
}
@code { /// /// 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 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; } = 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 // to a visible time-span and routes the rest straight to the lava/colour dials. 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(); } }