Optimize mobile layout, add animated counters, improve crane swing

This commit is contained in:
Satyam
2026-02-16 19:30:10 +05:30
parent ed79b266af
commit 35cbdeb33c
12 changed files with 573 additions and 163 deletions
+51 -6
View File
@@ -1,6 +1,10 @@
import React from 'react';
import React, { useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Icons } from './landing/constants';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
const services = [
{
@@ -12,7 +16,7 @@ const services = [
badge: "Licensed",
badgeClass: "permit-stamp--verified",
badgeIcon: "\u2713",
stat: "2,847",
stat: 5847,
statLabel: "Properties Inspected",
},
{
@@ -24,13 +28,47 @@ const services = [
badge: "Certified",
badgeClass: "permit-stamp--accent",
badgeIcon: "\u25B9",
stat: "96%",
stat: 96,
statSuffix: "%",
statLabel: "Lead Accuracy",
}
];
const Services = () => {
const navigate = useNavigate();
const statRefs = useRef([]);
useEffect(() => {
// Animated Stat Counters
statRefs.current.forEach((el) => {
if (!el) return;
const target = parseFloat(el.dataset.target);
const suffix = el.dataset.suffix || '';
const counter = { value: 0 };
el.innerText = '0' + suffix;
const formatNumber = (num) => {
return Math.round(num).toLocaleString('en-US');
};
gsap.to(counter, {
value: target,
duration: 2.5,
ease: "power2.out",
scrollTrigger: {
trigger: el,
start: "top 85%",
toggleActions: "play none none none",
},
onUpdate: () => {
el.innerText = formatNumber(counter.value) + suffix;
},
snap: { value: 1 }
});
});
}, []);
const handleMouseMove = (e) => {
const card = e.currentTarget;
@@ -65,7 +103,7 @@ const Services = () => {
key={idx}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
className="group relative p-8 rounded-2xl border border-zinc-200 dark:border-white/10 bg-white dark:bg-white/[0.04] hover:bg-zinc-50 dark:hover:bg-white/[0.07] backdrop-blur-sm transition-all duration-200 cursor-pointer overflow-hidden shadow-xl dark:shadow-[0_20px_60px_rgba(0,0,0,0.4)]"
className="group relative p-6 md:p-8 rounded-2xl border border-zinc-200 dark:border-white/10 bg-white dark:bg-white/[0.04] hover:bg-zinc-50 dark:hover:bg-white/[0.07] backdrop-blur-sm transition-all duration-200 cursor-pointer overflow-hidden shadow-xl dark:shadow-[0_20px_60px_rgba(0,0,0,0.4)]"
style={{ transformStyle: 'preserve-3d', transition: 'transform 0.1s ease-out' }}
>
{/* Measurement ticks — top */}
@@ -93,9 +131,16 @@ const Services = () => {
{service.desc}
</p>
{/* Inline stat */}
{/* Inline stat with animation */}
<div className="flex items-baseline gap-2 mb-6">
<span className="font-mono text-2xl font-bold text-zinc-900 dark:text-white tracking-tight">{service.stat}</span>
<span
ref={(el) => statRefs.current[idx] = el}
data-target={service.stat}
data-suffix={service.statSuffix || ''}
className="font-mono text-2xl font-bold text-zinc-900 dark:text-white tracking-tight"
>
0{service.statSuffix || ''}
</span>
<span className="text-xs text-zinc-500 font-medium uppercase tracking-wider">{service.statLabel}</span>
</div>