'use client'; // Optional `subtle` reactive cover. Reads levels from the imperative store, // down-mixes to a single envelope, writes a CSS variable that drives a // compositor-only scale. No box-shadow, no glow. import { useEffect, useRef } from 'react'; import { usePlayerLevels } from '../../context/selectors'; import type { ReactNode } from 'react'; type Props = { enabled: boolean; children: ReactNode }; const VAR = '--audioplayer-pulse'; const MAX_SCALE = 0.03; const SMOOTH = 0.18; export function ReactivePulse({ enabled, children }: Props) { const ref = useRef(null); const store = usePlayerLevels(); useEffect(() => { if (!enabled) { ref.current?.style.setProperty(VAR, '1'); return; } const el = ref.current; if (!el) return; if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) { el.style.setProperty(VAR, '1'); return; } let raf = 0; let env = 0; const tick = () => { const buf = store.getCurrent(); let energy = 0; const usable = Math.min(buf.length, 32); if (usable > 0) { for (let i = 0; i < usable; i++) energy += buf[i]; energy /= usable; } env = env + (energy - env) * SMOOTH; const scale = 1 + Math.min(MAX_SCALE, env * MAX_SCALE * 1.5); el.style.setProperty(VAR, scale.toFixed(4)); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [enabled, store]); return (
{children}
); }