import React, { useRef, useEffect } from 'react'; import { SpotlightCard } from './SpotlightCard'; import gsap from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; gsap.registerPlugin(ScrollTrigger); export const StatCard = ({ label, value, suffix = '', icon: Icon, trend, trendLabel, color = 'blue', delay = 0 }) => { const counterRef = useRef(null); useEffect(() => { if (!counterRef.current) return; // Clean value string if it contains non-numeric chars (e.g., "$5,000") to get raw number // But for GSAP to animate "5,000" to "10,000" gracefully, best to animate a proxy object // and format on update. let targetValue = 0; let prefix = ''; if (typeof value === 'string') { // Check for prefix like $ if (value.startsWith('$')) prefix = '$'; // Parse number targetValue = parseFloat(value.replace(/[^0-9.-]+/g, "")); } else { targetValue = value; } const counter = { val: 0 }; const el = counterRef.current; // Initial state el.innerText = prefix + '0' + suffix; gsap.to(counter, { val: targetValue, duration: 2, ease: "power2.out", delay: delay, scrollTrigger: { trigger: el, start: "top 90%", toggleActions: "play none none none" }, onUpdate: () => { // Determine if we need integers or decimals based on target const isFloat = targetValue % 1 !== 0; const current = isFloat ? counter.val.toFixed(2) : Math.round(counter.val); el.innerText = prefix + current.toLocaleString('en-US') + suffix; } }); }, [value, suffix, delay]); // Color maps const colors = { blue: 'text-blue-500 bg-blue-500/10', emerald: 'text-emerald-500 bg-emerald-500/10', amber: 'text-amber-500 bg-amber-500/10', red: 'text-red-500 bg-red-500/10', purple: 'text-purple-500 bg-purple-500/10', }; // Safely get color class or default const colorClass = colors[color] || colors.blue; const textColor = colorClass.split(' ')[0]; const bgColor = colorClass.split(' ')[1]; return (

{label}

0
{Icon && (
)}
{trend && (
0 ? 'text-emerald-500' : 'text-red-500'} flex items-center`}> {trend > 0 ? '+' : ''}{trend}% {trendLabel || 'vs last month'}
)}
); };