"use client" import { useState, useEffect, useRef } from "react" import { useInView } from "react-intersection-observer" import { cn } from "@/lib/utils" interface CountUpAnimationProps { end: number start?: number duration?: number delay?: number prefix?: string suffix?: string decimals?: number className?: string } export function CountUpAnimation({ end, start = 0, duration = 2000, delay = 0, prefix = "", suffix = "", decimals = 0, className, }: CountUpAnimationProps) { const [count, setCount] = useState(start) const countRef = useRef(start) const [ref, inView] = useInView({ triggerOnce: true, threshold: 0.1, }) useEffect(() => { let startTime: number | null = null let animationFrame: number | null = null // Only start animation when in view and after delay if (!inView) return const timeout = setTimeout(() => { const animate = (timestamp: number) => { if (!startTime) startTime = timestamp const progress = Math.min((timestamp - startTime) / duration, 1) // Use easeOutExpo for a nice deceleration effect const easeOutExpo = 1 - Math.pow(2, -10 * progress) const currentCount = start + (end - start) * easeOutExpo countRef.current = currentCount setCount(currentCount) if (progress < 1) { animationFrame = requestAnimationFrame(animate) } else { // Ensure we end exactly at the target number countRef.current = end setCount(end) } } animationFrame = requestAnimationFrame(animate) }, delay) return () => { if (timeout) clearTimeout(timeout) if (animationFrame) cancelAnimationFrame(animationFrame) } }, [inView, start, end, duration, delay]) // Format the number with commas and decimals const formattedCount = () => { const value = Math.round(count * Math.pow(10, decimals)) / Math.pow(10, decimals) return ( prefix + value.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals, }) + suffix ) } return ( {formattedCount()} ) }