feat: add AnimatedCounter component and update stats section with hero asset color adjustment
This commit is contained in:
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import HeroBackground3D from '@/components/ui/3d/HeroBackground3D';
|
import HeroBackground3D from '@/components/ui/3d/HeroBackground3D';
|
||||||
|
import AnimatedCounter from '@/components/ui/AnimatedCounter';
|
||||||
import CardIcon3D from '@/components/ui/3d/CardIcon3D';
|
import CardIcon3D from '@/components/ui/3d/CardIcon3D';
|
||||||
import { Zap, ArrowRight, Bike, Wallet, Leaf, ShieldCheck, MapPin, Truck, ChevronRight, BarChart3, BatteryCharging, Clock, Activity } from 'lucide-react';
|
import { Zap, ArrowRight, Bike, Wallet, Leaf, ShieldCheck, MapPin, Truck, ChevronRight, BarChart3, BatteryCharging, Clock, Activity } from 'lucide-react';
|
||||||
|
|
||||||
@@ -117,24 +118,34 @@ export default function LandingPage() {
|
|||||||
<div className="absolute top-24 -right-24 w-96 h-96 bg-emerald-500/20 rounded-full blur-3xl mix-blend-screen pointer-events-none"></div>
|
<div className="absolute top-24 -right-24 w-96 h-96 bg-emerald-500/20 rounded-full blur-3xl mix-blend-screen pointer-events-none"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Stats Section (Overlapping Hero) */}
|
{/* Stats Section */}
|
||||||
<section className="relative z-20 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 -mt-12 mb-20">
|
<section className="py-12 lg:py-16 bg-white border-b border-slate-100">
|
||||||
<div className="bg-white rounded-2xl shadow-2xl p-6 lg:p-10 grid grid-cols-2 lg:grid-cols-4 gap-8 divide-x divide-slate-100 border border-slate-100">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center px-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 divide-x divide-slate-100">
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">500+</div>
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
<div className="text-slate-500 font-medium text-sm">Active Riders</div>
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2">
|
||||||
</div>
|
<AnimatedCounter end={500} suffix="+" />
|
||||||
<div className="text-center px-4">
|
</h3>
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">200+</div>
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">Active Riders</p>
|
||||||
<div className="text-slate-500 font-medium text-sm">EV Fleet Size</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
<div className="text-center px-4">
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2">
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">18%</div>
|
<AnimatedCounter end={200} suffix="+" />
|
||||||
<div className="text-slate-500 font-medium text-sm">Avg. Investor ROI</div>
|
</h3>
|
||||||
</div>
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">EV Fleet Size</p>
|
||||||
<div className="text-center px-4">
|
</div>
|
||||||
<div className="text-3xl lg:text-4xl font-black text-accent mb-2">50k+</div>
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
<div className="text-slate-500 font-medium text-sm">Deliveries Done</div>
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2 text-accent">
|
||||||
|
<AnimatedCounter end={18} suffix="%" />
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">Avg. Investor ROI</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center justify-center text-center px-2">
|
||||||
|
<h3 className="text-4xl md:text-5xl font-black text-slate-900 mb-2">
|
||||||
|
<AnimatedCounter end={50} suffix="k+" />
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs sm:text-sm font-semibold text-slate-500 uppercase tracking-wider">Deliveries Done</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -166,7 +177,8 @@ export default function LandingPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Service 2 */}
|
{/* Service 2 - FICO Investment - User will redesign this section */}
|
||||||
|
{/*
|
||||||
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
||||||
<div className="absolute top-0 right-0 w-32 h-32 bg-investor/10 rounded-bl-full -mr-8 -mt-8 transition-transform group-hover:scale-110"></div>
|
<div className="absolute top-0 right-0 w-32 h-32 bg-investor/10 rounded-bl-full -mr-8 -mt-8 transition-transform group-hover:scale-110"></div>
|
||||||
<div className="w-16 h-16 bg-investor/10 rounded-2xl flex items-center justify-center mb-6 relative z-10 text-investor group-hover:bg-investor group-hover:text-white transition-colors">
|
<div className="w-16 h-16 bg-investor/10 rounded-2xl flex items-center justify-center mb-6 relative z-10 text-investor group-hover:bg-investor group-hover:text-white transition-colors">
|
||||||
@@ -174,14 +186,15 @@ export default function LandingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<h4 className="text-2xl font-bold text-slate-900 mb-3 relative z-10">FICO Investment</h4>
|
<h4 className="text-2xl font-bold text-slate-900 mb-3 relative z-10">FICO Investment</h4>
|
||||||
<p className="text-slate-600 mb-8 flex-grow relative z-10">Invest in fractional EV ownership. Let us manage the fleet while you enjoy guaranteed monthly returns.</p>
|
<p className="text-slate-600 mb-8 flex-grow relative z-10">Invest in fractional EV ownership. Let us manage the fleet while you enjoy guaranteed monthly returns.</p>
|
||||||
{/* <button
|
<button
|
||||||
onClick={() => handleLogin('investor@email.com')}
|
onClick={() => handleLogin('investor@email.com')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="inline-flex items-center gap-2 text-investor font-bold group-hover:translate-x-2 transition-transform relative z-10"
|
className="inline-flex items-center gap-2 text-investor font-bold group-hover:translate-x-2 transition-transform relative z-10"
|
||||||
>
|
>
|
||||||
Login as Investor <ChevronRight className="w-5 h-5" />
|
Login as Investor <ChevronRight className="w-5 h-5" />
|
||||||
</button> */}
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
|
|
||||||
{/* Service 3 */}
|
{/* Service 3 */}
|
||||||
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
<div className="group bg-white rounded-3xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 border border-slate-100 relative overflow-hidden flex flex-col h-full">
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ function Battery({ position, scale = 1, rotation = [0, 0, 0] }: { position: [num
|
|||||||
{/* Main Battery Body */}
|
{/* Main Battery Body */}
|
||||||
<mesh position={[0, 0, 0]} castShadow receiveShadow>
|
<mesh position={[0, 0, 0]} castShadow receiveShadow>
|
||||||
<boxGeometry args={[0.6, 1.2, 0.4]} />
|
<boxGeometry args={[0.6, 1.2, 0.4]} />
|
||||||
<meshStandardMaterial color="#41694cff" roughness={0.4} metalness={0.8} />
|
<meshStandardMaterial color="#1e293b" roughness={0.4} metalness={0.8} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Top Cap */}
|
{/* Top Cap */}
|
||||||
|
|||||||
45
src/components/ui/AnimatedCounter.tsx
Normal file
45
src/components/ui/AnimatedCounter.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
'use client';
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export default function AnimatedCounter({ end, duration = 2000, suffix = "" }: { end: number, duration?: number, suffix?: string }) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const [hasStarted, setHasStarted] = useState(false);
|
||||||
|
const elementRef = useRef<HTMLSpanElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
if (entries[0].isIntersecting && !hasStarted) {
|
||||||
|
setHasStarted(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (elementRef.current) {
|
||||||
|
observer.observe(elementRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [hasStarted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasStarted) return;
|
||||||
|
|
||||||
|
let startTime: number | null = null;
|
||||||
|
const animate = (currentTime: number) => {
|
||||||
|
if (!startTime) startTime = currentTime;
|
||||||
|
const progress = Math.min((currentTime - startTime) / duration, 1);
|
||||||
|
// Ease out cubic function for smooth deceleration
|
||||||
|
const easeOut = 1 - Math.pow(1 - progress, 3);
|
||||||
|
const currentCount = Math.floor(easeOut * end);
|
||||||
|
|
||||||
|
setCount(currentCount);
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}, [end, duration, hasStarted]);
|
||||||
|
|
||||||
|
return <span ref={elementRef}>{count}{suffix}</span>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user