diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx index 98760ff..e268f30 100755 --- a/src/components/Stats.tsx +++ b/src/components/Stats.tsx @@ -10,12 +10,28 @@ const stats = [ { value: 5, suffix: "", label: "Industry Verticals", description: "Sports, corporate, financial, aerospace, defense" }, ]; +function formatCount(count: number, value: number) { + return count >= 1000 && value >= 1000 + ? `${(count / 1000).toFixed(1).replace(/\.0$/, "")}k` + : count.toString(); +} + function AnimatedNumber({ value, suffix }: { value: number; suffix: string }) { - const [count, setCount] = useState(0); + // Start with the final value so SSR/crawlers/OG-previewers see real numbers. + // Animate from 0 -> value only after hydration, when the user can see it. + const [count, setCount] = useState(value); + const [hasMounted, setHasMounted] = useState(false); const ref = useRef(null); const hasAnimated = useRef(false); useEffect(() => { + setHasMounted(true); + }, []); + + useEffect(() => { + if (!hasMounted) return; + // Reset to 0 only on the client, then count up. + setCount(0); const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !hasAnimated.current) { @@ -39,13 +55,11 @@ function AnimatedNumber({ value, suffix }: { value: number; suffix: string }) { ); if (ref.current) observer.observe(ref.current); return () => observer.disconnect(); - }, [value]); + }, [value, hasMounted]); return ( - {count >= 1000 && value >= 1000 - ? `${(count / 1000).toFixed(1).replace(/\.0$/, "")}k` - : count} + {formatCount(count, value)} {suffix} ); @@ -53,7 +67,7 @@ function AnimatedNumber({ value, suffix }: { value: number; suffix: string }) { export default function Stats() { return ( -
+
{stats.map((stat, i) => (