Files
gridpilot.gg/apps/website/components/mockups/DriverProfileMockup.tsx
2025-12-02 00:19:49 +01:00

220 lines
7.4 KiB
TypeScript

'use client';
import { motion, useReducedMotion, useMotionValue, useSpring, useTransform } from 'framer-motion';
import { useEffect } from 'react';
export default function DriverProfileMockup() {
const shouldReduceMotion = useReducedMotion();
const stats = [
{ label: 'Wins', value: 24 },
{ label: 'Podiums', value: 48 },
{ label: 'Championships', value: 3 },
{ label: 'Races', value: 156 },
{ label: 'Finish Rate', value: 94, suffix: '%' }
];
const formData = [85, 72, 68, 91, 88, 95, 88, 79, 82, 91];
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: shouldReduceMotion ? 0 : 0.1 }
}
};
const itemVariants = {
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
visible: {
opacity: 1,
y: 0,
transition: { type: 'spring' as const, stiffness: 200, damping: 20 }
}
};
return (
<div className="relative w-full h-full bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite rounded-lg p-6 md:p-8 overflow-hidden">
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, y: 0 }}
className="mb-6"
>
<div className="flex items-center justify-between mb-4">
<div>
<div className="h-6 w-48 bg-white/10 rounded mb-2"></div>
<div className="h-4 w-24 bg-white/5 rounded"></div>
</div>
<div className="text-4xl font-bold text-charcoal-outline">#33</div>
</div>
<div className="flex items-center gap-4 mb-2">
<div className="text-xs text-gray-400">GridPilot Rating:</div>
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={2150} />
<div className="text-xs text-gray-400 ml-4">iRating:</div>
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={3200} />
</div>
<div className="relative h-3 bg-charcoal-outline rounded-full overflow-hidden">
<motion.div
className="absolute inset-y-0 left-0 bg-gradient-to-r from-primary-blue to-neon-aqua rounded-full"
initial={{ width: '0%' }}
animate={{ width: '86%' }}
transition={{ delay: shouldReduceMotion ? 0 : 0.4, duration: 0.8, ease: 'easeOut' }}
/>
</div>
<div className="flex justify-end mt-1">
<span className="text-xs text-gray-400">86%</span>
</div>
</motion.div>
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="mb-6"
>
<div className="h-4 w-32 bg-white/10 rounded mb-3"></div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
{stats.map((stat, index) => (
<motion.div
key={stat.label}
variants={itemVariants}
className="bg-iron-gray/50 border border-charcoal-outline rounded-lg p-3 text-center"
>
<AnimatedCounter
value={stat.value}
shouldReduceMotion={shouldReduceMotion ?? false}
delay={index * 0.1}
suffix={stat.suffix}
/>
<div className="text-xs text-gray-400 mt-1">{stat.label}</div>
</motion.div>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.6 }}
className="mb-6"
>
<div className="h-4 w-28 bg-white/10 rounded mb-3"></div>
<div className="h-20 bg-iron-gray/30 border border-charcoal-outline rounded-lg p-3 flex items-end gap-1">
{formData.map((value, i) => (
<motion.div
key={i}
className="flex-1 bg-gradient-to-t from-performance-green to-primary-blue rounded-sm"
initial={{ height: 0 }}
animate={{ height: `${value}%` }}
transition={{
delay: shouldReduceMotion ? 0 : 0.8 + i * 0.05,
duration: 0.4,
ease: 'easeOut'
}}
/>
))}
</div>
<div className="flex justify-between mt-1 text-xs text-gray-500">
<span>Last 10 races</span>
<span>Recent</span>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.8 }}
>
<div className="h-4 w-20 bg-white/10 rounded mb-3"></div>
<div className="space-y-2">
{[
{ team: 'Red Bull Racing', status: 'Current', color: 'primary-blue' },
{ team: 'Mercedes AMG', status: '2023', color: 'charcoal-outline' }
].map((team, i) => (
<motion.div
key={team.team}
initial={{ opacity: 0, x: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.9 + i * 0.1 }}
className="flex items-center justify-between bg-iron-gray/30 border border-charcoal-outline rounded-lg p-2 text-sm"
>
<div className="h-3 w-32 bg-white/10 rounded"></div>
<span className={`text-xs px-2 py-0.5 rounded ${
team.status === 'Current'
? 'bg-primary-blue/20 text-primary-blue'
: 'bg-charcoal-outline text-gray-400'
}`}>
{team.status}
</span>
</motion.div>
))}
</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: shouldReduceMotion ? 0 : 1.2 }}
className="mt-4 text-center text-xs text-gray-400"
>
Active in 3 leagues
</motion.div>
</motion.div>
</div>
);
}
function AnimatedRating({ shouldReduceMotion, value }: { shouldReduceMotion: boolean; value: number }) {
const count = useMotionValue(0);
const rounded = useTransform(count, (v) => Math.round(v));
const spring = useSpring(count, { stiffness: 50, damping: 25 });
useEffect(() => {
if (shouldReduceMotion) {
count.set(value);
} else {
const timeout = setTimeout(() => count.set(value), 200);
return () => clearTimeout(timeout);
}
}, [shouldReduceMotion, count, value]);
return (
<motion.span className="text-lg font-bold text-primary-blue font-mono">
{shouldReduceMotion ? value : <motion.span>{rounded}</motion.span>}
</motion.span>
);
}
function AnimatedCounter({
value,
shouldReduceMotion,
delay,
suffix = ''
}: {
value: number;
shouldReduceMotion: boolean;
delay: number;
suffix?: string;
}) {
const count = useMotionValue(0);
const rounded = useTransform(count, (v) => Math.round(v));
useEffect(() => {
if (shouldReduceMotion) {
count.set(value);
} else {
const timeout = setTimeout(() => count.set(value), delay * 1000 + 400);
return () => clearTimeout(timeout);
}
}, [shouldReduceMotion, count, value, delay]);
return (
<div className="text-xl font-bold text-white font-mono">
{shouldReduceMotion ? value : <motion.span>{rounded}</motion.span>}{suffix}
</div>
);
}