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.initials || r.name?.slice(0, 2)}
{Array.from({ length: 5 }).map((_, s) => (
★
))}
“{r.quote}”
{r.name}
✓
{r.role}{r.service ? ` · ${r.service}` : ''}
))}
{reviews.map((_, k) => (
);
}
window.TestimonialSlider = TestimonialSlider;