function TestimonialSlider({ reviews = [] }) { const n = reviews.length; const [index, setIndex] = React.useState(0); const [paused, setPaused] = React.useState(false); const [animating, setAnimating] = React.useState(true); const trackRef = React.useRef(null); const touchX = React.useRef(null); const timerRef = React.useRef(null); const slides = React.useMemo(() => (n ? [...reviews, reviews[0]] : []), [reviews, n]); const goTo = React.useCallback((i, animate = true) => { setAnimating(animate); setIndex(i); }, []); const next = React.useCallback(() => goTo(index + 1), [index, goTo]); const prev = React.useCallback(() => goTo(index <= 0 ? n - 1 : index - 1), [index, n, goTo]); React.useEffect(() => { if (paused || n < 2) return; timerRef.current = setTimeout(next, 5000); return () => clearTimeout(timerRef.current); }, [index, paused, n, next]); React.useEffect(() => { if (index === n) { const t = setTimeout(() => { goTo(0, false); }, 700); return () => clearTimeout(t); } }, [index, n, goTo]); if (!n) return null; const activeDot = ((index % n) + n) % n; return (
setPaused(true)} onMouseLeave={() => setPaused(false)} onKeyDown={(e) => { if (e.key === 'ArrowLeft') { setPaused(true); prev(); } if (e.key === 'ArrowRight') { setPaused(true); next(); } }} tabIndex={0} role="region" aria-roledescription="carousel" aria-label="Client testimonials" >
{ touchX.current = e.touches[0].clientX; }} onTouchEnd={(e) => { if (touchX.current == null) return; const dx = e.changedTouches[0].clientX - touchX.current; touchX.current = null; if (Math.abs(dx) < 40) return; setPaused(true); if (dx < 0) next(); else prev(); }} >
{slides.map((r, k) => (

“{r.quote}”

{r.name}
{r.role}{r.service ? ` · ${r.service}` : ''}
))}
{reviews.map((_, k) => (
); } window.TestimonialSlider = TestimonialSlider;