fix(p15): remediate seven control-panel + knob defects

Greyer panel ground (token); remove drag scrollbar + lock body scroll; caption icons light; center WAVE slider; RadialKnob drag uses pointer events (robust to cursor leaving window); milder scrim alpha; overlay z-index above header/footer.
This commit is contained in:
daniel-c-harvey
2026-06-17 15:32:01 -04:00
parent 007033e7e8
commit 8a329aadcf
3 changed files with 66 additions and 25 deletions
@@ -399,8 +399,9 @@ h2, h3, h4, h5, h6,
section labels are LIGHT (static). The slider track/thumb and the lamp toggles are green. section labels are LIGHT (static). The slider track/thumb and the lamp toggles are green.
============================================================================= */ ============================================================================= */
.waveform-visualizer-control-panel.mix-visualizer-controls-bar { .waveform-visualizer-control-panel.mix-visualizer-controls-bar {
/* Lighter-navy elevated panel ground (§5: navy-mid). */ /* Greyed panel ground — desaturated charcoal so the blue slider reads against it (defect #1).
background: var(--deepdrft-navy-mid); Token is tunable in deepdrft-tokens.css without touching this rule. */
background: var(--deepdrft-panel-ground);
/* Square corners + thin light border — NowPlayingCard chrome (§5). */ /* Square corners + thin light border — NowPlayingCard chrome (§5). */
border: 1px solid var(--deepdrft-border-light); border: 1px solid var(--deepdrft-border-light);
border-radius: 0; border-radius: 0;
@@ -424,11 +425,12 @@ h2, h3, h4, h5, h6,
/* ── Row layout (§3). Each row is a horizontal band. Row 1 (MODE) and row 3 (WAVE) use /* ── 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 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. ── */ 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). ── */
.waveform-visualizer-control-panel .wvc-row { .waveform-visualizer-control-panel .wvc-row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: center;
gap: 0.85rem 1rem; gap: 0.85rem 1rem;
} }
@@ -454,7 +456,7 @@ h2, h3, h4, h5, h6,
.waveform-visualizer-control-panel .wvc-row-left { .waveform-visualizer-control-panel .wvc-row-left {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: center;
gap: 0.85rem 1rem; gap: 0.85rem 1rem;
} }
@@ -496,29 +498,52 @@ h2, h3, h4, h5, h6,
width: 100%; width: 100%;
} }
/* Caption icons + section labels render LIGHT (§5/§9 colour principle: static/decorative = light). MudIcon /* Caption icons render LIGHT (§5/§9: static/decorative = light). !important beats the scoped
is portaled here too, so this is a plain global descendant selector — no ::deep, no scope attribute (CSS .mix-visualizer-control ::deep .mix-visualizer-control-icon rule (which sets green for the legacy
isolation does not reach inside the overlay). The knob arcs/pointers + slider stay green (interactive). */ inline mount) when the icon also carries mix-visualizer-control-icon. Lamp toggles are MudIconButton
not MudIcon so they are unaffected — they stay green (interactive, Color.Primary). (defect #3) */
.waveform-visualizer-control-panel .waveform-visualizer-control-icon { .waveform-visualizer-control-panel .waveform-visualizer-control-icon {
color: var(--deepdrft-white); color: var(--deepdrft-white) !important;
opacity: 0.85; opacity: 0.85;
} }
/* ── The modal overlay (Phase 15 §4). MudOverlay is already a full-viewport flex scrim that centers its /* ── 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 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 screen-centered panel on every host for free — we do NOT fight that positioning. We:
mild modal tint from the SINGLE --deepdrft-modal-scrim-alpha token (§10.5, one point of change) and (a) Raise the overlay z-index above the header (100) and the player-dock footer (1200/1300) so the
(b) cap the centered content's height so a tall both-on deck scrolls inside the modal rather than scrim tints the ENTIRE viewport uniformly — header and footer included (defect #7). The panel
overflowing the viewport. The overlay portals to the body, so these are plain global rules (no scope content needs z-index: auto (inherits from stacking context) so it sits above the scrim naturally;
attribute). The doubled .mud-overlay-scrim.mud-overlay-dark selector (0,2,0) outranks MudBlazor's own the RadialKnob capture div at 9999 remains above everything.
.mud-overlay-dark (0,1,0), so the tint wins regardless of stylesheet load order. ── */ (b) Set the mild tint from the SINGLE --deepdrft-modal-scrim-alpha token (§10.5, defect #6).
(c) Remove overflow-y:auto on the content wrapper — it was the source of the drag scrollbar (defect #2).
The panel's max-width/flex-column already contain its size; the outer overlay clips at 100vh.
(d) Suppress body scroll while the overlay is present so no page-scroll occurs during a drag (defect #2).
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. ── */
/* Raise the overlay itself above the sticky header (z-index:100) and the fixed player dock (z-index:1200).
Use 1400 so it sits above the minimized-dock FAB (1300) too. The panel content inherits this context
and stacks above the scrim; the RadialKnob capture div (z-index:9999) stays highest. */
.waveform-visualizer-control-overlay {
z-index: 1400 !important;
}
.waveform-visualizer-control-overlay .mud-overlay-scrim.mud-overlay-dark { .waveform-visualizer-control-overlay .mud-overlay-scrim.mud-overlay-dark {
background-color: rgba(var(--deepdrft-scrim-rgb), var(--deepdrft-modal-scrim-alpha)); background-color: rgba(var(--deepdrft-scrim-rgb), var(--deepdrft-modal-scrim-alpha));
} }
/* No overflow-y:auto — removing it eliminates the spurious scrollbar that appeared while dragging a
knob (defect #2). The panel's flex-column layout is self-contained and never overflows the overlay. */
.waveform-visualizer-control-overlay .mud-overlay-content { .waveform-visualizer-control-overlay .mud-overlay-content {
max-height: 90vh; max-height: 90vh;
overflow-y: auto; overflow: visible;
}
/* Lock body scroll while the controls overlay is open so the page cannot be scrolled during a
knob drag (defect #2). :has() degrades gracefully in older browsers (no lock, no crash). */
body:has(.waveform-visualizer-control-overlay) {
overflow: hidden;
} }
@media (max-width: 419.98px) { @media (max-width: 419.98px) {
@@ -1,10 +1,16 @@
@using System.Globalization @using System.Globalization
<!-- Global mouse capture container when dragging --> <!-- Global pointer-capture container when dragging.
position:fixed; inset:0; z-index:9999 so it sits above all overlays (the controls modal is z-index:1400).
Pointer events used instead of mouse events: @onpointermove / @onpointerup fire even if the
cursor moves outside the browser window when pointer capture is active (set on mousedown below).
@onpointercancel covers the OS-level cancel path (e.g. Alt+Tab on Windows). Drag always ends cleanly. -->
@if (_isDragging) @if (_isDragging)
{ {
<div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999; cursor: ns-resize;" <div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999; cursor: ns-resize;"
@onmousemove="@OnGlobalMouseMove" @onmouseup="@OnGlobalMouseUp"> @onpointermove="@OnGlobalPointerMove"
@onpointerup="@OnGlobalPointerUp"
@onpointercancel="@OnGlobalPointerCancel">
</div> </div>
} }
@@ -156,19 +162,19 @@
_lastMouseY = e.ClientY; _lastMouseY = e.ClientY;
_dragValue = Value; // Initialize drag value with current value _dragValue = Value; // Initialize drag value with current value
// Add global mouse event handlers using Blazor's event handling // The full-viewport capture div renders; pointer events on it will handle the rest.
StateHasChanged(); StateHasChanged();
} }
private async Task OnGlobalMouseMove(MouseEventArgs e) private async Task OnGlobalPointerMove(PointerEventArgs e)
{ {
if (_isDragging) if (_isDragging)
{ {
await UpdateValueFromMouse(e); await UpdateValueFromPointer(e);
} }
} }
private async Task OnGlobalMouseUp(MouseEventArgs e) private async Task OnGlobalPointerUp(PointerEventArgs e)
{ {
if (_isDragging && HoldValue) if (_isDragging && HoldValue)
{ {
@@ -183,9 +189,16 @@
StateHasChanged(); StateHasChanged();
} }
private async Task UpdateValueFromMouse(MouseEventArgs e) // Pointer capture cancelled by OS (e.g. Alt+Tab, system gesture) — end drag cleanly.
private async Task OnGlobalPointerCancel(PointerEventArgs e)
{ {
// Calculate vertical delta from last mouse position _isDragging = false;
StateHasChanged();
}
private async Task UpdateValueFromPointer(PointerEventArgs e)
{
// Calculate vertical delta from last pointer position
double deltaY = _lastMouseY - e.ClientY; // Inverted: up = positive, down = negative double deltaY = _lastMouseY - e.ClientY; // Inverted: up = positive, down = negative
_lastMouseY = e.ClientY; _lastMouseY = e.ClientY;
@@ -26,7 +26,10 @@
--deepdrft-scrim-rgb: 13, 27, 42; --deepdrft-scrim-rgb: 13, 27, 42;
/* Modal scrim opacity — the SINGLE point of truth for the visualizer-controls overlay tint /* 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. */ (Phase 15 §4/§10.5). Mild so the panel reads as modal without a blackout. Change here once. */
--deepdrft-modal-scrim-alpha: 0.3; --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;
/* Wireframe font stack */ /* Wireframe font stack */
--deepdrft-font-display: "Cormorant Garamond", Georgia, serif; --deepdrft-font-display: "Cormorant Garamond", Georgia, serif;