wilddragon-site/src/components/Stats.tsx

82 lines
2.7 KiB
TypeScript
Raw Normal View History

2026-04-17 15:51:01 -04:00
"use client";
import { useEffect, useRef, useState } from "react";
import ScrollReveal from "./ScrollReveal";
const stats = [
{ value: 10, suffix: "+", label: "Years in Broadcast", description: "Production & engineering" },
{ value: 8, suffix: "", label: "Major Facilities", description: "Designed & integrated" },
{ value: 5900, suffix: "+", label: "End Users Served", description: "Global content delivery" },
{ value: 3, suffix: "", label: "Industry Verticals", description: "Sports, corporate, financial, aerospace, defense" },
];
function AnimatedNumber({ value, suffix }: { value: number; suffix: string }) {
const [count, setCount] = useState(0);
const ref = useRef<HTMLSpanElement>(null);
const hasAnimated = useRef(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !hasAnimated.current) {
hasAnimated.current = true;
const duration = 2000;
const steps = 60;
const increment = value / steps;
let current = 0;
const timer = setInterval(() => {
current += increment;
if (current >= value) {
setCount(value);
clearInterval(timer);
} else {
setCount(Math.floor(current));
}
}, duration / steps);
}
},
{ threshold: 0.3 }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, [value]);
const formatted = count >= 1000 ? `${(count / 1000).toFixed(count >= value ? 1 : 0).replace(/\.0$/, "")}k` : count;
return (
<span ref={ref} className="tabular-nums">
{count >= 1000 && value >= 1000
? `${(count / 1000).toFixed(1).replace(/\.0$/, "")}k`
: count}
{suffix}
</span>
);
}
export default function Stats() {
return (
<section className="py-20 md:py-24 bg-white border-t border-neutral-100">
<div className="max-w-6xl mx-auto px-6">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-10 md:gap-6">
{stats.map((stat, i) => (
<ScrollReveal key={stat.label} delay={i * 80}>
<div className="text-center md:text-left">
<div className="text-4xl md:text-5xl font-semibold text-primary mb-2">
<AnimatedNumber value={stat.value} suffix={stat.suffix} />
</div>
<p className="text-sm font-medium text-primary mb-1">
{stat.label}
</p>
<p className="text-xs text-muted">
{stat.description}
</p>
</div>
</ScrollReveal>
))}
</div>
</div>
</section>
);
}