diff --git a/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor b/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor
index 089f478..e9c379e 100644
--- a/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor
+++ b/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor
@@ -1,6 +1,6 @@
@if (_isMinimized)
{
-
+
}
diff --git a/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs b/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs
index 7f9a75e..9c70361 100644
--- a/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs
+++ b/DeepDrftPublic.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs
@@ -27,7 +27,15 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
// var via a ResizeObserver (see Interop/layout/spacer.ts) rather than a static
// value, because the player reflows across breakpoints and grows with the
// error banner.
+ //
+ // _miniDock is the minimized FAB container. We observe it in minimized state so
+ // --player-height stays non-zero (the FAB's actual height) and the MixWaveformVisualizer
+ // clips to the top of the FAB rather than extending to the viewport bottom (fix §1).
+ // The player-spacer's .minimized class uses a hardcoded 60px and ignores the var,
+ // so publishing the FAB height here does not regress the spacer.
private ElementReference _playerRoot;
+ private ElementReference _miniDock;
+ private ElementReference _lastObservedElement;
private IJSObjectReference? _spacerModule;
private bool _spacerObserved;
@@ -114,22 +122,27 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
protected override async Task OnAfterRenderAsync(bool firstRender)
{
- // Only the docked, expanded shape needs a spacer: the Fixed embed is
- // already in normal flow, and the minimized FAB floats clear of content.
- // Toggle the observer on the minimized/expanded transition only — the
- // ResizeObserver itself handles every size change in between.
- var shouldObserve = !_isMinimized && !Fixed;
- if (shouldObserve == _spacerObserved) return;
+ // The Fixed embed is already in normal flow — no spacer/clip needed.
+ // For the docked player: we observe in BOTH expanded and minimized states
+ // so --player-height always reflects the live height of whichever element
+ // is visible. This keeps the MixWaveformVisualizer clipped to the top of
+ // the footer in both states (fix §1).
+ // expanded → observe _playerRoot (full player bar, reflows across breakpoints)
+ // minimized → observe _miniDock (floating FAB container, ~56–60px)
+ // The player-spacer's .minimized class uses a hardcoded height and ignores
+ // the var, so publishing the FAB height here does not regress the spacer.
+ if (Fixed) return;
+
+ var elementToObserve = _isMinimized ? _miniDock : _playerRoot;
+ var alreadyOnThisElement = _spacerObserved && elementToObserve.Id == _lastObservedElement.Id;
+ if (alreadyOnThisElement) return;
var module = await GetSpacerModuleAsync();
if (module is null) return;
- if (shouldObserve)
- await module.InvokeVoidAsync("observe", _playerRoot);
- else
- await module.InvokeVoidAsync("unobserve");
-
- _spacerObserved = shouldObserve;
+ await module.InvokeVoidAsync("observe", elementToObserve);
+ _spacerObserved = true;
+ _lastObservedElement = elementToObserve;
}
private async Task
GetSpacerModuleAsync()
diff --git a/DeepDrftPublic.Client/Controls/MixWaveformVisualizer.razor.css b/DeepDrftPublic.Client/Controls/MixWaveformVisualizer.razor.css
index 91583b2..e1c5f4c 100644
--- a/DeepDrftPublic.Client/Controls/MixWaveformVisualizer.razor.css
+++ b/DeepDrftPublic.Client/Controls/MixWaveformVisualizer.razor.css
@@ -3,13 +3,13 @@
Footer clip (Phase 10 W1, spec §2c): the backdrop must stop cleanly ABOVE the audio player bar so
no lava/waveform pixel paints over or under it. `overflow: hidden` clips the canvas to this box, and
- `bottom` is inset by the player bar's LIVE height — `--player-height`, the custom property the player
- already publishes on :root via its ResizeObserver (AudioPlayerBar + Interop/layout/spacer.ts). That
- var tracks the expanded bar's border-box height across breakpoints/error-banner reflow, and resets to
- 0 when the bar is minimized — so the clip line follows the bar's actual current height with no extra
- coupling: when minimized the var is 0 and the backdrop reaches the viewport bottom (the floating FAB,
- z-index 1300, simply sits over it — there is no full-width bar to clip to), matching spec §2c. The
- 0px fallback keeps the backdrop full-height on any page that doesn't host the player. */
+ `bottom` is inset by `--player-height`, which AudioPlayerBar publishes on :root via its ResizeObserver
+ (Interop/layout/spacer.ts). The observer now points at whichever element is live:
+ expanded → the full player dock (tracks breakpoint reflow + error-banner growth)
+ minimized → the minimized-dock FAB container (~56–60 px)
+ so --player-height is always non-zero while the player is mounted and the clip line follows the bar in
+ BOTH states (fix §1 / p10-reframe-w1-fix). The 0px fallback keeps the backdrop full-height on any
+ page that does not host the player. */
.mix-waveform-bg {
position: fixed;
inset: 0;
diff --git a/DeepDrftShared.Client/Common/DDIcons.cs b/DeepDrftShared.Client/Common/DDIcons.cs
index ab6f6d0..5be0a7e 100644
--- a/DeepDrftShared.Client/Common/DDIcons.cs
+++ b/DeepDrftShared.Client/Common/DDIcons.cs
@@ -24,24 +24,13 @@ public static class DDIcons
""";
///
- /// Lava lamp - the Mix visualizer settings glyph. Classic 1970s "Lava" silhouette, redrawn (Phase 10
- /// W1). Inner path/shape markup for MudBlazor's Icon= slot (no outer <svg> wrapper — MudBlazor
- /// supplies that); coordinates in the 24×24 space matching viewBox="0 0 24 24".
- ///
- /// Bottom→top: a WIDE truncated-cone metal BASE (widest at the very bottom, tapering up to a narrow
- /// waist); a tall tapered GLASS VESSEL on the base — bulbous/rounded at the bottom, tapering up to a
- /// roundedly-pointed (teardrop/bullet) top; a small truncated-cone metal CAP on top mirroring the base.
- /// The metal base + cap are currentColor so the silhouette stays tintable with its context
- /// (Color.Secondary, light/dark). The fluid + blobs are hard navy/moss so the lamp reads as "lit" at
- /// icon size: navy (#17283F) vessel fluid with 3 varied moss (#429D6A) lava blobs suspended in it.
- /// NOTE: these hexes mirror the app theme tokens (navy ~#17283F/#0D1B2A, moss ~#3D7A68/#429D6A).
+ /// 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
+ /// inner 24×24 coordinate space (50 × 0.48 = 24). Fill uses currentColor so it themes with
+ /// its context (Color.Secondary, light/dark). Inner markup only — no outer <svg> wrapper,
+ /// MudBlazor supplies viewBox="0 0 24 24".
///
public const string LavaLamp = """
-
-
-
-
-
-
+
""";
}