135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
'use client';
|
|
|
|
import Image from 'next/image';
|
|
import { useState, useEffect, useRef } from 'react';
|
|
import styles from './StatsCounter.module.css';
|
|
|
|
export default function StatsCounter() {
|
|
const [hasAnimated, setHasAnimated] = useState(false);
|
|
const sectionRef = useRef<HTMLElement>(null);
|
|
|
|
// Stats State
|
|
const [counts, setCounts] = useState({
|
|
users: 0,
|
|
posts: 0,
|
|
satisfaction: 0
|
|
});
|
|
|
|
const targets = {
|
|
users: 15, // 15K+
|
|
posts: 2, // 2M+
|
|
satisfaction: 99 // 99%
|
|
};
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
if (entries[0].isIntersecting && !hasAnimated) {
|
|
setHasAnimated(true);
|
|
}
|
|
},
|
|
{ threshold: 0.5 }
|
|
);
|
|
|
|
if (sectionRef.current) {
|
|
observer.observe(sectionRef.current);
|
|
}
|
|
|
|
return () => observer.disconnect();
|
|
}, [hasAnimated]);
|
|
|
|
useEffect(() => {
|
|
if (!hasAnimated) return;
|
|
|
|
const duration = 2000; // 2s
|
|
const steps = 60;
|
|
const intervalTime = duration / steps;
|
|
|
|
let currentStep = 0;
|
|
|
|
const timer = setInterval(() => {
|
|
currentStep++;
|
|
const progress = Math.min(currentStep / steps, 1);
|
|
|
|
// Easing function for smooth count
|
|
const easeOutQuart = (x: number) => 1 - Math.pow(1 - x, 4);
|
|
const value = easeOutQuart(progress);
|
|
|
|
setCounts({
|
|
users: Math.floor(targets.users * value),
|
|
posts: Math.floor(targets.posts * value * 10) / 10, // Keep decimal for smaller numbers if needed, here integers roughly
|
|
satisfaction: Math.floor(targets.satisfaction * value)
|
|
});
|
|
|
|
if (currentStep >= steps) clearInterval(timer);
|
|
}, intervalTime);
|
|
|
|
return () => clearInterval(timer);
|
|
}, [hasAnimated]);
|
|
|
|
return (
|
|
<section className={styles.statsSection} ref={sectionRef}>
|
|
<div className={styles.container}>
|
|
<div className={styles.statsGrid}>
|
|
{/* Stat 1 */}
|
|
<div className={styles.statItem}>
|
|
<div className={styles.iconWrapper}>
|
|
<div className={styles.iconBg}></div>
|
|
<Image
|
|
src="/images/home/growing.webp"
|
|
alt="Growing Community"
|
|
width={38}
|
|
height={44}
|
|
/>
|
|
</div>
|
|
<div className={styles.textContent}>
|
|
<div className={styles.numberWrapper}>
|
|
{hasAnimated ? counts.users : 0}k+
|
|
</div>
|
|
<p className={styles.label}>Growing Community</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stat 2 */}
|
|
<div className={styles.statItem}>
|
|
<div className={styles.iconWrapper}>
|
|
<div className={styles.iconBg}></div>
|
|
<Image
|
|
src="/images/home/content.webp"
|
|
alt="Content Published"
|
|
width={38}
|
|
height={44}
|
|
/>
|
|
</div>
|
|
<div className={styles.textContent}>
|
|
<div className={styles.numberWrapper}>
|
|
{hasAnimated ? '2' : '0'}M+
|
|
</div>
|
|
<p className={styles.label}>Content Published</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stat 3 */}
|
|
<div className={styles.statItem}>
|
|
<div className={styles.iconWrapper}>
|
|
<div className={styles.iconBg}></div>
|
|
<Image
|
|
src="/images/home/approval.webp"
|
|
alt="Customer Approval"
|
|
width={38}
|
|
height={44}
|
|
/>
|
|
</div>
|
|
<div className={styles.textContent}>
|
|
<div className={styles.numberWrapper}>
|
|
{hasAnimated ? counts.satisfaction : 0}%
|
|
</div>
|
|
<p className={styles.label}>Customer Approval</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|