// Reusable mobile carousel: autoplay + pagination bullets. // On desktop it's an inert wrapper (children keep their CSS grid layout); // on mobile (<=720px) the track becomes a snap-scroller with dots + autoplay. const Carousel = ({ children, trackClassName = '', interval = 4500, autoplay = true }) => { const trackRef = React.useRef(null); const [active, setActive] = React.useState(0); const [slides, setSlides] = React.useState(0); const [isMobile, setIsMobile] = React.useState(false); const [paused, setPaused] = React.useState(false); React.useEffect(() => { const mq = window.matchMedia('(max-width: 720px)'); const update = () => setIsMobile(mq.matches); update(); mq.addEventListener('change', update); return () => mq.removeEventListener('change', update); }, []); React.useEffect(() => { const el = trackRef.current; if (el) setSlides(el.children.length); }, [children]); // Track which slide is centered as the user scrolls React.useEffect(() => { const el = trackRef.current; if (!el) return; let raf = 0; const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { const base = el.getBoundingClientRect().left; let best = 0, bestDist = Infinity; Array.from(el.children).forEach((c, i) => { const d = Math.abs(c.getBoundingClientRect().left - base); if (d < bestDist) { bestDist = d; best = i; } }); setActive(best); }); }; el.addEventListener('scroll', onScroll, { passive: true }); return () => { el.removeEventListener('scroll', onScroll); cancelAnimationFrame(raf); }; }, [slides]); const goTo = React.useCallback((i) => { const el = trackRef.current; if (!el) return; const child = el.children[i]; if (!child) return; const pad = parseFloat(getComputedStyle(el).paddingLeft) || 0; const left = child.getBoundingClientRect().left - el.getBoundingClientRect().left + el.scrollLeft - pad; el.scrollTo({ left, behavior: 'smooth' }); }, []); // Autoplay (mobile only, pauses on touch/hover) React.useEffect(() => { if (!isMobile || !autoplay || paused || slides <= 1) return; const id = setTimeout(() => goTo((active + 1) % slides), interval); return () => clearTimeout(id); }, [active, isMobile, autoplay, paused, slides, interval, goTo]); const pause = () => setPaused(true); const resume = () => setPaused(false); return (