website-monitor/frontend/components/landing/LiveStatsBar.tsx

180 lines
6.1 KiB
TypeScript

'use client'
import { motion } from 'framer-motion'
import { useState, useEffect } from 'react'
import { Activity, TrendingUp, Zap, Shield } from 'lucide-react'
function AnimatedNumber({ value, suffix = '' }: { value: number; suffix?: string }) {
const [displayValue, setDisplayValue] = useState(0)
useEffect(() => {
const duration = 2000 // 2 seconds
const steps = 60
const increment = value / steps
const stepDuration = duration / steps
let currentStep = 0
const interval = setInterval(() => {
currentStep++
if (currentStep <= steps) {
setDisplayValue(Math.floor(increment * currentStep))
} else {
setDisplayValue(value)
clearInterval(interval)
}
}, stepDuration)
return () => clearInterval(interval)
}, [value])
return (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))] tabular-nums">
{displayValue.toLocaleString()}{suffix}
</span>
)
}
function FluctuatingNumber({ base, variance }: { base: number; variance: number }) {
const [value, setValue] = useState(base)
useEffect(() => {
const interval = setInterval(() => {
const fluctuation = (Math.random() - 0.5) * variance
setValue(base + fluctuation)
}, 1500)
return () => clearInterval(interval)
}, [base, variance])
return (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))] tabular-nums">
{Math.round(value)}ms
</span>
)
}
export function LiveStatsBar() {
const stats = [
{
icon: <Activity className="h-5 w-5" />,
label: 'Checks performed today',
value: 2847,
type: 'counter' as const
},
{
icon: <TrendingUp className="h-5 w-5" />,
label: 'Changes detected this hour',
value: 127,
type: 'counter' as const
},
{
icon: <Shield className="h-5 w-5" />,
label: 'Uptime',
value: '99.9%',
type: 'static' as const
},
{
icon: <Zap className="h-5 w-5" />,
label: 'Avg response time',
value: '< ',
type: 'fluctuating' as const,
base: 42,
variance: 10
}
]
return (
<section className="border-y border-border bg-gradient-to-r from-foreground/95 via-foreground to-foreground/95 dark:from-secondary dark:via-secondary dark:to-secondary py-8 overflow-hidden">
<div className="mx-auto max-w-7xl px-6">
{/* Desktop: Grid */}
<div className="hidden lg:grid lg:grid-cols-4 gap-8">
{stats.map((stat, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
className="flex flex-col items-center text-center gap-3"
>
{/* Icon */}
<motion.div
className="flex items-center justify-center w-12 h-12 rounded-full bg-[hsl(var(--teal))]/10 text-[hsl(var(--teal))]"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ duration: 0.2 }}
>
{stat.icon}
</motion.div>
{/* Value */}
<div>
{stat.type === 'counter' && typeof stat.value === 'number' && (
<AnimatedNumber value={stat.value} />
)}
{stat.type === 'static' && (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))]">
{stat.value}
</span>
)}
{stat.type === 'fluctuating' && stat.base && stat.variance && (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))]">
{stat.value}<FluctuatingNumber base={stat.base} variance={stat.variance} />
</span>
)}
</div>
{/* Label */}
<p className="text-xs font-medium text-white/90 uppercase tracking-wider">
{stat.label}
</p>
</motion.div>
))}
</div>
{/* Mobile: Horizontal Scroll */}
<div className="lg:hidden overflow-x-auto scrollbar-thin pb-2">
<div className="flex gap-8 min-w-max px-4">
{stats.map((stat, i) => (
<motion.div
key={i}
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
className="flex flex-col items-center text-center gap-3 min-w-[160px]"
>
{/* Icon */}
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-[hsl(var(--teal))]/10 text-[hsl(var(--teal))]">
{stat.icon}
</div>
{/* Value */}
<div>
{stat.type === 'counter' && typeof stat.value === 'number' && (
<AnimatedNumber value={stat.value} />
)}
{stat.type === 'static' && (
<span className="font-mono text-2xl font-bold text-[hsl(var(--teal))]">
{stat.value}
</span>
)}
{stat.type === 'fluctuating' && stat.base && stat.variance && (
<span className="font-mono text-2xl font-bold text-[hsl(var(--teal))]">
{stat.value}<FluctuatingNumber base={stat.base} variance={stat.variance} />
</span>
)}
</div>
{/* Label */}
<p className="text-[10px] font-medium text-white/90 uppercase tracking-wider">
{stat.label}
</p>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}