"use client"; import { useInView } from "motion/react"; import { useEffect, useRef, useState } from "react"; import { useReducedMotion } from "@/app/hooks/useReducedMotion"; interface CounterValueProps { value: number; className?: string; duration?: number; prefix?: string; suffix?: string; once?: boolean; amount?: number; } export function CounterValue({ value, className = "", duration = 1200, prefix = "", suffix = "", once = true, amount = 0.45, }: Readonly) { const shouldReduceMotion = useReducedMotion(); const ref = useRef(null); const isInView = useInView(ref, { once, amount }); const [displayValue, setDisplayValue] = useState(0); useEffect(() => { if (!isInView) return; if (shouldReduceMotion) { setDisplayValue(value); return; } const start = performance.now(); let rafId = 0; const frame = (now: number) => { const elapsed = now - start; const progress = Math.min(elapsed / duration, 1); const eased = 1 - (1 - progress) ** 3; setDisplayValue(Math.round(value * eased)); if (progress < 1) { rafId = requestAnimationFrame(frame); } }; rafId = requestAnimationFrame(frame); return () => cancelAnimationFrame(rafId); }, [isInView, value, duration, shouldReduceMotion]); return ( {prefix} {displayValue} {suffix} ); }