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" },
|
2026-04-18 16:27:46 -04:00
|
|
|
{ value: 8, suffix: "+", label: "Major Facilities", description: "Designed & integrated" },
|
2026-04-17 15:51:01 -04:00
|
|
|
{ value: 5900, suffix: "+", label: "End Users Served", description: "Global content delivery" },
|
2026-04-18 16:27:46 -04:00
|
|
|
{ value: 5, suffix: "", label: "Industry Verticals", description: "Sports, corporate, financial, aerospace, defense" },
|
2026-04-17 15:51:01 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
|
|
|
|
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 (
|
2026-04-18 16:27:46 -04:00
|
|
|
<section className="py-16 md:py-20 bg-white border-t border-neutral-100">
|
2026-04-17 15:51:01 -04:00
|
|
|
<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">
|
2026-04-18 16:27:46 -04:00
|
|
|
<div className="text-4xl md:text-5xl font-semibold text-primary mb-1.5">
|
2026-04-17 15:51:01 -04:00
|
|
|
<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>
|
|
|
|
|
);
|
|
|
|
|
}
|