feat: add src/components/Stats.tsx
This commit is contained in:
parent
fea59977ef
commit
a81ee8e4d0
|
|
@ -0,0 +1,114 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
const stats = [
|
||||
{
|
||||
value: 280000,
|
||||
suffix: "+",
|
||||
label: "Anciens militaires en France",
|
||||
description: "rejoignent chaque année le civil",
|
||||
},
|
||||
{
|
||||
value: 94,
|
||||
suffix: "%",
|
||||
label: "Taux de placement",
|
||||
description: "de nos candidats dans l'emploi",
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
suffix: " mois",
|
||||
label: "Durée moyenne",
|
||||
description: "pour trouver un emploi avec nous",
|
||||
},
|
||||
{
|
||||
value: 450,
|
||||
suffix: "+",
|
||||
label: "Entreprises partenaires",
|
||||
description: "recrutant des profils militaires",
|
||||
},
|
||||
];
|
||||
|
||||
function useCountUp(target: number, duration = 2000, start = false) {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!start) return;
|
||||
let startTime: number | null = null;
|
||||
const step = (timestamp: number) => {
|
||||
if (!startTime) startTime = timestamp;
|
||||
const progress = Math.min((timestamp - startTime) / duration, 1);
|
||||
setCount(Math.floor(progress * target));
|
||||
if (progress < 1) requestAnimationFrame(step);
|
||||
};
|
||||
requestAnimationFrame(step);
|
||||
}, [target, duration, start]);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
function StatCard({
|
||||
value,
|
||||
suffix,
|
||||
label,
|
||||
description,
|
||||
animate,
|
||||
}: (typeof stats)[0] & { animate: boolean }) {
|
||||
const count = useCountUp(value, 2000, animate);
|
||||
|
||||
return (
|
||||
<div className="text-center group">
|
||||
<div className="bg-military-dark rounded-lg p-8 border border-military-green/30 hover:border-gold/50 transition-all duration-300 hover:-translate-y-1 shadow-lg">
|
||||
<div className="font-serif text-5xl font-black text-gold mb-2">
|
||||
{count.toLocaleString("fr-FR")}
|
||||
{suffix}
|
||||
</div>
|
||||
<div className="text-white font-semibold text-lg mb-1">{label}</div>
|
||||
<div className="text-gray-400 text-sm">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Stats() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setVisible(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.3 }
|
||||
);
|
||||
if (ref.current) observer.observe(ref.current);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="stats"
|
||||
ref={ref}
|
||||
className="bg-military-dark py-20 border-y border-military-green/20"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<p className="text-gold text-sm font-semibold tracking-widest uppercase mb-2">
|
||||
En chiffres
|
||||
</p>
|
||||
<h2 className="font-serif text-3xl md:text-4xl font-bold text-white">
|
||||
La reconversion militaire en France
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{stats.map((stat) => (
|
||||
<StatCard key={stat.label} {...stat} animate={visible} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue