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

198 lines
8.1 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 } from 'framer-motion';
import { useEffect, useState } from 'react';
export default function StandingsTableMockup() {
const shouldReduceMotion = useReducedMotion();
const [hoveredRow, setHoveredRow] = useState<number | null>(null);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
setIsMobile(window.innerWidth < 768);
}, []);
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="mb-3">
<div className="flex items-center gap-2 pb-2 border-b border-charcoal-outline">
<div className="text-xs font-mono text-gray-400">#</div>
<div className="text-xs flex-1 font-semibold text-white">Driver</div>
<div className="text-xs font-mono text-gray-400">Pts</div>
</div>
</div>
<div className="space-y-2">
{[1, 2, 3, 4, 5].map((i) => (
<div
key={i}
className={`flex items-center gap-2 py-2 px-2 rounded-lg border ${
i <= 3
? 'bg-gradient-to-r from-performance-green/10 to-iron-gray border-performance-green/20'
: 'bg-iron-gray border-charcoal-outline'
}`}
>
<div
className={`h-7 w-7 rounded-full flex items-center justify-center font-semibold text-xs ${
i <= 3
? 'bg-primary-blue text-white shadow-glow'
: 'bg-charcoal-outline text-gray-400'
}`}
>
{i}
</div>
<div className="flex-1 flex items-center gap-2">
<div className="h-5 w-5 rounded-full bg-charcoal-outline border border-primary-blue/20 flex items-center justify-center text-xs">
🏎
</div>
<div className="h-2.5 w-full max-w-[100px] bg-white/10 rounded"></div>
</div>
<div className="relative w-16 h-5 bg-charcoal-outline rounded border border-primary-blue/20 overflow-hidden">
<div
className={`absolute inset-y-0 left-0 ${
i <= 3
? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20'
: 'bg-gradient-to-r from-iron-gray to-charcoal-outline'
}`}
style={{ width: `${100 - (i - 1) * 15}%` }}
/>
<div className="relative h-full flex items-center justify-center">
<span className="text-xs font-mono font-semibold text-white">
{300 - i * 20}
</span>
</div>
</div>
</div>
))}
</div>
</div>
);
}
const getRowAnimation = (i: number) => ({
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
visible: {
opacity: 1,
y: 0,
transition: {
delay: shouldReduceMotion ? 0 : i * 0.05,
type: 'spring' as const,
stiffness: 300,
damping: 24
}
}
});
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-4 lg:p-6 overflow-hidden">
<div className="mb-1.5 sm:mb-2 md:mb-3 lg:mb-4">
<div className="text-[8px] sm:text-[10px] md:text-xs text-white/50 mb-1.5 sm:mb-2 md:mb-3">Real-time standings updated after every race</div>
<div className="flex items-center gap-1.5 sm:gap-2 md:gap-3 lg:gap-4 pb-1.5 sm:pb-2 md:pb-3 border-b border-charcoal-outline">
<div className="text-[8px] sm:text-[10px] md:text-xs font-mono text-gray-400">#</div>
<div className="text-[8px] sm:text-[10px] md:text-xs flex-1 font-semibold text-white">Driver</div>
<div className="text-[8px] sm:text-[10px] md:text-xs font-mono text-gray-400 hidden md:block">Wins</div>
<div className="text-[8px] sm:text-[10px] md:text-xs font-mono text-gray-400 flex items-center gap-1">
<span>Points</span>
<span className="text-performance-green text-[8px]"></span>
</div>
</div>
</div>
<div className="space-y-0.5">
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
<motion.div
key={i}
variants={getRowAnimation(i)}
initial="hidden"
animate="visible"
className={`relative flex items-center gap-1.5 sm:gap-2 md:gap-3 lg:gap-4 py-1.5 sm:py-2 md:py-2.5 lg:py-3 px-1.5 sm:px-2 md:px-3 rounded-lg border transition-all duration-150 ${
i <= 3
? 'bg-gradient-to-r from-performance-green/10 to-iron-gray border-performance-green/20'
: 'bg-iron-gray border-charcoal-outline'
}`}
onHoverStart={() => !shouldReduceMotion && setHoveredRow(i)}
onHoverEnd={() => setHoveredRow(null)}
whileHover={shouldReduceMotion ? {} : {
scale: 1.01,
boxShadow: '0 0 20px rgba(25,140,255,0.3)',
transition: { duration: 0.15 }
}}
>
<motion.div
className={`h-6 w-6 sm:h-7 sm:w-7 rounded-full flex items-center justify-center font-semibold text-[10px] sm:text-xs ${
i <= 3
? 'bg-primary-blue text-white shadow-glow'
: 'bg-charcoal-outline text-gray-400'
}`}
animate={
shouldReduceMotion ? {} : i <= 3 && hoveredRow === i
? { scale: 1.15, boxShadow: '0 0 28px rgba(25,140,255,0.5)' }
: {}
}
>
{i}
</motion.div>
<div className="flex-1 flex items-center gap-1 sm:gap-1.5 md:gap-2">
<div className="h-4 w-4 sm:h-5 sm:w-5 md:h-6 md:w-6 rounded-full bg-charcoal-outline border border-primary-blue/20 flex items-center justify-center text-[8px] sm:text-[10px] md:text-xs aspect-square">
🏎
</div>
<div className="h-1.5 sm:h-2 md:h-2.5 lg:h-3 w-full max-w-[80px] sm:max-w-[100px] md:max-w-[140px] bg-white/10 rounded"></div>
</div>
<div className="h-1.5 sm:h-2 md:h-2.5 lg:h-3 w-10 sm:w-12 md:w-16 bg-white/5 rounded font-mono hidden md:block"></div>
<div className="relative">
<AnimatedPoints
points={300 - i * 20}
position={i}
shouldReduceMotion={shouldReduceMotion ?? false}
/>
</div>
</motion.div>
))}
</div>
</div>
);
}
function AnimatedPoints({
points,
position,
shouldReduceMotion
}: {
points: number;
position: number;
shouldReduceMotion: boolean;
}) {
const motionValue = useMotionValue(0);
const spring = useSpring(motionValue, { stiffness: 50, damping: 20 });
useEffect(() => {
if (shouldReduceMotion) {
motionValue.set(points);
} else {
setTimeout(() => motionValue.set(points), 100 + position * 50);
}
}, [points, position, shouldReduceMotion, motionValue]);
const percentage = (points / 300) * 100;
return (
<div className="relative w-12 sm:w-16 md:w-20 lg:w-24 h-4 sm:h-5 md:h-6 lg:h-7 bg-charcoal-outline rounded border border-primary-blue/20 overflow-hidden">
<motion.div
className={`absolute inset-y-0 left-0 ${
position <= 3
? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20'
: 'bg-gradient-to-r from-iron-gray to-charcoal-outline'
}`}
initial={{ width: '0%' }}
animate={{ width: `${percentage}%` }}
transition={{ duration: shouldReduceMotion ? 0 : 0.8, ease: 'easeOut', delay: 0.1 + position * 0.05 }}
/>
<div className="relative h-full flex items-center justify-center">
<motion.span className="text-[8px] sm:text-[10px] md:text-xs font-mono font-semibold text-white">
{shouldReduceMotion ? points : <motion.span>{spring}</motion.span>}
</motion.span>
</div>
</div>
);
}