"use client"; import React, { useState, useEffect, useRef } from "react"; import { motion, useScroll, useTransform, useSpring, MotionValue, } from "framer-motion"; import { cn } from "../../lib/utils"; import { Card, CardContent } from "./card"; import { Calendar } from "lucide-react"; export interface TimelineEvent { id?: string; year: string; title: string; subtitle?: string; description: string; icon?: React.ReactNode; color?: string; } export interface ScrollTimelineProps { events: TimelineEvent[]; title?: string; subtitle?: string; animationOrder?: "sequential" | "staggered" | "simultaneous"; cardAlignment?: "alternating" | "left" | "right"; lineColor?: string; activeColor?: string; progressIndicator?: boolean; cardVariant?: "default" | "elevated" | "outlined" | "filled"; cardEffect?: "none" | "glow" | "shadow" | "bounce"; parallaxIntensity?: number; progressLineWidth?: number; progressLineCap?: "round" | "square"; dateFormat?: "text" | "badge"; className?: string; revealAnimation?: "fade" | "slide" | "scale" | "flip" | "none"; connectorStyle?: "dots" | "line" | "dashed"; perspective?: boolean; darkMode?: boolean; smoothScroll?: boolean; } const DEFAULT_EVENTS: TimelineEvent[] = [ { year: "2023", title: "Major Achievement", subtitle: "Organization Name", description: "Description of the achievement or milestone reached during this time period.", }, { year: "2022", title: "Important Milestone", subtitle: "Organization Name", description: "Details about this significant milestone and its impact.", }, { year: "2021", title: "Key Event", subtitle: "Organization Name", description: "Information about this key event in the timeline.", }, ]; export const ScrollTimeline = ({ events = DEFAULT_EVENTS, title = "Timeline", subtitle = "Scroll to explore the journey", animationOrder = "sequential", cardAlignment = "alternating", lineColor = "bg-primary/30", activeColor = "bg-primary", progressIndicator = true, cardVariant = "default", cardEffect = "none", parallaxIntensity = 0.2, progressLineWidth = 2, progressLineCap = "round", dateFormat = "badge", revealAnimation = "fade", className = "", connectorStyle = "line", perspective = false, darkMode = false, smoothScroll = true, }: ScrollTimelineProps) => { const scrollRef = useRef(null); const [activeIndex, setActiveIndex] = useState(-1); const timelineRefs = useRef<(HTMLDivElement | null)[]>([]); const { scrollYProgress } = useScroll({ target: scrollRef, offset: ["start start", "end end"], }); const smoothProgress = useSpring(scrollYProgress, { stiffness: 100, damping: 30, restDelta: 0.001, }); const progressHeight = useTransform(smoothProgress, [0, 1], ["0%", "100%"]); useEffect(() => { const unsubscribe = scrollYProgress.onChange((v) => { const newIndex = Math.floor(v * events.length); if ( newIndex !== activeIndex && newIndex >= 0 && newIndex < events.length ) { setActiveIndex(newIndex); } }); return () => unsubscribe(); }, [scrollYProgress, events.length, activeIndex]); const getCardVariants = (index: number) => { const baseDelay = animationOrder === "simultaneous" ? 0 : animationOrder === "staggered" ? index * 0.2 : index * 0.3; const initialStates = { fade: { opacity: 0, y: 20 }, slide: { x: cardAlignment === "left" ? -100 : cardAlignment === "right" ? 100 : index % 2 === 0 ? -100 : 100, opacity: 0, }, scale: { scale: 0.8, opacity: 0 }, flip: { rotateY: 90, opacity: 0 }, none: { opacity: 1 }, }; return { initial: initialStates[revealAnimation], whileInView: { opacity: 1, y: 0, x: 0, scale: 1, rotateY: 0, transition: { duration: 0.7, delay: baseDelay, ease: [0.25, 0.1, 0.25, 1.0] as [number, number, number, number], }, }, viewport: { once: false, margin: "-100px" }, }; }; const getConnectorClasses = () => { const baseClasses = cn( "absolute left-1/2 transform -translate-x-1/2", lineColor ); const widthStyle = `w-[${progressLineWidth}px]`; switch (connectorStyle) { case "dots": return cn(baseClasses, "w-1 rounded-full"); case "dashed": return cn( baseClasses, widthStyle, `[mask-image:linear-gradient(to_bottom,black_33%,transparent_33%,transparent_66%,black_66%)] [mask-size:1px_12px]` ); case "line": default: return cn(baseClasses, widthStyle); } }; const getCardClasses = (index: number) => { const baseClasses = "relative z-30 rounded-lg transition-all duration-300"; const variantClasses = { default: "bg-card border shadow-sm", elevated: "bg-card border border-border/40 shadow-md", outlined: "bg-card/50 backdrop-blur border-2 border-primary/20", filled: "bg-primary/10 border border-primary/30", }; const effectClasses = { none: "", glow: "hover:shadow-[0_0_15px_rgba(var(--primary-rgb)/0.5)]", shadow: "hover:shadow-lg hover:-translate-y-1", bounce: "hover:scale-[1.03] hover:shadow-md active:scale-[0.97]", }; const alignmentClassesDesktop = cardAlignment === "alternating" ? index % 2 === 0 ? "lg:mr-[calc(50%+20px)]" : "lg:ml-[calc(50%+20px)]" : cardAlignment === "left" ? "lg:mr-auto lg:ml-0" : "lg:ml-auto lg:mr-0"; const perspectiveClass = perspective ? "transform transition-transform hover:rotate-y-1 hover:rotate-x-1" : ""; return cn( baseClasses, variantClasses[cardVariant], effectClasses[cardEffect], alignmentClassesDesktop, "w-full lg:w-[calc(50%-40px)]" ); }; return (

{title}

{subtitle}

{/* === MODIFICATION START === */} {/* Enhanced Progress Indicator with Traveling Glow */} {progressIndicator && ( <> {/* The main filled progress line */} {/* The traveling glow "comet" at the head of the line */} )} {/* === MODIFICATION END === */}
{events.map((event, index) => { const yOffset = useTransform( smoothProgress, [0, 1], [parallaxIntensity * 100, -parallaxIntensity * 100] ); return (
{ timelineRefs.current[index] = el; }} className={cn( "relative flex items-center mb-20 py-4", "flex-col lg:flex-row", cardAlignment === "alternating" ? index % 2 === 0 ? "lg:justify-start" : "lg:flex-row-reverse lg:justify-start" : cardAlignment === "left" ? "lg:justify-start" : "lg:flex-row-reverse lg:justify-start" )} >
0 ? { y: yOffset } : undefined} > {dateFormat === "badge" ? (
{event.icon || ( )} {event.year}
) : (

{event.year}

)}

{event.title}

{event.subtitle && (

{event.subtitle}

)}

{event.description}

); })}
); };