import { useState, useEffect, useRef } from 'react'; interface UseCountUpOptions { end: number; duration?: number; decimals?: number; startOnInView?: boolean; } export const useCountUp = ({ end, duration = 2000, decimals = 0, startOnInView = true }: UseCountUpOptions) => { const [count, setCount] = useState(0); const [isVisible, setIsVisible] = useState(false); const [hasStarted, setHasStarted] = useState(false); const elementRef = useRef(null); // Intersection Observer for triggering animation when element comes into view useEffect(() => { if (!startOnInView) { setHasStarted(true); return; } const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !hasStarted) { setIsVisible(true); setHasStarted(true); } }, { threshold: 0.1 } ); if (elementRef.current) { observer.observe(elementRef.current); } return () => observer.disconnect(); }, [startOnInView, hasStarted]); // Counter animation useEffect(() => { if (!hasStarted) return; let startTime: number; let animationFrame: number; const animate = (currentTime: number) => { if (!startTime) startTime = currentTime; const progress = Math.min((currentTime - startTime) / duration, 1); // Easing function for smooth animation const easeOutCubic = 1 - Math.pow(1 - progress, 3); const currentCount = end * easeOutCubic; setCount(currentCount); if (progress < 1) { animationFrame = requestAnimationFrame(animate); } }; animationFrame = requestAnimationFrame(animate); return () => { if (animationFrame) { cancelAnimationFrame(animationFrame); } }; }, [end, duration, hasStarted]); const formattedCount = count.toFixed(decimals); return { count: formattedCount, elementRef }; }; // Utility function to parse numbers from strings like "150+", "99.9%", "<2min" export const parseNumberFromString = (value: string): number => { const numericMatch = value.match(/(\d+\.?\d*)/); return numericMatch ? parseFloat(numericMatch[1]) : 0; }; // Utility function to format the final value with original suffix/prefix export const formatWithOriginalString = (originalValue: string, animatedNumber: string): string => { const numericMatch = originalValue.match(/(\d+\.?\d*)/); if (!numericMatch) return originalValue; const originalNumber = numericMatch[1]; return originalValue.replace(originalNumber, animatedNumber); };