"use client"; import { animate, motion, MotionConfig, useInView, useReducedMotion } from "motion/react"; import { type CSSProperties, type ReactNode, useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; function useReducedMotionSafe() { const prefers = useReducedMotion(); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); return mounted && Boolean(prefers); } export interface Stat { value: number; decimals?: number; prefix?: string; suffix?: string; label: string; caption?: string; } export interface StatsBandProps { eyebrow?: string; title?: ReactNode; description?: ReactNode; stats: Stat[]; duration?: number; className?: string; } const ease = [0.23, 1, 0.32, 1] as const; function format(value: number, decimals = 0) { return value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }); } function CountUp({ stat, start, delay, duration }: { stat: Stat; start: boolean; delay: number; duration: number }) { const reduce = useReducedMotionSafe(); const ref = useRef(null); const final = format(stat.value, stat.decimals); useEffect(() => { const el = ref.current; if (!el) return; if (reduce) { el.textContent = final; return; } if (!start) { el.textContent = format(0, stat.decimals); return; } // Write straight to the node so a running count never re-renders React. const controls = animate(0, stat.value, { duration, delay, ease: [0.16, 1, 0.3, 1], onUpdate: (v) => { el.textContent = format(v, stat.decimals); }, }); return () => controls.stop(); }, [start, reduce, stat.value, stat.decimals, final, delay, duration]); return ( {final} {reduce ? final : format(0, stat.decimals)} ); } export function StatsBand({ eyebrow, title, description, stats, duration = 1.8, className }: StatsBandProps) { const ref = useRef(null); const inView = useInView(ref, { once: true, amount: 0.5 }); const columns = stats.length; return (
{(eyebrow || title || description) && (
{eyebrow &&

{eyebrow}

} {title && (

{title}

)}
{description && (

{description}

)}
)}
{stats.map((stat) => ( ))}
{stats.map((stat, i) => ( 0 && "lg:border-l", i >= 2 && "max-lg:border-t", i === 0 && "lg:ps-0", i % 2 === 0 && "max-lg:ps-0", )} initial={{ opacity: 0, y: 8 }} key={stat.label} transition={{ duration: 0.6, ease, delay: 0.15 + i * 0.08 }} >
{stat.label}
{stat.prefix} {format(stat.value, stat.decimals)} {stat.suffix} {stat.prefix && ( {stat.prefix} )} {stat.suffix && ( {stat.suffix} )}
{stat.caption && (
{stat.caption}
)}
))}
); }