import React, { useState, useEffect, useRef } from 'react'; import { cn } from '@/lib/utils'; interface AnimatedNumberProps { value: number; duration?: number; className?: string; suffix?: string; prefix?: string; decimals?: number; delay?: number; threshold?: number; } const AnimatedNumber: React.FC = ({ value, duration = 2000, className, suffix = '', prefix = '', decimals = 0, delay = 0, threshold = 0.1, }) => { const [count, setCount] = useState(0); const countRef = useRef(null); const startedRef = useRef(false); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !startedRef.current) { startedRef.current = true; setTimeout(() => { const startTime = Date.now(); const startValue = 0; const updateCount = () => { const now = Date.now(); const progress = Math.min((now - startTime) / duration, 1); const easedProgress = easeOutQuart(progress); const currentValue = startValue + (value - startValue) * easedProgress; setCount(currentValue); if (progress < 1) { requestAnimationFrame(updateCount); } }; requestAnimationFrame(updateCount); }, delay); observer.unobserve(entry.target); } }, { threshold } ); if (countRef.current) { observer.observe(countRef.current); } return () => { if (countRef.current) { observer.unobserve(countRef.current); } }; }, [value, duration, delay, threshold]); // Easing function for smoother animation const easeOutQuart = (x: number): number => { return 1 - Math.pow(1 - x, 4); }; return ( {prefix} {count.toFixed(decimals)} {suffix} ); }; export default AnimatedNumber;