'use client' import { useEffect, useRef, useState } from 'react' interface AnimatedNumberProps { value: number duration?: number className?: string } export function AnimatedNumber({ value, duration = 2000, className = '' }: AnimatedNumberProps) { const [displayValue, setDisplayValue] = useState(0) const [isVisible, setIsVisible] = useState(false) const elementRef = useRef(null) useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting && !isVisible) { setIsVisible(true) } }) }, { threshold: 0.1 } ) if (elementRef.current) { observer.observe(elementRef.current) } return () => { if (elementRef.current) { observer.unobserve(elementRef.current) } } }, [isVisible]) useEffect(() => { if (!isVisible || value === 0) { setDisplayValue(0) return } const startTime = Date.now() const startValue = 0 const endValue = value const animate = () => { const now = Date.now() const elapsed = now - startTime const progress = Math.min(elapsed / duration, 1) // Easing function for bounce effect (ease-out-bounce) const easeOutBounce = (t: number): number => { if (t < 1 / 2.75) { return 7.5625 * t * t } else if (t < 2 / 2.75) { return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75 } else if (t < 2.5 / 2.75) { return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375 } else { return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375 } } const easedProgress = easeOutBounce(progress) const currentValue = Math.floor(startValue + (endValue - startValue) * easedProgress) setDisplayValue(currentValue) if (progress < 1) { requestAnimationFrame(animate) } else { setDisplayValue(endValue) } } const frameId = requestAnimationFrame(animate) return () => cancelAnimationFrame(frameId) }, [isVisible, value, duration]) return (
{displayValue}
) }