"use client"; import { useEffect, useRef, useState } from "react"; import "./odometer-stats.css"; /** * Odometer Stats * Big numbers that read like a mechanical counter. Each digit is a vertical * reel of 0–9 that spins to its value when the section scrolls into view, * with the rightmost reels arriving last. Separators and suffixes stay still. */ export type OdometerStat = { value: string; label: string }; function Reel({ ch, delay, run }: { ch: string; delay: number; run: boolean }) { const isDigit = /\d/.test(ch); if (!isDigit) return ; const n = Number(ch); return ( ); } export function OdometerStats({ stats }: { stats: OdometerStat[] }) { const ref = useRef(null); const [run, setRun] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return setRun(true); const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setRun(true); io.disconnect(); } }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, []); return (
{stats.map((s, si) => { const chars = Array.from(s.value); return (
{s.label}
{s.value}
); })}
); }