Files
pipu-music/apps/web/src/widgets/Ambient.tsx
T
mrleo1nid 4c2286e70d
ci / ts / ts (packages + web) (push) Failing after 2m2s
feat(web): react wave and ambient visuals to live audio
2026-10-21 16:44:04 +03:00

41 lines
1.6 KiB
TypeScript

import { useEffect, useRef, type CSSProperties } from 'react';
import s from './Ambient.module.css';
/**
* Ambient-фон (docs/design.md#1): три размытых дрейфующих пятна; третье — в цвете обложки текущего трека.
* Отключается в настройках; при prefers-reduced-motion пятна неподвижны.
*/
export function Ambient({ color, readAudio }: {
color: string;
readAudio?: () => { bass: number; mid: number; treble: number };
}) {
const root = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!readAudio) return;
const mq = window.matchMedia?.('(prefers-reduced-motion: reduce)');
let raf = 0;
let last = performance.now();
let level = 0;
const frame = (now: number) => {
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
const audio = !mq?.matches && !document.hidden ? readAudio() : undefined;
const target = audio ? audio.bass * 0.65 + audio.mid * 0.25 + audio.treble * 0.1 : 0;
level += (target - level) * (1 - Math.exp(-dt * 7));
root.current?.style.setProperty('--music-scale', String(1 + level * 0.16));
root.current?.style.setProperty('--music-light', String(1 + level * 0.3));
raf = requestAnimationFrame(frame);
};
raf = requestAnimationFrame(frame);
return () => cancelAnimationFrame(raf);
}, [readAudio]);
return (
<div ref={root} className={s.ambient} aria-hidden>
<span className={s.a} />
<span className={s.b} />
<span className={s.c} style={{ '--amb-c3': `${color}99` } as CSSProperties} />
</div>
);
}