"use client"; import React, { useEffect, useRef, useState, useLayoutEffect, forwardRef, } from "react"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import { LucideIcon } from "lucide-react"; // Assuming these are external, import them import { cn } from "../../lib/utils"; gsap.registerPlugin(ScrollTrigger); // --- Component Props and Types --- // Define a type for a single feature object export interface FeatureItem { icon: LucideIcon; title: string; description: string; image: string; } // Define the component's props interface export interface ScrollCarouselProps { features: FeatureItem[]; className?: string; // To allow external classes maxScrollHeight?: number; // New optional prop for max scroll height } // --- Custom Hook for Animations --- const useFeatureAnimations = ( containerRef: React.RefObject, scrollContainerRef: React.RefObject, scrollContainerRef2: React.RefObject, progressBarRef: React.RefObject, cardRefs: React.MutableRefObject, cardRefs2: React.MutableRefObject, isDesktop: boolean, maxScrollHeight?: number ) => { useLayoutEffect(() => { let ctx = gsap.context(() => { // Desktop horizontal scroll logic if (isDesktop) { const scrollWidth1 = scrollContainerRef.current?.scrollWidth || 0; const scrollWidth2 = scrollContainerRef2.current?.scrollWidth || 0; const containerWidth = containerRef.current?.offsetWidth || 0; const cardWidth = cardRefs.current[0]?.offsetWidth || 0; const viewportOffset = (containerWidth - cardWidth) / 2; const finalOffset1 = scrollWidth1 - containerWidth + viewportOffset; const finalOffset2 = scrollWidth2 - containerWidth + viewportOffset; // Use the provided maxScrollHeight or the calculated offset as the scroll distance const scrollDistance = maxScrollHeight || finalOffset1; gsap.set(scrollContainerRef2.current, { x: -finalOffset2 + viewportOffset * 2, }); gsap .timeline({ scrollTrigger: { trigger: containerRef.current, start: "top top", end: () => `+=${scrollDistance}`, scrub: 1, pin: true, }, }) .fromTo( scrollContainerRef.current, { x: viewportOffset }, { x: -finalOffset1 + viewportOffset, ease: "none" } ); gsap .timeline({ scrollTrigger: { trigger: containerRef.current, start: "top top", end: () => `+=${scrollDistance}`, scrub: 1, }, }) .to(scrollContainerRef2.current, { x: viewportOffset, ease: "none" }); gsap.to(progressBarRef.current, { width: "100%", ease: "none", scrollTrigger: { trigger: containerRef.current, start: "top top", end: () => `+=${scrollDistance}`, scrub: true, }, }); } else { // Mobile vertical scroll logic const allCards = [...cardRefs.current, ...cardRefs2.current]; allCards.forEach((card, index) => { if (card) { gsap.fromTo( card, { opacity: 0, x: index % 2 === 0 ? -200 : 200, }, { opacity: 1, x: 0, duration: 1, ease: "power2.out", scrollTrigger: { trigger: card, start: "top 0%", toggleActions: "play none none none", once: true, }, } ); } }); } }, containerRef); return () => { ctx.revert(); }; }, [isDesktop, maxScrollHeight]); }; // --- Component Definition --- export const ScrollCarousel = forwardRef( ({ features, className, maxScrollHeight }, ref) => { const containerRef = useRef(null); const scrollContainerRef = useRef(null); const scrollContainerRef2 = useRef(null); const progressBarRef = useRef(null); const cardRefs = useRef([]); const cardRefs2 = useRef([]); const [isDesktop, setIsDesktop] = useState(false); // Dynamic sorting for the second row of cards const features2 = [...features].sort(() => Math.random() - 0.5); useEffect(() => { const checkDesktop = () => { setIsDesktop(window.matchMedia("(min-width: 768px)").matches); }; checkDesktop(); window.addEventListener("resize", checkDesktop); return () => window.removeEventListener("resize", checkDesktop); }, []); useFeatureAnimations( containerRef, scrollContainerRef, scrollContainerRef2, progressBarRef, cardRefs, cardRefs2, isDesktop, maxScrollHeight ); const renderFeatureCards = ( featureSet: FeatureItem[], refs: React.MutableRefObject ) => featureSet.map((feature, index) => (
{ if (el) refs.current[index] = el; }} className="feature-card flex-shrink-0 w-full md:w-full h-full z-10 gap-4 group relative transition-all duration-300 ease-in-out" >
{/* */}

{feature.title}

{feature.description}

{/* */}
)); return (
{renderFeatureCards(features, cardRefs)}
{renderFeatureCards(features2, cardRefs2)}
{isDesktop && (
)}
); } ); ScrollCarousel.displayName = "ScrollCarousel"; export default ScrollCarousel;