diff --git a/src/components/StatsBar.tsx b/src/components/StatsBar.tsx new file mode 100644 index 0000000..e1f3b6a --- /dev/null +++ b/src/components/StatsBar.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { useInView } from 'framer-motion'; +import { useLocale, useTranslations } from 'next-intl'; + +type CountUpProps = { + end: number; + duration?: number; +}; + +function CountUp({ end, duration = 1800 }: CountUpProps) { + const [count, setCount] = useState(0); + const ref = useRef(null); + const isInView = useInView(ref, { once: true, margin: '-50px' }); + const locale = useLocale(); + + useEffect(() => { + if (!isInView) return; + + let startTime: number | null = null; + const startValue = 0; + + const animate = (timestamp: number) => { + if (!startTime) startTime = timestamp; + const elapsed = timestamp - startTime; + const progress = Math.min(elapsed / duration, 1); + + // Easing function: easeOutQuad + const easeProgress = progress * (2 - progress); + + const current = Math.floor(easeProgress * (end - startValue) + startValue); + setCount(current); + + if (progress < 1) { + requestAnimationFrame(animate); + } else { + setCount(end); + } + }; + + requestAnimationFrame(animate); + }, [isInView, end, duration]); + + return {count.toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US')}; +} + +export default function StatsBar() { + const t = useTranslations('Stats'); + + const stats = [ + { value: 28, key: 'articles' }, + { value: 118280, key: 'views' }, + { value: 4789, key: 'reactions' }, + ]; + + return ( +
+
+ {stats.map((stat, idx) => ( +
+
+ +
+
+ {t(stat.key)} +
+
+ ))} +
+
+ ); +}