This commit is contained in:
2026-07-14 20:19:21 +00:00
parent db9370f916
commit 573a4ae915
52 changed files with 2955 additions and 2366 deletions

View File

@@ -0,0 +1,36 @@
import { useEffect, useRef, useState } from 'react'
export default function CountUp({ end, duration = 1000, suffix = '' }) {
const [display, setDisplay] = useState(0)
const ref = useRef(null)
const started = useRef(false)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !started.current) {
started.current = true
const startTime = performance.now()
const animate = (now) => {
const elapsed = now - startTime
const progress = Math.min(elapsed / duration, 1)
// ease-out quad
const eased = 1 - (1 - progress) * (1 - progress)
setDisplay(Math.round(eased * end))
if (progress < 1) requestAnimationFrame(animate)
}
requestAnimationFrame(animate)
observer.disconnect()
}
}, { threshold: 0.3 })
observer.observe(el)
return () => observer.disconnect()
}, [end, duration])
return <span ref={ref}>{display}{suffix}</span>
}