"use client"; import { useRef, useEffect, type ReactNode } from "react"; interface HorizontalScrollProps { children: ReactNode; className?: string; } export default function HorizontalScroll({ children, className = "", }: HorizontalScrollProps) { const containerRef = useRef(null); const trackRef = useRef(null); useEffect(() => { const container = containerRef.current; const track = trackRef.current; if (!container || !track) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; // Disable on mobile for better UX if (window.innerWidth < 768) return; let cleanup: (() => void) | undefined; const setup = async () => { const [{ gsap }, { ScrollTrigger }] = await Promise.all([ import("gsap"), import("gsap/ScrollTrigger"), ]); gsap.registerPlugin(ScrollTrigger); const totalWidth = track.scrollWidth; const viewportWidth = window.innerWidth; const tween = gsap.to(track, { x: -(totalWidth - viewportWidth), ease: "none", scrollTrigger: { trigger: container, pin: true, scrub: 1, end: () => `+=${totalWidth - viewportWidth}`, invalidateOnRefresh: true, }, }); // Animate individual items as they come into view const items = track.querySelectorAll("[data-scroll-item]"); for (const item of items) { gsap.fromTo( item, { opacity: 0.3, scale: 0.9, rotateY: -8, transformPerspective: 1000, }, { opacity: 1, scale: 1, rotateY: 0, scrollTrigger: { trigger: item, containerAnimation: tween, start: "left 80%", end: "left 30%", scrub: true, }, } ); } cleanup = () => { ScrollTrigger.getAll().forEach((st) => { if (st.trigger === container || (st.vars as { containerAnimation?: unknown }).containerAnimation === tween) { st.kill(); } }); tween.kill(); }; }; void setup(); return () => { cleanup?.(); }; }, []); return (
{children}
); }