/** * Player-height spacer observer. * * The audio player docks `position: fixed` to the viewport bottom, so it sits * outside normal flow and would overlap page content. A spacer div in the layout * reserves the equivalent space — but the player's height is not constant: it * reflows across the four breakpoints and grows when an error banner appears. A * static height can't track that, so we mirror the player's live border-box * height into the `--player-height` custom property on :root and let the spacer * read it. One observer at a time, re-pointed on each `observe` call; the var * resets to 0 on `unobserve` (player minimized / disposed) so the spacer * collapses. */ const HEIGHT_VAR = '--player-height'; let observer: ResizeObserver | null = null; function writeHeight(px: number): void { // Round up so sub-pixel heights never leave a hairline of overlap. document.documentElement.style.setProperty(HEIGHT_VAR, `${Math.ceil(px)}px`); } export function observe(element: Element): void { unobserve(); if (!element) return; observer = new ResizeObserver(entries => { const entry = entries[0]; if (!entry) return; // Prefer the border-box measurement; fall back to contentRect on the // (older) engines that don't populate borderBoxSize. const box = entry.borderBoxSize?.[0]; writeHeight(box ? box.blockSize : entry.contentRect.height); }); observer.observe(element); // Seed synchronously so the spacer is correct on this frame, before the // first ResizeObserver callback fires. writeHeight(element.getBoundingClientRect().height); } export function unobserve(): void { observer?.disconnect(); observer = null; writeHeight(0); }