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

301 lines
12 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { motion, useReducedMotion, useMotionValue, useSpring, useTransform } from 'framer-motion';
import { useEffect, useState } from 'react';
export default function DriverProfileMockup() {
const shouldReduceMotion = useReducedMotion();
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
setIsMobile(window.innerWidth < 768);
}, []);
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];
if (isMobile) {
return (
<div className="relative w-full h-full bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite rounded-lg p-3 overflow-hidden">
<div className="space-y-4">
<div>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
<div className="relative h-12 w-12 rounded-full border-2 border-primary-blue/50 overflow-hidden bg-charcoal-outline">
<div className="absolute inset-0 flex items-center justify-center text-2xl">🏎</div>
</div>
<div>
<div className="text-base font-bold text-white">Driver Profile</div>
<div className="text-xs text-white/50">Cross-league</div>
</div>
</div>
<div className="text-2xl font-bold text-charcoal-outline">#33</div>
</div>
<div className="relative h-2 bg-charcoal-outline rounded-full overflow-hidden mb-1">
<div
className="absolute inset-y-0 left-0 bg-gradient-to-r from-primary-blue to-neon-aqua rounded-full"
style={{ width: '86%' }}
/>
</div>
<div className="flex justify-end">
<span className="text-xs text-gray-400">2150 GP Rating</span>
</div>
</div>
<div>
<div className="text-sm font-semibold text-white mb-2">Career Stats</div>
<div className="grid grid-cols-3 gap-2">
{stats.slice(0, 3).map((stat) => (
<div
key={stat.label}
className="bg-iron-gray/50 border border-charcoal-outline rounded-lg p-2 text-center"
>
<div className="text-base font-bold text-white font-mono">
{stat.value}{stat.suffix}
</div>
<div className="text-xs text-gray-400 mt-0.5">{stat.label}</div>
</div>
))}
</div>
</div>
<div>
<div className="text-sm font-semibold text-white mb-2">Recent Form</div>
<div className="h-16 bg-iron-gray/30 border border-charcoal-outline rounded-lg p-2 flex items-end gap-1">
{formData.slice(-6).map((value, i) => (
<div
key={i}
className="flex-1 bg-gradient-to-t from-performance-green to-primary-blue rounded-sm"
style={{ height: `${value}%` }}
/>
))}
</div>
</div>
</div>
</div>
);
}
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-1.5 sm:p-3 md:p-5 lg:p-8 overflow-hidden">
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, y: 0 }}
className="mb-1.5 sm:mb-3 md:mb-4 lg:mb-6"
>
<div className="flex items-center justify-between mb-1.5 sm:mb-2 md:mb-3 lg:mb-4">
<div className="flex items-center gap-1.5 sm:gap-2 md:gap-3 lg:gap-4">
<div className="relative h-8 w-8 sm:h-10 sm:w-10 md:h-12 md:w-12 lg:h-16 lg:w-16 rounded-full border-2 border-primary-blue/50 overflow-hidden bg-charcoal-outline">
<div className="absolute inset-0 flex items-center justify-center text-base sm:text-xl md:text-2xl lg:text-3xl">🏎</div>
</div>
<div>
<div className="text-sm sm:text-base md:text-lg lg:text-xl font-bold text-white mb-1 sm:mb-1.5 md:mb-2">Driver Profile</div>
<div className="text-[8px] sm:text-[10px] md:text-xs text-white/50">Cross-league racing identity</div>
</div>
</div>
<div className="text-xl sm:text-2xl md:text-3xl lg:text-4xl font-bold text-charcoal-outline">#33</div>
</div>
<div className="flex flex-wrap items-center gap-1 sm:gap-2 md:gap-3 lg:gap-4 mb-1 sm:mb-1.5 md:mb-2">
<div className="text-[8px] sm:text-[10px] md:text-xs text-gray-400">GridPilot Rating:</div>
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={2150} />
<div className="text-[8px] sm:text-[10px] md:text-xs text-gray-400">iRating:</div>
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={3200} />
</div>
<div className="relative h-1.5 sm:h-2 md:h-2.5 lg: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-[8px] sm:text-[10px] md:text-xs text-gray-400">86%</span>
</div>
</motion.div>
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="mb-1.5 sm:mb-3 md:mb-4 lg:mb-6"
>
<div className="text-[9px] sm:text-xs md:text-sm font-semibold text-white mb-1">Career Statistics</div>
<div className="text-[8px] sm:text-[10px] md:text-xs text-white/50 mb-1 sm:mb-2 md:mb-3">Aggregated across all leagues</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-1.5 sm:gap-2 md: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-1.5 sm:p-2 md:p-3 text-center"
>
<AnimatedCounter
value={stat.value}
shouldReduceMotion={shouldReduceMotion ?? false}
delay={index * 0.1}
suffix={stat.suffix}
/>
<div className="text-[8px] sm:text-[10px] md:text-xs text-gray-400 mt-0.5">{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-1.5 sm:mb-3 md:mb-4 lg:mb-6"
>
<div className="text-[9px] sm:text-xs md:text-sm font-semibold text-white mb-1">Recent Form</div>
<div className="text-[8px] sm:text-[10px] md:text-xs text-white/50 mb-1 sm:mb-2 md:mb-3">Performance trend over last 10 races</div>
<div className="h-12 sm:h-16 md:h-20 bg-iron-gray/30 border border-charcoal-outline rounded-lg p-1.5 sm:p-2 md:p-3 flex items-end gap-0.5">
{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-0.5 text-[8px] sm:text-[10px] md: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="text-[9px] sm:text-xs md:text-sm font-semibold text-white mb-1">Teams</div>
<div className="text-[8px] sm:text-[10px] md:text-xs text-white/50 mb-1 sm:mb-2 md:mb-3">Current and past team memberships</div>
<div className="space-y-1 sm:space-y-1.5 md: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-1 sm:p-1.5 md:p-2 text-[10px] sm:text-xs md:text-sm"
>
<div className="flex items-center gap-2 sm:gap-3">
<div className="h-5 w-5 sm:h-6 sm:w-6 md:h-8 md:w-8 rounded border border-primary-blue/30 bg-charcoal-outline flex items-center justify-center text-sm sm:text-base md:text-lg">
🏁
</div>
<div className="h-1.5 sm:h-2 md:h-3 w-16 sm:w-20 md:w-32 bg-white/10 rounded"></div>
</div>
<span className={`text-[8px] sm:text-[10px] md:text-xs px-1 sm:px-1.5 md: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-2 sm:mt-3 md:mt-4 text-center text-[8px] sm:text-[10px] md: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-sm sm:text-base md: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-sm sm:text-base md:text-lg lg:text-xl font-bold text-white font-mono">
{shouldReduceMotion ? value : <motion.span>{rounded}</motion.span>}{suffix}
</div>
);
}