import { useEffect, useRef, useState } from 'react'; type VisibilityCallback = (isVisible: boolean) => void; class SharedObserver { private observer: IntersectionObserver | null = null; private callbacks = new Map(); private threshold: number; constructor(threshold: number) { this.threshold = threshold; } observe(element: Element, callback: VisibilityCallback) { if (!this.observer) { this.observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { const cb = this.callbacks.get(entry.target); cb?.(entry.isIntersecting && entry.intersectionRatio >= this.threshold); }); }, { threshold: this.threshold } ); } this.callbacks.set(element, callback); this.observer.observe(element); } unobserve(element: Element) { this.callbacks.delete(element); this.observer?.unobserve(element); if (this.callbacks.size === 0) { this.observer?.disconnect(); this.observer = null; } } } const observers = new Map(); function getSharedObserver(threshold: number): SharedObserver { const key = Math.round(threshold * 100); if (!observers.has(key)) { observers.set(key, new SharedObserver(threshold)); } return observers.get(key)!; } export function useSharedVisibility(threshold: number, dwellTime: number) { const [isVisible, setIsVisible] = useState(false); const [shouldPlay, setShouldPlay] = useState(false); const ref = useRef(null); const timerRef = useRef | undefined>(undefined); useEffect(() => { const element = ref.current; if (!element) return; const observer = getSharedObserver(threshold); const handleVisibilityChange = (visible: boolean) => { setIsVisible(visible); if (visible) { timerRef.current = setTimeout(() => { setShouldPlay(true); }, dwellTime); } else { if (timerRef.current !== undefined) { clearTimeout(timerRef.current); } setShouldPlay(false); } }; observer.observe(element, handleVisibilityChange); return () => { if (timerRef.current !== undefined) { clearTimeout(timerRef.current); } observer.unobserve(element); }; }, [threshold, dwellTime]); return { ref, isVisible, shouldPlay }; }