The homepage hero shows "16 Models tracked · 8 Providers · ∞ Contributors" as static text. Animate the numbers so they count up when the page loads — makes the stats feel alive.
Expected behaviour:
On page load, numbers count up from 0 to their value over ~1 second
Easing: ease-out (fast start, slow finish)
Only plays once per page load (not on every re-render)
Respects prefers-reduced-motion — shows final value immediately if motion is reduced
Implementation: Use a custom useCountUp hook with requestAnimationFrame — no library needed.
function useCountUp(target: number, duration = 1000) {
const [count, setCount] = useState(0);
useEffect(() => {
let start = 0;
const step = (timestamp: number) => {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
setCount(Math.floor(progress * target));
if (progress < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}, [target, duration]);
return count;
}
Files to touch:
page.tsx
— homepage hero stats section
New hook:
useCountUp.ts
Acceptance criteria:
Numbers animate on load
Animation respects prefers-reduced-motion
Works correctly when data comes from the DB (dynamic count)
The homepage hero shows "16 Models tracked · 8 Providers · ∞ Contributors" as static text. Animate the numbers so they count up when the page loads — makes the stats feel alive.
Expected behaviour:
On page load, numbers count up from 0 to their value over ~1 second
Easing: ease-out (fast start, slow finish)
Only plays once per page load (not on every re-render)
Respects prefers-reduced-motion — shows final value immediately if motion is reduced
Implementation: Use a custom useCountUp hook with requestAnimationFrame — no library needed.
function useCountUp(target: number, duration = 1000) {
const [count, setCount] = useState(0);
useEffect(() => {
let start = 0;
const step = (timestamp: number) => {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
setCount(Math.floor(progress * target));
if (progress < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}, [target, duration]);
return count;
}
Files to touch:
page.tsx
— homepage hero stats section
New hook:
useCountUp.ts
Acceptance criteria:
Numbers animate on load
Animation respects prefers-reduced-motion
Works correctly when data comes from the DB (dynamic count)