Files
740cdd13-bb52-43a6-8245-f61…/src/components/home/Stats.tsx
“dongming” 2a8369ac46 first commit
2025-12-29 11:46:49 +08:00

146 lines
4.8 KiB
TypeScript

import { useEffect, useRef, useState } from 'react'
import { statsData, serviceListData, commonDescriptions } from '../../data/siteData'
function useCountUp(end: number, duration: number = 2000, startCounting: boolean = false) {
const [count, setCount] = useState(0)
useEffect(() => {
if (!startCounting) return
let startTime: number
let animationFrame: number
const animate = (currentTime: number) => {
if (!startTime) startTime = currentTime
const progress = Math.min((currentTime - startTime) / duration, 1)
setCount(Math.floor(progress * end))
if (progress < 1) {
animationFrame = requestAnimationFrame(animate)
}
}
animationFrame = requestAnimationFrame(animate)
return () => cancelAnimationFrame(animationFrame)
}, [end, duration, startCounting])
return count
}
function StatItem({ value, label, startCounting }: { value: number; label: string; startCounting: boolean }) {
const count = useCountUp(value, 2000, startCounting)
return (
<div className="stats-1-left">
<h4 className="text-[36px] font-semibold mb-1.5 font-sans" style={{ color: '#ffffff' }}>{count}</h4>
<h6 className="text-lg" style={{ color: '#ffffff' }}>{label}</h6>
</div>
)
}
export default function Stats() {
const sectionRef = useRef<HTMLElement>(null)
const [startCounting, setStartCounting] = useState(false)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setStartCounting(true)
observer.disconnect()
}
},
{ threshold: 0.3 }
)
if (sectionRef.current) {
observer.observe(sectionRef.current)
}
return () => observer.disconnect()
}, [])
return (
<section
ref={sectionRef}
className="py-16 relative"
id="stats"
style={{
backgroundImage: "url('/src/assets/images/2.jpg')",
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
{/* Overlay */}
<div
className="absolute inset-0 z-0"
style={{ background: 'rgba(10, 30, 80, 0.92)' }}
/>
<div className="container mx-auto px-4 py-3 relative z-10">
{/* Section Header */}
<div className="text-center max-w-3xl mx-auto mb-8">
<h3 className="text-3xl md:text-4xl font-bold mb-4 font-sans" style={{ color: '#ffffff' }}>
{commonDescriptions.statsTitle}
</h3>
<p className="my-3" style={{ color: '#d0d0d0' }}>
{commonDescriptions.sectionDesc}
</p>
</div>
<div className="flex flex-wrap lg:flex-nowrap gap-12 pt-8 mt-3">
{/* Left Content */}
<div className="w-full lg:w-5/12">
<h4 className="text-[38px] leading-[46px] font-semibold mb-4 font-sans" style={{ color: '#ffffff' }}>
{commonDescriptions.statsSubtitle}
</h4>
<p className="mt-2.5 text-base leading-6" style={{ color: '#d0d0d0' }}>
{commonDescriptions.statsDesc}
</p>
<p className="mt-2.5 text-base leading-6" style={{ color: '#d0d0d0' }}>
{commonDescriptions.statsDesc2}
</p>
{/* Stats Grid */}
<div className="grid grid-cols-3 gap-2.5 mt-12">
{statsData.map((stat) => (
<StatItem
key={stat.id}
value={stat.value}
label={stat.label}
startCounting={startCounting}
/>
))}
</div>
</div>
{/* Right Content - Service List */}
<div className="w-full lg:w-7/12 my-8 lg:my-0">
<div className="grid grid-cols-1 md:grid-cols-2 gap-[30px]">
{serviceListData.map((service) => (
<div
key={service.id}
className="group stats-service-card flex gap-4 p-10 rounded"
>
<div className="flex-shrink-0">
<span className={`fa fa-${service.icon} text-[32px] text-secondary transition-colors duration-300`} />
</div>
<div>
<h6 className="font-bold mb-2.5 service-title">
<a href="#url" className="text-white group-hover:text-title transition-colors duration-300">
{service.title}
</a>
</h6>
<p className="text-[15px] leading-[25px] mt-2.5 text-[#d0d0d0] group-hover:text-title transition-colors duration-300">
{service.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
)
}