website refactor

This commit is contained in:
2026-01-17 02:32:34 +01:00
parent 6a49448e0a
commit 4d5ce9bfd6
43 changed files with 1642 additions and 2022 deletions

View File

@@ -1,10 +1,9 @@
import { useParallax } from "@/hooks/useScrollProgress";
import { Box } from '@/ui/Box';
import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { useRef } from 'react';
interface AlternatingSectionProps {
@@ -25,8 +24,7 @@ export function AlternatingSection({
backgroundVideo
}: AlternatingSectionProps) {
const sectionRef = useRef<HTMLElement>(null);
const bgParallax = useParallax(sectionRef, 0.2);
const bgParallax = useParallax(sectionRef, 0.1);
return (
<Box
@@ -34,93 +32,97 @@ export function AlternatingSection({
ref={sectionRef}
position="relative"
overflow="hidden"
bg="bg-deep-graphite"
px={{ base: 'calc(1rem+var(--sal))', lg: 8 }}
py={{ base: 20, sm: 24, md: 32 }}
bg="graphite-black"
py={{ base: 20, md: 32 }}
className="border-b border-border-gray"
>
{backgroundVideo && (
<>
<Box
position="absolute"
inset="0"
fullWidth
fullHeight
overflow="hidden"
>
<Box
as="video"
autoPlay
loop
muted
playsInline
fullWidth
fullHeight
objectFit="cover"
opacity={0.1}
>
<Box as="source" src={backgroundVideo} type="video/mp4" />
</Box>
{/* Dark overlay to ensure readability */}
<Box position="absolute" inset="0" bg="linear-gradient(to bottom, #0C0D0F, transparent, #0C0D0F)" />
</Box>
)}
{backgroundImage && !backgroundVideo && (
<Box
position="absolute"
inset="0"
fullWidth
fullHeight
objectFit="cover"
opacity={0.2}
maskImage="radial-gradient(ellipse at center, black 0%, rgba(0,0,0,0.8) 40%, transparent 70%)"
webkitMaskImage="radial-gradient(ellipse at center, black 0%, rgba(0,0,0,0.8) 40%, transparent 70%)"
overflow="hidden"
>
<Box as="source" src={backgroundVideo} type="video/mp4" />
</Box>
{/* Racing red accent for sections with background videos */}
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, rgba(239, 68, 68, 0.3), transparent)" />
</>
)}
{backgroundImage && !backgroundVideo && (
<>
<Box
position="absolute"
inset="0"
bg={`url(${backgroundImage})`}
backgroundSize="cover"
backgroundPosition="center"
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
transform={`translateY(${bgParallax * 0.3}px)`}
opacity={0.1}
style={{ transform: `translateY(${bgParallax * 0.3}px)` }}
/>
{/* Racing red accent for sections with background images */}
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, rgba(239, 68, 68, 0.3), transparent)" />
</>
{/* Dark overlay to ensure readability */}
<Box position="absolute" inset="0" bg="linear-gradient(to bottom, #0C0D0F, transparent, #0C0D0F)" />
</Box>
)}
{/* Carbon fiber texture on sections without images or videos */}
{!backgroundImage && !backgroundVideo && (
<Box position="absolute" inset="0" opacity={0.3} bg="carbon-fiber" />
)}
{/* Checkered pattern accent */}
<Box position="absolute" inset="0" opacity={0.1} bg="checkered-pattern" />
<Container size="lg" position="relative" zIndex={10}>
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={{ base: 8, md: 12, lg: 16 }} alignItems="center">
{/* Text Content - Always first on mobile, respects layout on desktop */}
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={{ base: 12, lg: 24 }} alignItems="center">
{/* Text Content */}
<Box
display="flex"
flexDirection="column"
gap={{ base: 4, md: 6, lg: 8 }}
gap={8}
order={{ lg: layout === 'text-right' ? 2 : 1 }}
>
<Heading level={2} fontSize={{ base: 'xl', md: '2xl', lg: '3xl', xl: '4xl' }} weight="medium" style={{ background: 'linear-gradient(to right, #dc2626, #ffffff, #2563eb)', backgroundClip: 'text', WebkitBackgroundClip: 'text', color: 'transparent', filter: 'drop-shadow(0 0 15px rgba(220,0,0,0.4))', WebkitTextStroke: '0.5px rgba(220,0,0,0.2)' }}>
<Stack gap={4}>
<Box w="12" h="1" bg="primary-accent" />
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" className="tracking-tight">
{heading}
</Heading>
<Box display="flex" flexDirection="column" gap={{ base: 3, md: 5 }}>
<Text size={{ base: 'sm', md: 'base', lg: 'lg' }} color="text-slate-400" weight="light" leading="relaxed">
{description}
</Text>
</Stack>
<Box className="text-gray-400">
{typeof description === 'string' ? (
<Text size="lg" leading="relaxed" weight="normal">{description}</Text>
) : (
description
)}
</Box>
</Box>
{/* Mockup - Always second on mobile, respects layout on desktop */}
{/* Mockup */}
<Box
position="relative"
order={{ lg: layout === 'text-right' ? 1 : 2 }}
group
className="bg-panel-gray/30 border border-border-gray/50 rounded-none p-2 shadow-2xl group"
>
<Box
fullWidth
minHeight={{ base: '240px', md: '380px', lg: '440px' }}
transition
hoverScale
maskImage={`linear-gradient(to ${layout === 'text-left' ? 'right' : 'left'}, white 50%, rgba(255,255,255,0.8) 70%, rgba(255,255,255,0.4) 85%, transparent 100%)`}
webkitMaskImage={`linear-gradient(to ${layout === 'text-left' ? 'right' : 'left'}, white 50%, rgba(255,255,255,0.8) 70%, rgba(255,255,255,0.4) 85%, transparent 100%)`}
minHeight={{ base: '240px', md: '380px' }}
className="overflow-hidden rounded-none border border-border-gray/30 bg-graphite-black"
>
{mockup}
</Box>
{/* Decorative corner accents */}
<Box position="absolute" top="-1px" left="-1px" w="4" h="4" borderTop borderLeft borderColor="primary-accent/50" />
<Box position="absolute" bottom="-1px" right="-1px" w="4" h="4" borderBottom borderRight borderColor="primary-accent/50" />
</Box>
</Box>
</Container>

View File

@@ -0,0 +1,123 @@
'use client';
import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Container } from '@/ui/Container';
import { Grid } from '@/ui/Grid';
import { Heading } from '@/ui/Heading';
import { Link } from '@/ui/Link';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { LeagueCard } from '@/ui/LeagueCard';
import { TeamCard } from '@/ui/TeamCard';
import { UpcomingRaceItem } from '@/ui/UpcomingRaceItem';
import { HomeViewData } from '@/templates/HomeTemplate';
interface DiscoverySectionProps {
viewData: HomeViewData;
}
export function DiscoverySection({ viewData }: DiscoverySectionProps) {
return (
<Box py={{ base: 20, md: 32 }} bg="graphite-black" borderBottom borderColor="border-gray/50">
<Container size="lg">
<Stack gap={16}>
<Box maxWidth="2xl">
<Box borderLeft borderStyle="solid" borderColor="primary-accent" pl={4} mb={4}>
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-[0.2em]">
Live Ecosystem
</Text>
</Box>
<Heading level={2} weight="bold" fontSize={{ base: '3xl', md: '4xl' }} className="tracking-tight">
Discover the Grid
</Heading>
<Text size="lg" color="text-gray-400" block mt={6} leading="relaxed">
Explore leagues, teams, and races that make up the GridPilot ecosystem.
</Text>
</Box>
<Grid cols={1} lgCols={3} gap={12}>
{/* Top leagues */}
<Stack gap={8}>
<Stack direction="row" align="center" justify="between" className="border-b border-border-gray/30 pb-4">
<Heading level={5} color="text-gray-400" weight="bold" className="tracking-widest">FEATURED LEAGUES</Heading>
<Link href={routes.public.leagues}>
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-widest hover:text-white transition-colors">View all</Text>
</Link>
</Stack>
<Stack gap={4}>
{viewData.topLeagues.slice(0, 2).map((league) => (
<LeagueCard
key={league.id}
name={league.name}
description={league.description}
coverUrl="/images/ff1600.jpeg"
slotLabel="Drivers"
usedSlots={18}
maxSlots={24}
fillPercentage={75}
hasOpenSlots={true}
openSlotsCount={6}
/>
))}
</Stack>
</Stack>
{/* Teams */}
<Stack gap={8}>
<Stack direction="row" align="center" justify="between" className="border-b border-border-gray/30 pb-4">
<Heading level={5} color="text-gray-400" weight="bold" className="tracking-widest">TEAMS ON THE GRID</Heading>
<Link href={routes.public.teams}>
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-widest hover:text-white transition-colors">Browse</Text>
</Link>
</Stack>
<Stack gap={4}>
{viewData.teams.slice(0, 2).map(team => (
<TeamCard
key={team.id}
name={team.name}
description={team.description}
logo={team.logoUrl}
memberCount={12}
isRecruiting={true}
/>
))}
</Stack>
</Stack>
{/* Upcoming races */}
<Stack gap={8}>
<Stack direction="row" align="center" justify="between" className="border-b border-border-gray/30 pb-4">
<Heading level={5} color="text-gray-400" weight="bold" className="tracking-widest">UPCOMING RACES</Heading>
<Link href={routes.public.races}>
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-widest hover:text-white transition-colors">Schedule</Text>
</Link>
</Stack>
{viewData.upcomingRaces.length === 0 ? (
<Box p={12} border borderStyle="dashed" borderColor="border-gray/30" rounded="none" center bg="panel-gray/10">
<Text size="sm" color="text-gray-600" font="mono" uppercase letterSpacing="widest">
No races scheduled.
</Text>
</Box>
) : (
<Stack gap={3}>
{viewData.upcomingRaces.map(race => (
<UpcomingRaceItem
key={race.id}
track={race.track}
car={race.car}
formattedDate={race.formattedDate}
formattedTime="20:00 GMT"
isMyLeague={false}
/>
))}
</Stack>
)}
</Stack>
</Grid>
</Stack>
</Container>
</Box>
);
}

View File

@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack';
import { Icon } from '@/ui/Icon';
import { ChevronDown } from 'lucide-react';
@@ -47,27 +48,31 @@ function FAQItem({ faq, index }: { faq: typeof faqs[0]; index: number }) {
transition={{ delay: index * 0.1 }}
group
>
<Box rounded="lg" bg="bg-iron-gray" border borderColor="border-charcoal-outline" transition hoverBorderColor="border-primary-blue/50" transform={isOpen ? '' : 'translateY(0)'} hoverScale={!isOpen}>
<Box rounded="none" bg="panel-gray/40" border borderColor="border-gray/50" transition hoverBorderColor="primary-accent/30">
<Box
as="button"
onClick={() => setIsOpen(!isOpen)}
fullWidth
p={{ base: 2, md: 3, lg: 4 }}
p={{ base: 4, md: 6 }}
textAlign="left"
rounded="lg"
rounded="none"
minHeight="44px"
className="relative overflow-hidden"
>
<Box display="flex" alignItems="center" justifyContent="between" gap={{ base: 1.5, md: 2 }}>
<Heading level={3} fontSize={{ base: 'xs', md: 'sm' }} weight="semibold" color="text-white" groupHoverColor="primary-blue" transition>
<Box display="flex" alignItems="center" justifyContent="between" gap={{ base: 2, md: 4 }}>
<Stack direction="row" align="center" gap={4}>
<Box w="1" h="4" bg={isOpen ? "primary-accent" : "border-gray"} transition className="group-hover:bg-primary-accent" />
<Heading level={3} fontSize={{ base: 'sm', md: 'base' }} weight="bold" color="text-white" groupHoverColor="primary-accent" transition className="tracking-wide">
{faq.question}
</Heading>
</Stack>
<Box
as={motion.div}
animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.15, ease: 'easeInOut' }}
w={{ base: "3.5", md: "4" }}
h={{ base: "3.5", md: "4" }}
color="text-neon-aqua"
w={{ base: "4", md: "5" }}
h={{ base: "4", md: "5" }}
color={isOpen ? "text-primary-accent" : "text-gray-500"}
flexShrink={0}
>
<Icon icon={ChevronDown} size="full" />
@@ -82,13 +87,13 @@ function FAQItem({ faq, index }: { faq: typeof faqs[0]; index: number }) {
opacity: isOpen ? 1 : 0
}}
transition={{
height: { duration: 0.3, ease: [0.34, 1.56, 0.64, 1] },
opacity: { duration: 0.2, ease: 'easeInOut' }
height: { duration: 0.2, ease: 'easeInOut' },
opacity: { duration: 0.15, ease: 'easeInOut' }
}}
overflow="hidden"
>
<Box px={{ base: 2, md: 3 }} pb={{ base: 2, md: 3 }} pt={1}>
<Text size={{ base: 'xs', md: 'xs' }} color="text-gray-300" weight="light" leading="relaxed">
<Box px={{ base: 4, md: 6 }} pb={{ base: 4, md: 6 }} pt={0} ml={5}>
<Text size="sm" color="text-gray-400" weight="normal" leading="relaxed" className="max-w-2xl">
{faq.answer}
</Text>
</Box>
@@ -100,7 +105,7 @@ function FAQItem({ faq, index }: { faq: typeof faqs[0]; index: number }) {
export function FAQ() {
return (
<Box as="section" position="relative" py={{ base: 3, md: 12, lg: 16 }} bg="bg-deep-graphite" overflow="hidden">
<Box as="section" position="relative" py={{ base: 20, md: 32 }} bg="graphite-black" overflow="hidden" borderBottom borderColor="border-gray/50">
{/* Background image with mask */}
<Box
position="absolute"
@@ -108,20 +113,21 @@ export function FAQ() {
bg="url(/images/porsche.jpeg)"
backgroundSize="cover"
backgroundPosition="center"
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
opacity={0.03}
/>
{/* Racing red accent */}
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, rgba(239, 68, 68, 0.3), transparent)" />
<Box maxWidth="3xl" mx="auto" px={{ base: 4, md: 6 }} position="relative" zIndex={10}>
<Box textAlign="center" mb={{ base: 4, md: 8 }}>
<Heading level={2} fontSize={{ base: 'base', md: 'xl', lg: '2xl' }} weight="semibold" color="text-white" mb={1}>
<Box maxWidth="4xl" mx="auto" px={{ base: 4, md: 6 }} position="relative" zIndex={10}>
<Box textAlign="center" mb={{ base: 12, md: 16 }}>
<Box borderLeft borderRight borderStyle="solid" borderColor="primary-accent" px={4} display="inline-block" mb={4}>
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-[0.2em]">
Support & Information
</Text>
</Box>
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" color="text-white" mb={4} className="tracking-tight">
Frequently Asked Questions
</Heading>
<Box mx="auto" rounded="full" w={{ base: "24", md: "32" }} h="1" bg="linear-gradient(to right, var(--primary-blue), var(--neon-aqua))" />
</Box>
<Box display="flex" flexDirection="column" gap={{ base: 1.5, md: 2 }}>
<Box display="flex" flexDirection="column" gap={4}>
{faqs.map((faq, index) => (
<FAQItem key={faq.question} faq={faq} index={index} />
))}

View File

@@ -53,49 +53,63 @@ function FeatureCard({ feature, index }: { feature: typeof features[0], index: n
display="flex"
flexDirection="column"
gap={6}
group
className="p-8 bg-panel-gray/40 border border-border-gray/50 rounded-none hover:border-primary-accent/30 transition-all duration-300 ease-smooth group relative overflow-hidden"
>
<Box aspectRatio="video" fullWidth position="relative">
<Box position="absolute" inset="-0.5" bg="linear-gradient(to right, rgba(239, 68, 68, 0.2), rgba(59, 130, 246, 0.2), rgba(239, 68, 68, 0.2))" rounded="lg" opacity={0} groupHoverOpacity={1} transition blur="sm" />
<Box position="relative">
<Box aspectRatio="video" fullWidth position="relative" className="bg-graphite-black rounded-none overflow-hidden border border-border-gray/30">
<MockupStack index={index}>
<feature.MockupComponent />
</MockupStack>
</Box>
</Box>
<Stack gap={3}>
<Box display="flex" alignItems="center" gap={2}>
<Heading level={3} weight="medium" style={{ background: 'linear-gradient(to right, #dc2626, #ffffff, #2563eb)', backgroundClip: 'text', WebkitBackgroundClip: 'text', color: 'transparent', filter: 'drop-shadow(0 0 15px rgba(220,0,0,0.4))', WebkitTextStroke: '0.5px rgba(220,0,0,0.2)' }}>
<Stack gap={4}>
<Box display="flex" alignItems="center" gap={3}>
<Box w="1" h="4" bg="primary-accent" />
<Heading level={3} weight="bold" fontSize="xl" className="tracking-tight">
{feature.title}
</Heading>
</Box>
<Text size={{ base: 'sm', sm: 'base' }} color="text-gray-400" weight="light" leading="relaxed">
<Text size="sm" color="text-gray-400" leading="relaxed" weight="normal">
{feature.description}
</Text>
</Stack>
{/* Subtle hover effect */}
<Box
position="absolute"
bottom="0"
left="0"
w="full"
h="1"
bg="primary-accent"
className="scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left"
/>
</Box>
);
}
export function FeatureGrid() {
return (
<Section variant="default">
<Section className="bg-graphite-black border-b border-border-gray/50 py-24">
<Container position="relative" zIndex={10}>
<Container size="sm" center>
<Box>
<Heading level={2} weight="semibold" style={{ background: 'linear-gradient(to right, #dc2626, #ffffff, #2563eb)', backgroundClip: 'text', WebkitBackgroundClip: 'text', color: 'transparent', filter: 'drop-shadow(0 0 20px rgba(220,0,0,0.5))', WebkitTextStroke: '1px rgba(220,0,0,0.2)' }}>
Building for League Racing
</Heading>
<Text size={{ base: 'base', sm: 'lg' }} color="text-gray-400" block mt={{ base: 4, sm: 6 }}>
These features are in development. Join the community to help shape what gets built first
<Stack gap={16}>
<Box maxWidth="2xl">
<Box borderLeft borderStyle="solid" borderColor="primary-accent" pl={4} mb={4}>
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-[0.2em]">
Engineered for Competition
</Text>
</Box>
</Container>
<Box mx="auto" mt={{ base: 8, sm: 12, md: 16 }} display="grid" gridCols={{ base: 1, lg: 2, xl: 3 }} gap={{ base: 10, sm: 12, md: 16 }} maxWidth={{ base: '2xl', lg: 'none' }}>
<Heading level={2} weight="bold" fontSize={{ base: '3xl', md: '4xl' }} className="tracking-tight">
Building for League Racing
</Heading>
<Text size="lg" color="text-gray-400" block mt={6} leading="relaxed">
Every feature is designed to reduce friction and increase immersion. Join our Discord to help shape the future of the platform.
</Text>
</Box>
<Box display="grid" gridCols={{ base: 1, md: 2, lg: 3 }} gap={8}>
{features.map((feature, index) => (
<FeatureCard key={feature.title} feature={feature} index={index} />
))}
</Box>
</Stack>
</Container>
</Section>
);

View File

@@ -1,4 +1,3 @@
import { useRef } from 'react';
import { useParallax } from '@/hooks/useScrollProgress';
import { Box } from '@/ui/Box';
@@ -7,17 +6,13 @@ import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Glow } from '@/ui/Glow';
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#';
if (!process.env.NEXT_PUBLIC_DISCORD_URL) {
console.warn('NEXT_PUBLIC_DISCORD_URL is not set. Discord button will use "#" as fallback.');
}
export function LandingHero() {
const sectionRef = useRef<HTMLElement>(null);
const bgParallax = useParallax(sectionRef, 0.3);
const bgParallax = useParallax(sectionRef, 0.2);
return (
<Box
@@ -25,11 +20,10 @@ export function LandingHero() {
ref={sectionRef}
position="relative"
overflow="hidden"
bg="bg-deep-graphite"
px={{ base: 'calc(1.5rem+var(--sal))', lg: 8 }}
pt={{ base: 'calc(3rem+var(--sat))', sm: 'calc(4rem+var(--sat))' }}
pb={{ base: 16, sm: 24 }}
py={{ md: 32 }}
bg="graphite-black"
pt={{ base: 24, md: 32, lg: 40 }}
pb={{ base: 16, md: 24, lg: 32 }}
className="border-b border-border-gray"
>
{/* Background image layer with parallax */}
<Box
@@ -38,131 +32,93 @@ export function LandingHero() {
bg="url(/images/header.jpeg)"
backgroundSize="cover"
backgroundPosition="center"
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.35) 40%, transparent 70%)"
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.35) 40%, transparent 70%)"
transform={`translateY(${bgParallax * 0.5}px)`}
opacity={0.2}
style={{ transform: `translateY(${bgParallax * 0.5}px)` }}
/>
{/* Racing red accent gradient */}
<Box position="absolute" top="0" left="0" right="0" h="1" bg="linear-gradient(to right, transparent, rgba(220, 38, 38, 0.4), transparent)" />
{/* Robust gradient overlay */}
<Box
position="absolute"
inset="0"
bg="linear-gradient(to bottom, #0C0D0F 0%, transparent 50%, #0C0D0F 100%)"
/>
{/* Racing stripes background */}
<Box position="absolute" inset="0" opacity={0.3} bg="racing-stripes" />
<Box
position="absolute"
inset="0"
bg="radial-gradient(circle at center, transparent 0%, #0C0D0F 100%)"
opacity={0.6}
/>
{/* Checkered pattern overlay */}
<Box position="absolute" inset="0" opacity={0.2} bg="checkered-pattern" />
<Glow color="primary" size="xl" position="center" opacity={0.1} />
{/* Speed lines - left side */}
<Box position="absolute" left="0" top="1/4" w="32" h="px" bg="linear-gradient(to right, transparent, rgba(59, 130, 246, 0.3))" className="animate-speed-lines" style={{ animationDelay: '0s' }} />
<Box position="absolute" left="0" top="1/3" w="24" h="px" bg="linear-gradient(to right, transparent, rgba(59, 130, 246, 0.2))" className="animate-speed-lines" style={{ animationDelay: '0.3s' }} />
<Box position="absolute" left="0" top="2/5" w="28" h="px" bg="linear-gradient(to right, transparent, rgba(59, 130, 246, 0.25))" className="animate-speed-lines" style={{ animationDelay: '0.6s' }} />
{/* Carbon fiber accent - bottom */}
<Box position="absolute" bottom="0" left="0" right="0" h="32" opacity={0.5} bg="carbon-fiber" />
{/* Radial gradient overlay with racing red accent */}
<Box position="absolute" inset="0" bg="radial-gradient(circle at center, rgba(220, 38, 38, 0.05), rgba(59, 130, 246, 0.05), transparent)" opacity={0.6} pointerEvents="none" />
<Container size="sm" position="relative" zIndex={10}>
<Stack gap={{ base: 6, sm: 8, md: 12 }}>
<Container size="lg" position="relative" zIndex={10}>
<Stack gap={{ base: 8, md: 12 }}>
<Stack gap={6} maxWidth="3xl">
<Box borderLeft borderStyle="solid" borderColor="primary-accent" pl={4} mb={2}>
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-[0.2em]">
Precision Racing Infrastructure
</Text>
</Box>
<Heading
level={1}
fontSize={{ base: '2xl', sm: '4xl', md: '5xl', lg: '6xl' }}
weight="semibold"
style={{
background: 'linear-gradient(to right, #dc2626, #ffffff, #2563eb)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
color: 'transparent',
filter: 'drop-shadow(0 0 15px rgba(220,0,0,0.4))',
WebkitTextStroke: '0.5px rgba(220,0,0,0.2)'
}}
fontSize={{ base: '3xl', sm: '4xl', md: '5xl', lg: '7xl' }}
weight="bold"
className="text-white leading-[1.1] tracking-tight"
>
League racing is incredible. What's missing is everything around it.
Modern Motorsport <br />
<span className="text-primary-accent">Infrastructure.</span>
</Heading>
<Box
display="flex"
flexDirection="column"
gap={{ base: 4, sm: 6 }}
>
<Text size={{ base: 'sm', md: 'lg' }} align={{ base: 'left', md: 'center' }} color="text-slate-200" weight="light">
If you've been in any league, you know the feeling:
<Text size={{ base: 'lg', md: 'xl' }} color="text-gray-400" weight="normal" leading="relaxed" className="max-w-2xl">
GridPilot gives your league racing a real home. Results, standings, teams, and career progression engineered for precision and control.
</Text>
{/* Problem badges - mobile optimized */}
<Box display="flex" flexDirection={{ base: 'col', sm: 'row' }} flexWrap="wrap" gap={{ base: 2, sm: 3 }} alignItems={{ base: 'stretch', sm: 'center' }} justifyContent="center" maxWidth="2xl" mx="auto">
{[
'Results scattered across Discord',
'No long-term identity',
'No career progression',
'Forgotten after each season'
].map((text) => (
<Box
key={text}
display="flex"
alignItems="center"
gap={2.5}
px={{ base: 3, sm: 5 }}
py={2.5}
bg="linear-gradient(to bottom right, rgba(30, 41, 59, 0.8), rgba(15, 23, 42, 0.8))"
border
borderColor="border-red-500/20"
rounded="lg"
transition
hoverBorderColor="border-red-500/40"
hoverScale
>
<Text color="text-red-500" weight="semibold">×</Text>
<Text size={{ base: 'sm', sm: 'base' }} weight="medium" color="text-slate-100">{text}</Text>
</Box>
))}
</Box>
<Text size={{ base: 'sm', md: 'lg' }} align={{ base: 'left', md: 'center' }} color="text-slate-200" weight="light">
The ecosystem isn't built for this.
</Text>
<Text size={{ base: 'sm', md: 'lg' }} align={{ base: 'left', md: 'center' }} color="text-white" weight="semibold">
GridPilot gives your league racing a real home.
</Text>
</Box>
<Box display="flex" alignItems="center" justifyContent="center">
</Stack>
<Box display="flex" flexDirection={{ base: 'column', sm: 'row' }} gap={4}>
<Button
as="a"
href={discordUrl}
variant="primary"
px={8}
py={4}
size="lg"
bg="bg-[#5865F2]"
hoverBg="bg-[#4752C4]"
shadow="0 0 20px rgba(88,101,242,0.3)"
hoverScale
transition
aria-label="Join us on Discord"
className="px-10 rounded-none uppercase tracking-widest text-xs font-bold"
>
{/* Discord Logo SVG */}
<Box
as="svg"
w="7"
h="7"
viewBox="0 0 71 55"
fill="none"
xmlns="http://www.w3.org/2000/svg"
transition
groupHoverScale
>
<g clipPath="url(#clip0)">
<path
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0">
<rect width="71" height="55" fill="white"/>
</clipPath>
</defs>
</Box>
<Text>Join us on Discord</Text>
Join the Grid
</Button>
<Button
variant="secondary"
size="lg"
className="px-10 rounded-none border-border-gray hover:border-primary-accent/50 uppercase tracking-widest text-xs font-bold"
>
Explore Leagues
</Button>
</Box>
{/* Problem list - more professional */}
<Box
display="grid"
gridCols={{ base: 1, sm: 2, lg: 4 }}
gap={8}
mt={12}
className="border-t border-border-gray/30 pt-12"
>
{[
{ label: 'IDENTITY', text: 'Your racing career in one place', color: 'primary' },
{ label: 'AUTOMATION', text: 'No more manual session setup', color: 'aqua' },
{ label: 'PRECISION', text: 'Real-time results and standings', color: 'amber' },
{ label: 'COMMUNITY', text: 'Built for teams and leagues', color: 'green' }
].map((item) => (
<Stack key={item.label} gap={3} className="group">
<Box display="flex" alignItems="center" gap={2}>
<Box w="2" h="2" rounded="full" className={`bg-${item.color === 'primary' ? 'primary-accent' : item.color === 'aqua' ? 'telemetry-aqua' : item.color === 'amber' ? 'warning-amber' : 'success-green'}`} />
<Text size="xs" weight="bold" color="text-gray-500" className="uppercase tracking-[0.2em] group-hover:text-white transition-colors">
{item.label}
</Text>
</Box>
<Text size="sm" color="text-gray-400" className="group-hover:text-gray-200 transition-colors">
{item.text}
</Text>
</Stack>
))}
</Box>
</Stack>
</Container>

View File

@@ -1,53 +0,0 @@
'use client';
import React from 'react';
import { Check } from 'lucide-react';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Surface } from '@/ui/Surface';
import { Icon } from '@/ui/Icon';
export function FeatureItem({ text }: { text: string }) {
return (
<Surface variant="muted" rounded="lg" border padding={4} bg="rgba(15, 23, 42, 0.6)" borderColor="rgba(51, 65, 85, 0.4)">
<Stack direction="row" align="start" gap={3}>
<Surface variant="muted" rounded="lg" padding={2} bg="rgba(59, 130, 246, 0.1)" border borderColor="rgba(59, 130, 246, 0.3)">
<Icon icon={Check} size={5} color="#3b82f6" />
</Surface>
<Text color="text-slate-200" leading="relaxed" weight="light">
{text}
</Text>
</Stack>
</Surface>
);
}
export function ResultItem({ text, color }: { text: string, color: string }) {
return (
<Surface variant="muted" rounded="lg" border padding={4} bg="rgba(15, 23, 42, 0.6)" borderColor="rgba(51, 65, 85, 0.4)">
<Stack direction="row" align="start" gap={3}>
<Surface variant="muted" rounded="lg" padding={2} bg={`${color}1A`} border borderColor={`${color}4D`}>
<Icon icon={Check} size={5} color={color} />
</Surface>
<Text color="text-slate-200" leading="relaxed" weight="light">
{text}
</Text>
</Stack>
</Surface>
);
}
export function StepItem({ step, text }: { step: number, text: string }) {
return (
<Surface variant="muted" rounded="lg" border padding={4} bg="rgba(15, 23, 42, 0.7)" borderColor="rgba(51, 65, 85, 0.5)">
<Stack direction="row" align="start" gap={3}>
<Surface variant="muted" rounded="lg" padding={2} bg="rgba(59, 130, 246, 0.1)" border borderColor="rgba(59, 130, 246, 0.4)" w="10" h="10" display="flex" center>
<Text weight="bold" size="sm" color="text-primary-blue">{step}</Text>
</Surface>
<Text color="text-slate-200" leading="relaxed" weight="light">
{text}
</Text>
</Stack>
</Surface>
);
}

View File

@@ -5,22 +5,35 @@ import { Text } from '@/ui/Text';
export function HeaderContent() {
return (
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<Link href="/" className="inline-flex items-center">
<div className="flex items-center justify-between h-16 md:h-20">
<div className="flex items-center space-x-6">
<Link href="/" className="inline-flex items-center group">
<div className="relative">
<Image
src="/images/logos/wordmark-rectangle-dark.svg"
alt="GridPilot"
width={160}
height={30}
className="h-6 w-auto md:h-8"
className="h-6 w-auto md:h-7 transition-opacity group-hover:opacity-80"
priority
/>
<div className="absolute -bottom-1 left-0 w-0 h-0.5 bg-primary-accent transition-all group-hover:w-full" />
</div>
</Link>
<Text size="sm" color="text-gray-400" className="hidden sm:block font-light">
Making league racing less chaotic
<div className="hidden sm:flex items-center space-x-2 border-l border-border-gray/50 pl-6">
<div className="w-1.5 h-1.5 rounded-full bg-primary-accent animate-pulse" />
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-[0.2em] font-mono">
Motorsport Infrastructure
</Text>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="hidden md:flex items-center space-x-1 px-3 py-1 border border-border-gray/30 bg-panel-gray/20">
<Text size="xs" color="text-gray-600" weight="bold" font="mono" className="uppercase">Status:</Text>
<Text size="xs" color="text-success-green" weight="bold" font="mono" className="uppercase">Operational</Text>
</div>
</div>
</div>
);
}

View File

@@ -25,7 +25,7 @@ export function CareerProgressionMockup() {
// Simple mobile version - just the essence
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Box
as={motion.div}
initial="hidden"
@@ -43,8 +43,8 @@ export function CareerProgressionMockup() {
{ value: '48', label: 'Podiums' },
{ value: '156', label: 'Races' }
].map((stat, i) => (
<Box key={i} bg="bg-iron-gray/60" rounded="lg" p={3} border borderColor="border-primary-blue/20" textAlign="center">
<Text size="2xl" weight="bold" color="text-primary-blue" font="mono" block>{stat.value}</Text>
<Box key={i} bg="panel-gray/60" rounded="none" p={3} border borderColor="primary-accent/20" textAlign="center">
<Text size="2xl" weight="bold" color="text-primary-accent" font="mono" block>{stat.value}</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '11px' }}
@@ -52,6 +52,8 @@ export function CareerProgressionMockup() {
opacity={0.5}
mt={1}
block
font="mono"
uppercase
>
{stat.label}
</Text>
@@ -61,11 +63,11 @@ export function CareerProgressionMockup() {
{/* Single elegant season card */}
<Box as={motion.div} variants={itemVariants}>
<Box bg="bg-iron-gray/40" rounded="lg" p={3} border borderColor="border-charcoal-outline">
<Box bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
<Box display="flex" alignItems="center" justifyContent="between">
<Text size="sm" color="text-white" opacity={0.7}>GT3 Championship</Text>
<Box as="span" px={2.5} py={1} bg="bg-performance-green/20" rounded="sm">
<Text size="xs" color="text-performance-green" weight="semibold">P2</Text>
<Text size="sm" color="text-white" opacity={0.7} weight="bold">GT3 Championship</Text>
<Box as="span" px={2.5} py={1} bg="success-green/10" border borderColor="success-green/30">
<Text size="xs" color="text-success-green" weight="bold" font="mono">P2</Text>
</Box>
</Box>
</Box>
@@ -78,7 +80,7 @@ export function CareerProgressionMockup() {
// Desktop version - more detailed
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box
as={motion.div}
initial="hidden"
@@ -89,26 +91,29 @@ export function CareerProgressionMockup() {
>
<Stack gap={{ base: 1.5, sm: 3, md: 4, lg: 6 }}>
{/* Driver Header */}
<Box as={motion.div} variants={itemVariants} display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 3, md: 4, lg: 6 }} borderBottom borderColor="border-charcoal-outline">
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} bg="bg-charcoal-outline" rounded="full" border borderWidth="2px" borderColor="border-primary-blue/30" display="flex" alignItems="center" justifyContent="center" overflow="hidden">
<Box as={motion.div} variants={itemVariants} display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 3, md: 4, lg: 6 }} borderBottom borderColor="border-gray/50">
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} bg="panel-gray" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" display="flex" alignItems="center" justifyContent="center" overflow="hidden" className="relative">
<Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏎</Text>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box>
<Box flexGrow={1}>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.9} mb={{ base: 1, sm: 1.5, md: 2 }} block>Your Racing Identity</Text>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-white" opacity={0.9} mb={{ base: 1, sm: 1.5, md: 2 }} block className="uppercase tracking-widest">Your Racing Identity</Text>
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 2, md: 3 }}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-primary-blue"
opacity={0.7}
color="text-primary-accent"
weight="bold"
className="uppercase tracking-widest"
>
Multi-league profile
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-performance-green"
opacity={0.7}
color="text-success-green"
weight="bold"
className="uppercase tracking-widest"
>
Career tracking
</Text>
@@ -118,7 +123,7 @@ export function CareerProgressionMockup() {
{/* Career Stats */}
<Box as={motion.div} variants={itemVariants}>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.6} mb={{ base: 1, sm: 2, md: 3 }} block>Career Overview</Text>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-gray-500" mb={{ base: 1, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Career Overview</Text>
<Box display="grid" gridCols={3} gap={{ base: 1, sm: 2, md: 3 }}>
{[
{ label: 'Wins', value: '24' },
@@ -128,19 +133,19 @@ export function CareerProgressionMockup() {
<Box
key={i}
as={motion.div}
bg="bg-iron-gray"
rounded="lg"
bg="panel-gray/40"
rounded="none"
p={{ base: 1, sm: 2, md: 3 }}
border
borderColor="border-charcoal-outline"
borderColor="border-gray/50"
whileHover={shouldReduceMotion ? {} : {
y: -2,
boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
borderColor: '#198CFF80',
transition: { duration: 0.15 }
}}
>
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }} weight="bold" color="text-primary-blue" font="mono" mb={0.5} block>{stat.value}</Text>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.4} block>{stat.label}</Text>
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }} weight="bold" color="text-primary-accent" font="mono" mb={0.5} block>{stat.value}</Text>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-gray-500" weight="bold" className="uppercase tracking-widest" block>{stat.label}</Text>
</Box>
))}
</Box>
@@ -148,7 +153,7 @@ export function CareerProgressionMockup() {
{/* Season Timeline */}
<Box as={motion.div} variants={itemVariants}>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.6} mb={{ base: 1, sm: 2, md: 3 }} block>Season History</Text>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-gray-500" mb={{ base: 1, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Season History</Text>
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
{[
{ league: 'GT3 Championship', season: 'S3', position: 'P2', points: '248' },
@@ -161,30 +166,30 @@ export function CareerProgressionMockup() {
display="flex"
alignItems="center"
gap={{ base: 1.5, sm: 2, md: 3 }}
bg="bg-iron-gray"
rounded="lg"
bg="panel-gray/20"
rounded="none"
p={{ base: 1.5, sm: 2, md: 3 }}
border
borderColor="border-charcoal-outline"
borderColor="border-gray/30"
whileHover={shouldReduceMotion ? {} : {
x: 4,
boxShadow: '0 2px 12px rgba(25,140,255,0.2)',
borderColor: '#198CFF50',
transition: { duration: 0.15 }
}}
>
<Box h={{ base: 5, sm: 6, md: 8 }} w={{ base: 5, sm: 6, md: 8 }} bg="bg-charcoal-outline" rounded border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
<Box h={{ base: 5, sm: 6, md: 8 }} w={{ base: 5, sm: 6, md: 8 }} bg="panel-gray" rounded="none" border borderColor="primary-accent/20" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }}>🏁</Text>
</Box>
<Box flexGrow={1}>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.8} mb={{ base: 0.5, sm: 1 }} block>{season.league}</Text>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.4} block>Season complete</Text>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" weight="bold" mb={{ base: 0.5, sm: 1 }} block>{season.league}</Text>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-gray-500" font="mono" uppercase block>Season complete</Text>
</Box>
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-performance-green/20" rounded="sm">
<Text size={{ base: 'xs', sm: 'sm' }} color="text-performance-green" weight="semibold">{season.position}</Text>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="success-green/10" border borderColor="success-green/30">
<Text size={{ base: 'xs', sm: 'sm' }} color="text-success-green" weight="bold" font="mono">{season.position}</Text>
</Box>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-primary-blue/20" rounded="sm">
<Text size={{ base: 'xs', sm: 'sm' }} color="text-primary-blue" font="mono">{season.points}</Text>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/30">
<Text size={{ base: 'xs', sm: 'sm' }} color="text-primary-accent" font="mono" weight="bold">{season.points}</Text>
</Box>
</Box>
</Box>
@@ -194,18 +199,18 @@ export function CareerProgressionMockup() {
{/* Multi-League Badge */}
<Box as={motion.div} variants={itemVariants}>
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }} bg="bg-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3 }} border borderColor="border-primary-blue/30">
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }} bg="panel-gray/40" rounded="none" p={{ base: 1.5, sm: 2, md: 3 }} border borderColor="primary-accent/20">
<Box display="flex"
// eslint-disable-next-line gridpilot-rules/component-classification
className="-space-x-2"
>
{[1, 2, 3].map((i) => (
<Box key={i} h={{ base: 4, sm: 5, md: 6 }} w={{ base: 4, sm: 5, md: 6 }} bg="bg-iron-gray" rounded="full" border borderWidth="2px" borderColor="border-charcoal-outline" display="flex" alignItems="center" justifyContent="center">
<Box key={i} h={{ base: 4, sm: 5, md: 6 }} w={{ base: 4, sm: 5, md: 6 }} bg="panel-gray" rounded="full" border borderWidth="2px" borderColor="border-gray" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'xs', sm: 'sm' }}>🏆</Text>
</Box>
))}
</Box>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.6}>Active in 3 leagues across seasons</Text>
<Text size={{ base: 'xs', sm: 'sm' }} color="text-gray-400" weight="medium">Active in 3 leagues across seasons</Text>
</Box>
</Box>
</Stack>

View File

@@ -2,12 +2,10 @@
import { motion, useReducedMotion } from 'framer-motion';
import { useState, useEffect } from 'react';
import { Check } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
export function CompanionAutomationMockup() {
const shouldReduceMotion = useReducedMotion();
@@ -23,7 +21,7 @@ export function CompanionAutomationMockup() {
// Simple mobile version - just the essence of automation
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Box
as={motion.div}
initial={{ opacity: 0 }}
@@ -32,39 +30,41 @@ export function CompanionAutomationMockup() {
>
<Stack gap={4}>
{/* Simple progress indicator */}
<Box bg="bg-iron-gray/60" rounded="xl" p={4} border borderColor="border-primary-blue/40">
<Box bg="panel-gray/60" rounded="none" p={4} border borderColor="primary-accent/40">
<Box display="flex" alignItems="center" gap={3} mb={3}>
<Box
as={motion.div}
h="8"
w="8"
bg="bg-performance-green/40"
rounded="full"
bg="success-green/20"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
border
borderWidth="2px"
borderColor="border-performance-green/60"
borderWidth="1px"
borderColor="success-green/40"
flexShrink={0}
animate={shouldReduceMotion ? {} : {
scale: [1, 1.1, 1],
scale: [1, 1.05, 1],
opacity: [0.6, 1, 0.6]
}}
transition={{ duration: 1.5, repeat: Infinity }}
className="relative"
>
<Box h="3" w="3" bg="bg-performance-green" rounded="full" />
<Box h="2" w="2" bg="success-green" />
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="success-green" />
</Box>
<Box>
<Text size="sm" color="text-white" weight="medium" block>Creating Session</Text>
<Text size="xs" color="text-white" opacity={0.5} block>Automated</Text>
<Text size="sm" color="text-white" weight="bold" block className="uppercase tracking-widest">Creating Session</Text>
<Text size="xs" color="text-gray-500" block font="mono">AUTOMATED</Text>
</Box>
</Box>
<Box h="2.5" fullWidth bg="bg-white/5" rounded="full" overflow="hidden">
<Box h="1" fullWidth bg="white/5" rounded="none" overflow="hidden">
<Box
as={motion.div}
h="full"
bg="bg-primary-blue/60"
bg="primary-accent"
initial={{ width: '0%' }}
animate={{ width: '75%' }}
transition={{ duration: 2, ease: 'easeInOut' }}
@@ -74,8 +74,8 @@ export function CompanionAutomationMockup() {
{/* Simple CTA */}
<Box display="flex" justifyContent="center">
<Box bg="bg-primary-blue/20" color="text-primary-blue" px={6} py={2.5} rounded="lg" border borderColor="border-primary-blue/40">
<Text size="sm" weight="semibold">One Click</Text>
<Box bg="primary-accent/10" color="text-primary-accent" px={6} py={2.5} rounded="none" border borderColor="primary-accent/30">
<Text size="xs" weight="bold" className="uppercase tracking-[0.2em]">One Click</Text>
</Box>
</Box>
</Stack>
@@ -86,7 +86,7 @@ export function CompanionAutomationMockup() {
// Desktop version - richer with more automation steps
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 3, md: 4, lg: 6 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 3, md: 4, lg: 6 }} overflow="hidden">
<Box
as={motion.div}
initial="hidden"
@@ -103,12 +103,14 @@ export function CompanionAutomationMockup() {
{/* Companion App Header - Enhanced */}
<Box as={motion.div} variants={{ hidden: { opacity: 0, y: -10 }, visible: { opacity: 1, y: 0 } }}>
<Box display="flex" alignItems="center" gap={{ base: 2.5, md: 3, lg: 4 }} mb={{ base: 3, md: 4, lg: 5 }}>
<Box h={{ base: 10, md: 12, lg: 14 }} w={{ base: 10, md: 12, lg: 14 }} bg="bg-primary-blue/20" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/40" display="flex" alignItems="center" justifyContent="center" shadow="lg">
<Box h={{ base: 6, md: 7, lg: 8 }} w={{ base: 6, md: 7, lg: 8 }} bg="bg-primary-blue/60" rounded="sm" />
<Box h={{ base: 10, md: 12, lg: 14 }} w={{ base: 10, md: 12, lg: 14 }} bg="primary-accent/10" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" display="flex" alignItems="center" justifyContent="center" className="relative">
<Box h={{ base: 6, md: 7, lg: 8 }} w={{ base: 6, md: 7, lg: 8 }} bg="primary-accent/40" />
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
<Box position="absolute" bottom="-1px" right="-1px" w="2" h="2" borderBottom borderRight borderColor="primary-accent" />
</Box>
<Box>
<Heading level={2} fontSize={{ base: 'base', md: 'lg', lg: 'xl' }} weight="semibold" color="text-white">GridPilot Companion</Heading>
<Text size={{ base: 'xs', md: 'sm', lg: 'base' }} color="text-white" opacity={0.5} block>Automated Session Creator</Text>
<Heading level={2} fontSize={{ base: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" className="uppercase tracking-widest">GridPilot Companion</Heading>
<Text size={{ base: 'xs', md: 'sm', lg: 'base' }} color="text-gray-500" block font="mono">AUTOMATED SESSION CREATOR</Text>
</Box>
</Box>
</Box>
@@ -118,26 +120,25 @@ export function CompanionAutomationMockup() {
as={motion.div}
variants={{ hidden: { opacity: 0, y: 10 }, visible: { opacity: 1, y: 0 } }}
position="relative"
bg="bg-charcoal-outline"
rounded="lg"
bg="panel-gray/40"
rounded="none"
p={{ base: 3, md: 4, lg: 5 }}
border
borderWidth="2px"
borderColor="border-primary-blue/40"
borderWidth="1px"
borderColor="border-gray/50"
overflow="hidden"
>
{/* Browser Window Mockup */}
<Stack gap={{ base: 3, md: 4 }}>
<Box display="flex" alignItems="center" gap={{ base: 2, md: 2.5 }} pb={{ base: 3, md: 4 }} borderBottom borderColor="border-white/10">
<Box h={{ base: 2.5, md: 3 }} w={{ base: 2.5, md: 3 }} bg="bg-red-500/60" rounded="full" />
<Box h={{ base: 2.5, md: 3 }} w={{ base: 2.5, md: 3 }} bg="bg-warning-amber/60" rounded="full" />
<Box h={{ base: 2.5, md: 3 }} w={{ base: 2.5, md: 3 }} bg="bg-performance-green/60" rounded="full" />
<Box flexGrow={1} h={{ base: 2.5, md: 3 }} bg="bg-white/5" rounded="sm" ml={2} px={2} display="flex" alignItems="center">
<Box display="flex" alignItems="center" gap={{ base: 2, md: 2.5 }} pb={{ base: 3, md: 4 }} borderBottom borderColor="border-gray/30">
<Box h="2" w="2" bg="critical-red/40" rounded="none" />
<Box h="2" w="2" bg="warning-amber/40" rounded="none" />
<Box h="2" w="2" bg="success-green/40" rounded="none" />
<Box flexGrow={1} h="4" bg="graphite-black" rounded="none" ml={2} px={2} display="flex" alignItems="center" border borderColor="border-gray/30">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
color="text-white"
opacity={0.3}
color="text-gray-500"
font="mono"
>
members.iracing.com
@@ -169,54 +170,53 @@ export function CompanionAutomationMockup() {
as={motion.div}
h={{ base: 7, md: 8, lg: 9 }}
w={{ base: 7, md: 8, lg: 9 }}
rounded="full"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
flexShrink={0}
border
borderWidth="2px"
borderWidth="1px"
bg={
step.status === 'Complete'
? 'bg-performance-green/40'
? 'success-green/10'
: step.status === 'Running'
? 'bg-primary-blue/40'
: 'bg-charcoal-outline'
? 'primary-accent/10'
: 'transparent'
}
borderColor={
step.status === 'Complete'
? 'border-performance-green/60'
? 'success-green/40'
: step.status === 'Running'
? 'border-primary-blue/60'
: 'border-white/20'
? 'primary-accent/40'
: 'border-gray/30'
}
animate={shouldReduceMotion ? {} : step.status === 'Running' ? {
scale: [1, 1.15, 1],
opacity: [0.4, 1, 0.4]
} : {}}
transition={{ duration: 1.5, repeat: Infinity }}
>
{step.status === 'Complete' && (
<Icon icon={Check} size={5} color="text-performance-green" />
<Box w="2" h="2" bg="success-green" />
)}
{step.status === 'Running' && (
<Box h={{ base: 3, md: 4 }} w={{ base: 3, md: 4 }} bg="bg-primary-blue" rounded="full" />
<Box h="2" w="2" bg="primary-accent" />
)}
{step.status === 'Pending' && (
<Box h={{ base: 2, md: 2.5 }} w={{ base: 2, md: 2.5 }} bg="bg-white/30" rounded="full" />
<Box h="1" w="1" bg="gray-700" />
)}
</Box>
<Box flexGrow={1}>
<Text size={{ base: 'sm', md: 'base', lg: 'lg' }} color="text-white" weight="medium" block>{step.label}</Text>
<Text size={{ base: 'xs', md: 'sm' }} color="text-white" opacity={0.5} block>{step.detail}</Text>
<Text size={{ base: 'sm', md: 'base' }} color="text-white" weight="bold" block className="uppercase tracking-widest">{step.label}</Text>
<Text size="xs" color="text-gray-500" block font="mono">{step.detail.toUpperCase()}</Text>
</Box>
</Box>
{step.status !== 'Pending' && (
<Box h={{ base: 2.5, md: 3 }} fullWidth bg="bg-white/5" rounded="full" overflow="hidden" ml={{ base: 9, md: 10, lg: 11 }}>
<Box h="1" fullWidth bg="white/5" rounded="none" overflow="hidden" ml={{ base: 9, md: 10, lg: 11 }}>
<Box
as={motion.div}
h="full"
bg={step.status === 'Complete' ? 'bg-performance-green/60' : 'bg-primary-blue/60'}
bg={step.status === 'Complete' ? 'success-green/60' : 'primary-accent/60'}
initial={{ width: '0%' }}
animate={{ width: step.status === 'Complete' ? '100%' : '65%' }}
transition={{ duration: 2, ease: 'easeInOut' }}
@@ -238,36 +238,33 @@ export function CompanionAutomationMockup() {
display="flex"
alignItems="center"
gap={2}
bg="bg-deep-graphite/90"
// eslint-disable-next-line gridpilot-rules/component-classification
className="backdrop-blur-sm"
bg="graphite-black"
px={3}
py={2}
rounded="full"
py={1.5}
rounded="none"
border
borderWidth="2px"
borderColor="border-primary-blue/40"
borderWidth="1px"
borderColor="primary-accent/40"
animate={shouldReduceMotion ? {} : {
boxShadow: [
'0 0 12px rgba(25,140,255,0.3)',
'0 0 20px rgba(25,140,255,0.5)',
'0 0 12px rgba(25,140,255,0.3)'
'0 0 8px rgba(25,140,255,0.2)',
'0 0 15px rgba(25,140,255,0.4)',
'0 0 8px rgba(25,140,255,0.2)'
]
}}
transition={{ duration: 2, repeat: Infinity }}
>
<Box
as={motion.div}
h={{ base: 2.5, md: 3 }}
w={{ base: 2.5, md: 3 }}
bg="bg-primary-blue"
rounded="full"
h="1.5"
w="1.5"
bg="primary-accent"
animate={shouldReduceMotion ? {} : {
opacity: [1, 0.5, 1]
opacity: [1, 0.3, 1]
}}
transition={{ duration: 1.5, repeat: Infinity }}
/>
<Text size={{ base: 'xs', md: 'sm' }} color="text-primary-blue" weight="medium">Running</Text>
<Text size="xs" color="text-primary-accent" weight="bold" className="uppercase tracking-widest">Running</Text>
</Box>
</Box>
@@ -278,32 +275,36 @@ export function CompanionAutomationMockup() {
display="flex"
flexDirection="col"
alignItems="center"
gap={{ base: 2, md: 3 }}
gap={3}
>
<Box
as={motion.div}
bg="bg-primary-blue/20"
color="text-primary-blue"
bg="primary-accent/10"
color="text-primary-accent"
px={8}
py={4}
rounded="lg"
py={3}
rounded="none"
border
borderWidth="2px"
borderColor="border-primary-blue/40"
borderWidth="1px"
borderColor="primary-accent/40"
cursor="pointer"
whileHover={shouldReduceMotion ? {} : {
scale: 1.03,
boxShadow: '0 4px 24px rgba(25,140,255,0.3)',
scale: 1.02,
borderColor: '#198CFF',
transition: { duration: 0.15 }
}}
className="relative"
>
<Text size={{ base: 'base', md: 'lg' }} weight="semibold">Create Session</Text>
<Text size="sm" weight="bold" className="uppercase tracking-[0.2em]">Create Session</Text>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
<Box position="absolute" bottom="-1px" right="-1px" w="2" h="2" borderBottom borderRight borderColor="primary-accent" />
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.4}
color="text-gray-500"
font="mono"
className="uppercase tracking-widest"
>
One click. All fields automated.
</Text>

View File

@@ -11,7 +11,10 @@ export function DriverProfileMockup() {
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
setIsMobile(window.innerWidth < 768);
const checkMobile = () => setIsMobile(window.innerWidth < 768);
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
const stats = [
@@ -26,77 +29,58 @@ export function DriverProfileMockup() {
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
<Stack gap={4}>
<Box>
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Box display="flex" alignItems="center" gap={3}>
<Box position="relative" h="12" w="12" rounded="full" border borderWidth="2px" borderColor="border-primary-blue/50" overflow="hidden" bg="bg-charcoal-outline">
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center">
<Box position="relative" h="12" w="12" rounded="none" border borderWidth="1px" borderColor="primary-accent/50" overflow="hidden" bg="panel-gray" display="flex" alignItems="center" justifyContent="center">
<Text size="2xl">🏎</Text>
</Box>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box>
<Box>
<Text weight="bold" color="text-white" block>Driver Profile</Text>
<Text size="xs" color="text-white" opacity={0.5} block>Cross-league</Text>
<Text weight="bold" color="text-white" block className="uppercase tracking-widest">Driver Profile</Text>
<Text size="xs" color="text-gray-500" block font="mono">CROSS-LEAGUE</Text>
</Box>
</Box>
<Text size="2xl" weight="bold" color="text-charcoal-outline">#33</Text>
<Text size="2xl" weight="bold" color="text-gray-800" font="mono">#33</Text>
</Box>
<Box position="relative" h="2" bg="bg-charcoal-outline" rounded="full" overflow="hidden" mb={1}>
<Box position="relative" h="1" bg="white/5" rounded="none" overflow="hidden" mb={1}>
<Box
position="absolute"
insetY="0"
left="0"
bg="bg-gradient-to-r from-primary-blue to-neon-aqua"
rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
bg="primary-accent"
style={{ width: '86%' }}
/>
</Box>
<Box display="flex" justifyContent="end">
<Text size="xs" color="text-gray-400">2150 GP Rating</Text>
<Text size="xs" color="text-primary-accent" font="mono" weight="bold">2150 GP RATING</Text>
</Box>
</Box>
<Box>
<Text size="sm" weight="semibold" color="text-white" mb={2} block>Career Stats</Text>
<Text size="xs" weight="bold" color="text-gray-500" mb={2} block className="uppercase tracking-widest">Career Stats</Text>
<Box display="grid" gridCols={3} gap={2}>
{stats.slice(0, 3).map((stat) => (
<Box
key={stat.label}
bg="bg-iron-gray/50"
bg="panel-gray/40"
border
borderColor="border-charcoal-outline"
rounded="lg"
borderColor="border-gray/50"
rounded="none"
p={2}
textAlign="center"
>
<Text weight="bold" color="text-white" font="mono" block>
{stat.value}{stat.suffix}
</Text>
<Text size="xs" color="text-gray-400" mt={0.5} block>{stat.label}</Text>
<Text size="xs" color="text-gray-500" mt={0.5} block className="uppercase tracking-tighter">{stat.label}</Text>
</Box>
))}
</Box>
</Box>
<Box>
<Text size="sm" weight="semibold" color="text-white" mb={2} block>Recent Form</Text>
<Box h="16" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" rounded="lg" p={2} display="flex" alignItems="end" gap={1}>
{formData.slice(-6).map((value, i) => (
<Box
key={i}
flexGrow={1}
bg="bg-gradient-to-t from-performance-green to-primary-blue"
rounded="sm"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ height: `${value}%` }}
/>
))}
</Box>
</Box>
</Stack>
</Box>
);
@@ -120,7 +104,7 @@ export function DriverProfileMockup() {
};
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box
as={motion.div}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }}
@@ -129,50 +113,46 @@ export function DriverProfileMockup() {
>
<Box display="flex" alignItems="center" justifyContent="between" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
<Box position="relative" h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="full" border borderWidth="2px" borderColor="border-primary-blue/50" overflow="hidden" bg="bg-charcoal-outline">
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center">
<Box position="relative" h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="none" border borderWidth="1px" borderColor="primary-accent/50" overflow="hidden" bg="panel-gray" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏎</Text>
</Box>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box>
<Box>
<Text size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={{ base: 1, sm: 1.5, md: 2 }} block>Driver Profile</Text>
<Text size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={{ base: 1, sm: 1.5, md: 2 }} block className="uppercase tracking-widest">Driver Profile</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
color="text-gray-500"
block
font="mono"
className="uppercase tracking-widest"
>
Cross-league racing identity
</Text>
</Box>
</Box>
<Text size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold" color="text-charcoal-outline">#33</Text>
<Text size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold" color="text-gray-800" font="mono">#33</Text>
</Box>
<Box display="flex" flexWrap="wrap" alignItems="center" gap={{ base: 1, sm: 2, md: 3, lg: 4 }} mb={{ base: 1, sm: 1.5, md: 2 }}>
<Text size="xs" color="text-gray-400">GridPilot Rating:</Text>
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">GP RATING:</Text>
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={2150} />
<Text size="xs" color="text-gray-400">iRating:</Text>
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest ml-4">iRATING:</Text>
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={3200} />
</Box>
<Box position="relative" h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} bg="bg-charcoal-outline" rounded="full" overflow="hidden">
<Box position="relative" h="1" bg="white/5" rounded="none" overflow="hidden">
<Box
as={motion.div}
position="absolute"
insetY="0"
left="0"
bg="bg-gradient-to-r from-primary-blue to-neon-aqua"
rounded="full"
bg="primary-accent"
initial={{ width: '0%' }}
animate={{ width: '86%' }}
transition={{ delay: shouldReduceMotion ? 0 : 0.4, duration: 0.8, ease: 'easeOut' }}
/>
</Box>
<Box display="flex" justifyContent="end" mt={1}>
<Text size="xs" color="text-gray-400">86%</Text>
</Box>
</Box>
<Box
@@ -182,8 +162,7 @@ export function DriverProfileMockup() {
animate="visible"
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }}
>
<Text size="sm" weight="semibold" color="text-white" mb={1} block>Career Statistics</Text>
<Text size="xs" color="text-white" opacity={0.5} mb={{ base: 1, sm: 2, md: 3 }} block>Aggregated across all leagues</Text>
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-[0.2em]">Career Statistics</Text>
<Box display="grid" gridCols={{ base: 2, md: 5 }} gap={{ base: 1.5, sm: 2, md: 3 }}>
{stats.map((stat, index) => (
@@ -191,10 +170,10 @@ export function DriverProfileMockup() {
key={stat.label}
as={motion.div}
variants={itemVariants}
bg="bg-iron-gray/50"
bg="panel-gray/40"
border
borderColor="border-charcoal-outline"
rounded="lg"
borderColor="border-gray/50"
rounded="none"
p={{ base: 1.5, sm: 2, md: 3 }}
textAlign="center"
>
@@ -204,7 +183,7 @@ export function DriverProfileMockup() {
delay={index * 0.1}
suffix={stat.suffix ?? ''}
/>
<Text size="xs" color="text-gray-400" mt={0.5} block>{stat.label}</Text>
<Text size="xs" color="text-gray-500" mt={0.5} block className="uppercase tracking-tighter font-bold">{stat.label}</Text>
</Box>
))}
</Box>
@@ -212,22 +191,20 @@ export function DriverProfileMockup() {
<Box
as={motion.div}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.6 }}
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }}
transition={{ delay: 0.6 }}
>
<Text size="sm" weight="semibold" color="text-white" mb={1} block>Recent Form</Text>
<Text size="xs" color="text-white" opacity={0.5} mb={{ base: 1, sm: 2, md: 3 }} block>Performance trend over last 10 races</Text>
<Box h={{ base: 12, sm: 16, md: 20 }} bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3 }} display="flex" alignItems="end" gap={0.5}>
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-[0.2em]">Recent Form</Text>
<Box h={{ base: 12, sm: 16, md: 20 }} bg="panel-gray/20" border borderColor="border-gray/50" rounded="none" p={{ base: 1.5, sm: 2, md: 3 }} display="flex" alignItems="end" gap={1}>
{formData.map((value, i) => (
<Box
key={i}
as={motion.div}
flexGrow={1}
bg="bg-gradient-to-t from-performance-green to-primary-blue"
rounded="sm"
bg="primary-accent"
opacity={0.4 + (i * 0.06)}
rounded="none"
initial={{ height: 0 }}
animate={{ height: `${value}%` }}
transition={{
@@ -238,66 +215,6 @@ export function DriverProfileMockup() {
/>
))}
</Box>
<Box display="flex" justifyContent="between" mt={0.5}>
<Text size="xs" color="text-gray-500">Last 10 races</Text>
<Text size="xs" color="text-gray-500">Recent</Text>
</Box>
</Box>
<Box
as={motion.div}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.8 }}
>
<Text size="sm" weight="semibold" color="text-white" mb={1} block>Teams</Text>
<Text size="xs" color="text-white" opacity={0.5} mb={{ base: 1, sm: 2, md: 3 }} block>Current and past team memberships</Text>
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
{[
{ team: 'Red Bull Racing', status: 'Current', color: 'primary-blue' },
{ team: 'Mercedes AMG', status: '2023', color: 'charcoal-outline' }
].map((team, i) => (
<Box
key={team.team}
as={motion.div}
initial={{ opacity: 0, x: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.9 + i * 0.1 }}
display="flex"
alignItems="center"
justifyContent="between"
bg="bg-iron-gray/30"
border
borderColor="border-charcoal-outline"
rounded="lg"
p={{ base: 1, sm: 1.5, md: 2 }}
>
<Box display="flex" alignItems="center" gap={{ base: 2, sm: 3 }}>
<Box h={{ base: 5, sm: 6, md: 8 }} w={{ base: 5, sm: 6, md: 8 }} rounded="sm" border borderColor="border-primary-blue/30" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }}>🏁</Text>
</Box>
<Box h={{ base: 1.5, sm: 2, md: 3 }} w={{ base: 16, sm: 20, md: 32 }} bg="bg-white/10" rounded="sm" />
</Box>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} rounded="sm" bg={team.status === 'Current' ? 'bg-primary-blue/20' : 'bg-charcoal-outline'}>
<Text size="xs" color={team.status === 'Current' ? 'text-primary-blue' : 'text-gray-400'}>
{team.status}
</Text>
</Box>
</Box>
))}
</Stack>
<Box
as={motion.div}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: shouldReduceMotion ? 0 : 1.2 }}
mt={{ base: 2, sm: 3, md: 4 }}
textAlign="center"
>
<Text size="xs" color="text-gray-400">Active in 3 leagues</Text>
</Box>
</Box>
</Box>
);
@@ -318,7 +235,7 @@ function AnimatedRating({ shouldReduceMotion, value }: { shouldReduceMotion: boo
}, [shouldReduceMotion, count, value]);
return (
<Box as={motion.span} weight="bold" color="text-primary-blue" font="mono" size={{ base: 'sm', sm: 'base', md: 'lg' }}>
<Box as={motion.span} weight="bold" color="text-primary-accent" font="mono" size={{ base: 'sm', sm: 'base' }}>
{shouldReduceMotion ? value : <Box as={motion.span}>{rounded}</Box>}
</Box>
);
@@ -348,7 +265,7 @@ function AnimatedCounter({
}, [shouldReduceMotion, count, value, delay]);
return (
<Box weight="bold" color="text-white" font="mono" size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }}>
<Box weight="bold" color="text-white" font="mono" size={{ base: 'sm', sm: 'base', md: 'lg' }}>
{shouldReduceMotion ? value : <Box as={motion.span}>{rounded}</Box>}{suffix}
</Box>
);

View File

@@ -43,86 +43,58 @@ export function LeagueDiscoveryMockup() {
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
<Box mb={3}>
<Box h="4" w="40" bg="bg-white/10" rounded="sm" mb={3} />
<Box h="1.5" w="40" bg="white/10" rounded="none" mb={3} />
<Box display="flex" gap={2} flexWrap="wrap">
{['Game', 'Region'].map((filter) => (
<Box
key={filter}
h="6"
h="5"
px={3}
bg="bg-charcoal-outline"
bg="panel-gray"
border
borderColor="border-primary-blue/30"
rounded="full"
borderColor="primary-accent/30"
rounded="none"
display="flex"
alignItems="center"
>
<Box h="1.5" w="10" bg="bg-white/10" rounded="sm" />
<Box h="1" w="8" bg="white/10" rounded="none" />
</Box>
))}
</Box>
</Box>
<Stack gap={3}>
<Stack gap={2}>
{leagues.map((league) => (
<Box
key={league.name}
bg="bg-iron-gray/80"
bg="panel-gray/40"
border
borderColor="border-charcoal-outline"
rounded="lg"
borderColor="border-gray/50"
rounded="none"
p={3}
>
<Box display="flex" alignItems="start" justifyContent="between" mb={2}>
<Box display="flex" alignItems="center" gap={2}>
<Box h="10" w="10" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/30" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center">
<Box h="10" w="10" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" bg="graphite-black" display="flex" alignItems="center" justifyContent="center">
<Text size="xl">{league.icon}</Text>
</Box>
<Box>
<Box h="3" w="32" bg="bg-white/10" rounded="sm" mb={1} />
<Box h="1.5" w="32" bg="white/10" rounded="none" mb={1.5} />
<Box display="flex" gap={1}>
<Box as="span" px={1.5} py={0.5} bg="bg-primary-blue/20" rounded="sm">
<Text size="xs" color="text-primary-blue">{league.carClass}</Text>
</Box>
<Box as="span" px={1.5} py={0.5} bg="bg-neon-aqua/20" rounded="sm">
<Text size="xs" color="text-neon-aqua">{league.region}</Text>
<Box as="span" px={1.5} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/20">
<Text size="xs" color="text-primary-accent" weight="bold" font="mono">{league.carClass}</Text>
</Box>
</Box>
</Box>
</Box>
</Box>
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Box display="flex" alignItems="center" gap={2}>
<Text size="xs" color="text-gray-400">{league.drivers} drivers</Text>
<Text size="xs" color="text-gray-400"></Text>
<Text size="xs" color="text-gray-400">{league.schedule}</Text>
</Box>
</Box>
<Box display="flex" alignItems="center" justifyContent="between">
<Box display="flex" alignItems="center" gap={1}>
{[...Array(5)].map((_, i) => (
<Box
as="svg"
key={i}
w="3"
h="3"
color={i < Math.floor(league.rating) ? 'text-warning-amber' : 'text-charcoal-outline'}
// eslint-disable-next-line gridpilot-rules/component-classification
className="fill-current"
viewBox="0 0 20 20"
>
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</Box>
))}
</Box>
<Box as="button" px={2} py={1} bg="bg-primary-blue" rounded="sm">
<Text size="xs" color="text-white">Join</Text>
<Text size="xs" color="text-gray-500" font="mono">{league.drivers} DRIVERS {league.schedule.toUpperCase()}</Text>
<Box px={2} py={0.5} bg="primary-accent" rounded="none">
<Text size="xs" color="text-white" weight="bold">JOIN</Text>
</Box>
</Box>
</Box>
@@ -141,25 +113,25 @@ export function LeagueDiscoveryMockup() {
};
const cardVariants = {
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 20 },
hidden: { opacity: 0, x: shouldReduceMotion ? 0 : -20 },
visible: {
opacity: 1,
y: 0,
transition: { type: 'spring' as const, stiffness: 200, damping: 20 }
x: 0,
transition: { duration: 0.4 }
}
};
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box
as={motion.div}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, y: 0 }}
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }}
>
<Box h={{ base: 3, sm: 4, md: 5, lg: 6 }} w={{ base: 32, sm: 40, md: 52 }} bg="bg-white/10" rounded="sm" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} />
<Box h="2" w="48" bg="white/10" rounded="none" mb={4} />
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }} flexWrap="wrap">
<Box display="flex" gap={2} flexWrap="wrap">
{['Game', 'Region', 'Skill'].map((filter, i) => (
<Box
key={filter}
@@ -167,16 +139,16 @@ export function LeagueDiscoveryMockup() {
initial={{ opacity: 0, scale: shouldReduceMotion ? 1 : 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: shouldReduceMotion ? 0 : i * 0.1 }}
h={{ base: 5, sm: 6, md: 7, lg: 8 }}
px={{ base: 2, sm: 3, md: 4 }}
bg="bg-charcoal-outline"
h="6"
px={4}
bg="panel-gray"
border
borderColor="border-primary-blue/30"
rounded="full"
borderColor="primary-accent/30"
rounded="none"
display="flex"
alignItems="center"
>
<Box h={{ base: 1, sm: 1.5, md: 2 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-white/10" rounded="sm" />
<Box h="1" w="10" bg="white/10" rounded="none" />
</Box>
))}
</Box>
@@ -188,7 +160,7 @@ export function LeagueDiscoveryMockup() {
initial="hidden"
animate="visible"
>
<Stack gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
<Stack gap={2}>
{leagues.map((league, index) => (
<Box
key={league.name}
@@ -197,157 +169,90 @@ export function LeagueDiscoveryMockup() {
onHoverStart={() => !shouldReduceMotion && setHoveredIndex(index)}
onHoverEnd={() => setHoveredIndex(null)}
whileHover={shouldReduceMotion ? {} : {
scale: 1.02,
y: -4,
x: 4,
borderColor: '#198CFF30',
transition: { duration: 0.2 }
}}
bg="bg-iron-gray/80"
bg="panel-gray/20"
border
borderColor="border-charcoal-outline"
rounded="lg"
borderColor="border-gray/50"
rounded="none"
p={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
// eslint-disable-next-line gridpilot-rules/component-classification
className="backdrop-blur-sm"
>
<Box display="flex" alignItems="start" justifyContent="between" mb={{ base: 1.5, sm: 2, md: 3 }}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
<Box h={{ base: 7, sm: 8, md: 10, lg: 12 }} w={{ base: 7, sm: 8, md: 10, lg: 12 }} rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/30" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" shadow="0_0_12px_rgba(25,140,255,0.2)">
<Box display="flex" alignItems="start" justifyContent="between" mb={3}>
<Box display="flex" alignItems="center" gap={3}>
<Box h={{ base: 7, sm: 8, md: 10, lg: 12 }} w={{ base: 7, sm: 8, md: 10, lg: 12 }} rounded="none" border borderWidth="1px" borderColor="primary-accent/30" bg="graphite-black" display="flex" alignItems="center" justifyContent="center" className="relative">
<Text size={{ base: 'base', sm: 'lg', md: 'xl', lg: '2xl' }}>{league.icon}</Text>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box>
<Box>
<Box h={{ base: 2.5, sm: 3, md: 4 }} w={{ base: 28, sm: 32, md: 40 }} bg="bg-white/10" rounded="sm" mb={{ base: 1, sm: 1.5, md: 2 }} />
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-primary-blue/20" rounded="sm">
<Box h="2" w="40" bg="white/10" rounded="none" mb={2} />
<Box display="flex" gap={2}>
<Box as="span" px={2} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/20">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-primary-blue"
color="text-primary-accent"
weight="bold"
font="mono"
>
{league.carClass}
</Text>
</Box>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-neon-aqua/20" rounded="sm">
<Box as="span" px={2} py={0.5} bg="telemetry-aqua/10" border borderColor="telemetry-aqua/20">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-neon-aqua"
color="text-telemetry-aqua"
weight="bold"
font="mono"
>
{league.region}
</Text>
</Box>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-charcoal-outline" rounded="sm">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-gray-400"
>
{league.skill}
</Text>
</Box>
</Box>
</Box>
</Box>
</Box>
<Box display="flex" alignItems="center" justifyContent="between" mb={{ base: 1.5, sm: 2, md: 3 }}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
<Box display="flex" alignItems="center" gap={1}>
<Box as="svg" w={{ base: 2.5, sm: 3, md: 4 }} h={{ base: 2.5, sm: 3, md: 4 }} color="text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-gray-400"
>
{league.drivers} drivers
</Text>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Box as="svg" w={{ base: 2.5, sm: 3, md: 4 }} h={{ base: 2.5, sm: 3, md: 4 }} color="text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-gray-400"
>
{league.schedule}
</Text>
</Box>
</Box>
</Box>
<Box display="flex" alignItems="center" justifyContent="between">
<Box display="flex" alignItems="center" gap={1}>
{[...Array(5)].map((_, i) => (
<Box
as={motion.svg}
key={i}
w={{ base: 2.5, sm: 3, md: 4 }}
h={{ base: 2.5, sm: 3, md: 4 }}
color={i < Math.floor(league.rating) ? 'text-warning-amber' : 'text-charcoal-outline'}
// eslint-disable-next-line gridpilot-rules/component-classification
className="fill-current"
viewBox="0 0 20 20"
animate={hoveredIndex === index && !shouldReduceMotion ? {
scale: [1, 1.2, 1],
transition: { delay: i * 0.05, duration: 0.3 }
} : {}}
>
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</Box>
))}
<Box display="flex" alignItems="center" gap={4}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-gray-400"
ml={1}
color="text-gray-500"
font="mono"
weight="bold"
>
{league.rating}
{league.drivers} DRIVERS
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-gray-500"
font="mono"
weight="bold"
>
{league.schedule.toUpperCase()}
</Text>
</Box>
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}>
<Box display="flex" gap={2}>
<Box
as={motion.button}
whileHover={shouldReduceMotion ? {} : { scale: 1.05 }}
whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}
px={{ base: 1.5, sm: 2, md: 3 }}
py={0.5}
bg="bg-primary-blue"
px={3}
py={1}
bg="primary-accent"
color="text-white"
rounded="sm"
transition
hoverBg="bg-primary-blue/80"
rounded="none"
>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
weight="bold"
>
Join
</Text>
</Box>
<Box
as={motion.button}
whileHover={shouldReduceMotion ? {} : { scale: 1.05 }}
whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}
px={{ base: 1.5, sm: 2, md: 3 }}
py={0.5}
bg="bg-charcoal-outline"
color="text-gray-300"
rounded="sm"
transition
hoverBg="bg-charcoal-outline/80"
>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
>
View
JOIN
</Text>
</Box>
</Box>

View File

@@ -17,47 +17,45 @@ export function LeagueHomeMockup() {
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
<Stack gap={4}>
<Box display="flex" alignItems="center" gap={3} mb={2}>
<Box h="12" w="12" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/50" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" shadow="0_0_20px_rgba(25,140,255,0.3)">
<Box h="12" w="12" rounded="none" border borderWidth="1px" borderColor="primary-accent/50" bg="panel-gray" display="flex" alignItems="center" justifyContent="center" className="relative">
<Text size="2xl">🏆</Text>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box>
<Box>
<Text size="base" weight="bold" color="text-white" block>Super GT</Text>
<Text size="xs" color="text-gray-400" block>Round 8/12</Text>
<Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">Super GT</Text>
<Text size="xs" color="text-gray-500" block font="mono">ROUND 8/12</Text>
</Box>
</Box>
<Box>
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Next Race</Text>
<Box position="relative" display="flex" alignItems="center" gap={3} bg="bg-iron-gray" rounded="lg" p={3} border borderColor="border-charcoal-outline">
<Box h="8" w="8" bg="bg-charcoal-outline" rounded border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Next Race</Text>
<Box position="relative" display="flex" alignItems="center" gap={3} bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
<Box h="8" w="8" bg="graphite-black" rounded="none" border borderColor="primary-accent/20" display="flex" alignItems="center" justifyContent="center">
<Text size="base">🏁</Text>
</Box>
<Box flexGrow={1}>
<Box h="2.5" w="28" bg="bg-white/10" rounded="sm" mb={2} />
<Box h="2" w="20" bg="bg-white/5" rounded="sm" />
<Box h="2" w="28" bg="white/10" rounded="none" mb={2} />
<Box h="1.5" w="20" bg="white/5" rounded="none" />
</Box>
<Box w="2" h="2" bg="bg-primary-blue" rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
className="shadow-glow"
/>
<Box w="1.5" h="1.5" bg="primary-accent" rounded="full" className="animate-pulse" />
</Box>
</Box>
<Box>
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Latest Result</Text>
<Box bg="bg-iron-gray" rounded="lg" p={3} border borderColor="border-charcoal-outline">
<Box display="flex" alignItems="center" gap={3} py={2} borderBottom borderColor="border-charcoal-outline">
<Box h="2.5" w="6" bg="bg-white/10" rounded="sm" />
<Box h="2.5" flexGrow={1} bg="bg-white/10" rounded="sm" />
<Box h="2.5" w="10" bg="bg-white/10" rounded="sm" />
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Latest Result</Text>
<Box bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
<Box display="flex" alignItems="center" gap={3} py={2} borderBottom borderColor="border-gray/30">
<Box h="1.5" w="6" bg="white/10" rounded="none" />
<Box h="1.5" flexGrow={1} bg="white/10" rounded="none" />
<Box h="1.5" w="10" bg="white/10" rounded="none" />
</Box>
<Box display="flex" alignItems="center" gap={3} py={2}>
<Box h="2" w="6" bg="bg-white/5" rounded="sm" />
<Box h="2" flexGrow={1} bg="bg-white/5" rounded="sm" />
<Box h="2" w="10" bg="bg-performance-green/20" rounded="sm" />
<Box h="1.5" w="6" bg="white/5" rounded="none" />
<Box h="1.5" flexGrow={1} bg="white/5" rounded="none" />
<Box h="1.5" w="10" bg="success-green/20" rounded="none" />
</Box>
</Box>
</Box>
@@ -80,7 +78,7 @@ export function LeagueHomeMockup() {
};
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box
as={motion.div}
variants={containerVariants}
@@ -90,44 +88,27 @@ export function LeagueHomeMockup() {
<Stack gap={{ base: 1.5, sm: 3, md: 4, lg: 6 }}>
<Box as={motion.div} variants={itemVariants}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} mb={{ base: 1, sm: 1.5, md: 2 }}>
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/50" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" shadow="0_0_20px_rgba(25,140,255,0.3)">
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="none" border borderWidth="1px" borderColor="primary-accent/50" bg="panel-gray" display="flex" alignItems="center" justifyContent="center" className="relative">
<Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏆</Text>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box>
<Box>
<Heading level={2} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={0.5}>Super GT Championship</Heading>
<Heading level={2} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={0.5} className="uppercase tracking-widest">Super GT Championship</Heading>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '9px' }}
color="text-gray-400"
color="text-gray-500"
font="mono"
className="uppercase tracking-widest"
>
Season 3 Round 8/12
SEASON 3 ROUND 8/12
</Text>
</Box>
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
color="text-white"
opacity={0.5}
mt={{ base: 1, sm: 1.5, md: 2 }}
block
>
Your league&apos;s dedicated home page
</Text>
</Box>
<Box as={motion.div} variants={itemVariants}>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} block>Upcoming Races</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
color="text-white"
opacity={0.5}
mb={{ base: 1.5, sm: 2, md: 3 }}
block
>
Calendar automatically synced from iRacing
</Text>
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-500" mb={{ base: 1.5, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Upcoming Races</Text>
<Stack gap={{ base: 1.5, sm: 2, md: 3 }}>
{[1, 2, 3].map((i) => (
<Box
@@ -137,25 +118,23 @@ export function LeagueHomeMockup() {
display="flex"
alignItems="center"
gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
bg="bg-iron-gray"
rounded="lg"
bg="panel-gray/40"
rounded="none"
p={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
border
borderColor="border-charcoal-outline"
shadow="inset_0_1px_2px_rgba(0,0,0,0.2)"
borderColor="border-gray/50"
whileHover={shouldReduceMotion ? {} : {
y: -2,
boxShadow: '0 4px 24px rgba(0,0,0,0.4), 0 0 20px rgba(25,140,255,0.3)',
x: 4,
borderColor: '#198CFF50',
transition: { duration: 0.15 }
}}
transition={{ type: 'spring', stiffness: 200, damping: 20 }}
>
<Box h={{ base: 6, sm: 7, md: 8, lg: 10 }} w={{ base: 6, sm: 7, md: 8, lg: 10 }} bg="bg-charcoal-outline" rounded border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
<Box h={{ base: 6, sm: 7, md: 8, lg: 10 }} w={{ base: 6, sm: 7, md: 8, lg: 10 }} bg="graphite-black" rounded="none" border borderColor="primary-accent/20" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }}>🏁</Text>
</Box>
<Box flexGrow={1}>
<Box h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} w={{ base: 20, sm: 24, md: 28, lg: 32 }} bg="bg-white/10" rounded="sm" mb={{ base: 1, sm: 1.5, md: 2 }} />
<Box h={{ base: 1, sm: 1.5, md: 2, lg: 2.5 }} w={{ base: 12, sm: 16, md: 20, lg: 24 }} bg="bg-white/5" rounded="sm" font="mono" />
<Box h="1.5" w={i === 1 ? "32" : "24"} bg="white/10" rounded="none" mb={{ base: 1, sm: 1.5, md: 2 }} />
<Box h="1" w="16" bg="white/5" rounded="none" />
</Box>
{i === 1 && (
<Box
@@ -163,19 +142,11 @@ export function LeagueHomeMockup() {
position="absolute"
right="4"
animate={shouldReduceMotion ? {} : {
scale: [1, 1.2, 1],
boxShadow: [
'0 0 20px rgba(25,140,255,0.3)',
'0 0 32px rgba(67,201,230,0.4)',
'0 0 20px rgba(25,140,255,0.3)'
]
opacity: [1, 0.4, 1]
}}
transition={{ duration: 2, repeat: Infinity }}
>
<Box w={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} bg="bg-primary-blue" rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
className="shadow-glow"
/>
<Box w="1.5" h="1.5" bg="primary-accent" rounded="full" />
</Box>
)}
</Box>
@@ -184,28 +155,18 @@ export function LeagueHomeMockup() {
</Box>
<Box as={motion.div} variants={itemVariants}>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} block>Recent Results</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
color="text-white"
opacity={0.5}
mb={{ base: 1.5, sm: 2, md: 3 }}
block
>
Results appear instantly after each race
</Text>
<Box bg="bg-iron-gray" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} border borderColor="border-charcoal-outline" shadow="0_4px_24px_rgba(0,0,0,0.4)">
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }} mb={{ base: 1.5, sm: 2, md: 3 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-charcoal-outline">
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 5, sm: 6, md: 8 }} bg="bg-white/10" rounded="sm" font="mono" />
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} flexGrow={1} bg="bg-white/10" rounded="sm" />
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-white/10" rounded="sm" font="mono" />
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-500" mb={{ base: 1.5, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Recent Results</Text>
<Box bg="panel-gray/20" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} border borderColor="border-gray/50">
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }} mb={{ base: 1.5, sm: 2, md: 3 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-gray/30">
<Box h="1" w="6" bg="white/10" rounded="none" />
<Box h="1" flexGrow={1} bg="white/10" rounded="none" />
<Box h="1" w="10" bg="white/10" rounded="none" />
</Box>
{[1, 2].map((i) => (
<Box key={i} display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }} py={{ base: 1, sm: 1.5, md: 2 }}>
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 5, sm: 6, md: 8 }} bg="bg-white/5" rounded="sm" font="mono" />
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} flexGrow={1} bg="bg-white/5" rounded="sm" />
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-performance-green/20" rounded="sm" textAlign="center" font="mono" color="text-performance-green" />
<Box h="1" w="6" bg="white/5" rounded="none" />
<Box h="1" flexGrow={1} bg="white/5" rounded="none" />
<Box h="1" w="10" bg={i === 1 ? "success-green/20" : "white/5"} rounded="none" />
</Box>
))}
</Box>

View File

@@ -30,7 +30,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
return (
<div className="relative w-full h-full scale-60 sm:scale-70 md:scale-85 lg:scale-95 max-w-[85vw] mx-auto my-4 sm:my-0" style={{ perspective: '1200px' }}>
<div
className="absolute rounded-lg bg-iron-gray/80 border border-charcoal-outline"
className="absolute rounded-none bg-panel-gray/80 border border-border-gray/50"
style={{
rotate: `${rotation1}deg`,
zIndex: 1,
@@ -44,7 +44,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/>
<div
className="absolute rounded-lg bg-iron-gray/90 border border-charcoal-outline"
className="absolute rounded-none bg-panel-gray/90 border border-border-gray/50"
style={{
rotate: `${rotation2}deg`,
zIndex: 2,
@@ -58,7 +58,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/>
<div
className="relative z-10 w-full h-full rounded-lg overflow-hidden"
className="relative z-10 w-full h-full rounded-none overflow-hidden border border-border-gray/30"
style={{
boxShadow: '0 20px 60px rgba(0,0,0,0.45)',
}}
@@ -73,7 +73,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
return (
<div className="relative w-full h-full scale-60 sm:scale-70 md:scale-85 lg:scale-95 max-w-[85vw] mx-auto my-4 sm:my-0" style={{ perspective: '1200px' }}>
<motion.div
className="absolute rounded-lg bg-iron-gray/80 border border-charcoal-outline"
className="absolute rounded-none bg-panel-gray/80 border border-border-gray/50"
style={{
rotate: `${rotation1}deg`,
zIndex: 1,
@@ -89,7 +89,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/>
<motion.div
className="absolute rounded-lg bg-iron-gray/90 border border-charcoal-outline"
className="absolute rounded-none bg-panel-gray/90 border border-border-gray/50"
style={{
rotate: `${rotation2}deg`,
zIndex: 2,
@@ -105,7 +105,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/>
<motion.div
className="relative z-10 w-full h-full rounded-lg overflow-hidden"
className="relative z-10 w-full h-full rounded-none overflow-hidden border border-border-gray/30"
style={{
boxShadow: '0 20px 60px rgba(0,0,0,0.45)',
}}
@@ -129,12 +129,12 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
transition={{ duration: 0.4, delay: 0.2 }}
>
<motion.div
className="absolute inset-0 pointer-events-none rounded-lg"
className="absolute inset-0 pointer-events-none rounded-none"
whileHover={
shouldReduceMotion
? {}
: {
boxShadow: '0 0 40px rgba(25, 140, 255, 0.4)',
boxShadow: '0 0 40px rgba(25, 140, 255, 0.2)',
transition: { duration: 0.2 },
}
}

View File

@@ -21,35 +21,32 @@ export function ProtestWorkflowMockup() {
{
name: 'Submit',
status: 'pending',
color: 'charcoal-outline',
icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z'
},
{
name: 'Review',
status: 'active',
color: 'warning-amber',
icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4'
},
{
name: 'Resolve',
status: 'resolved',
color: 'performance-green',
icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'
},
];
const getStatusColor = (status: string) => {
const getStatusStyles = (status: string) => {
switch (status) {
case 'pending': return 'bg-charcoal-outline border-charcoal-outline text-gray-500';
case 'active': return 'bg-warning-amber/20 border-warning-amber text-warning-amber';
case 'resolved': return 'bg-performance-green/20 border-performance-green text-performance-green';
default: return 'bg-charcoal-outline border-charcoal-outline text-gray-500';
case 'pending': return 'bg-panel-gray border-gray-700 text-gray-600';
case 'active': return 'bg-warning-amber/10 border-warning-amber text-warning-amber';
case 'resolved': return 'bg-success-green/10 border-success-green text-success-green';
default: return 'bg-panel-gray border-gray-700 text-gray-600';
}
};
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" overflow="hidden" p={3} display="flex" flexDirection="col" justifyContent="center" gap={4}>
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" overflow="hidden" p={3} display="flex" flexDirection="col" justifyContent="center" gap={4}>
<Box display="flex" alignItems="center" justifyContent="center" gap={3}>
{steps.map((step, i) => (
<Box key={step.name} display="flex" alignItems="center">
@@ -57,41 +54,36 @@ export function ProtestWorkflowMockup() {
<Box
w="10"
h="10"
rounded="lg"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
mb={1}
border
borderWidth="2px"
// eslint-disable-next-line gridpilot-rules/component-classification
className={getStatusColor(step.status)}
borderWidth="1px"
className={getStatusStyles(step.status)}
>
<Box as="svg" w="5" h="5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={step.icon} />
</Box>
</Box>
<Text size="xs" color="text-white" opacity={0.7} textAlign="center">{step.name}</Text>
<Text size="xs" color="text-white" weight="bold" textAlign="center" className="uppercase tracking-widest">{step.name}</Text>
</Box>
{i < steps.length - 1 && (
<Box as="svg" w="4" h="4" mx={1} viewBox="0 0 24 24" fill="none">
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#43C9E6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#198CFF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</Box>
)}
</Box>
))}
</Box>
<Box position="relative" h="1" bg="bg-charcoal-outline" rounded="full" overflow="hidden">
<Box position="relative" h="1" bg="white/5" rounded="none" overflow="hidden">
<Box
position="absolute"
insetY="0"
left="0"
bg="bg-gradient-to-r from-neon-aqua to-primary-blue"
rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
bg="primary-accent"
style={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
/>
</Box>
@@ -100,21 +92,19 @@ export function ProtestWorkflowMockup() {
}
const stepVariants = {
hidden: { opacity: 0, scale: shouldReduceMotion ? 1 : 0.8 },
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
visible: (i: number) => ({
opacity: 1,
scale: 1,
y: 0,
transition: {
delay: shouldReduceMotion ? 0 : i * 0.2,
type: 'spring' as const,
stiffness: 200,
damping: 20
duration: 0.4
}
})
};
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" overflow="hidden" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} display="flex" flexDirection="col" justifyContent="center" gap={{ base: 2, sm: 4, md: 6, lg: 8 }}>
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" overflow="hidden" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} display="flex" flexDirection="col" justifyContent="center" gap={{ base: 2, sm: 4, md: 6, lg: 8 }}>
<Box display="flex" flexDirection={{ base: 'col', md: 'row' }} alignItems="center" justifyContent="center" gap={{ base: 2, sm: 3, md: 4 }}>
{steps.map((step, i) => (
<Box key={step.name} display="flex" alignItems="center" flexShrink={0}>
@@ -134,84 +124,48 @@ export function ProtestWorkflowMockup() {
position="relative"
w={{ base: 8, sm: 10, md: 12, lg: 14 }}
h={{ base: 8, sm: 10, md: 12, lg: 14 }}
rounded="lg"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
mb={{ base: 1, sm: 1.5, md: 2 }}
border
borderWidth="2px"
// eslint-disable-next-line gridpilot-rules/component-classification
className={getStatusColor(step.status)}
borderWidth="1px"
className={getStatusStyles(step.status)}
whileHover={shouldReduceMotion ? {} : {
scale: 1.1,
boxShadow: step.status === 'active'
? '0 0 32px rgba(255,197,86,0.4)'
: step.status === 'resolved'
? '0 0 32px rgba(111,227,122,0.4)'
: '0 0 20px rgba(34,38,42,0.4)',
scale: 1.05,
borderColor: '#198CFF',
transition: { duration: 0.2 }
}}
transition={{ type: 'spring', stiffness: 300, damping: 15 }}
>
<Box as="svg" w={{ base: 4, sm: 5, md: 6, lg: 7 }} h={{ base: 4, sm: 5, md: 6, lg: 7 }} fill="none" viewBox="0 0 24 24" stroke="currentColor">
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={step.icon} />
</Box>
{step.status === 'active' && (
<Box
as={motion.div}
position="absolute"
inset="0"
rounded="lg"
animate={shouldReduceMotion ? {} : {
boxShadow: [
'0 0 20px rgba(25,140,255,0.3)',
'0 0 32px rgba(255,197,86,0.5)',
'0 0 20px rgba(25,140,255,0.3)'
]
}}
transition={{ duration: 2, repeat: Infinity }}
/>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="warning-amber" />
)}
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.7}
weight="bold"
textAlign="center"
mb={0.5}
className="uppercase tracking-widest"
>
{step.name}
</Text>
<Box
as={motion.div}
h={{ base: 0.5, sm: 1, md: 1.5 }}
w={{ base: 6, sm: 8, md: 10, lg: 12 }}
rounded="sm"
bg={
step.status === 'pending' ? 'bg-charcoal-outline' :
step.status === 'active' ? 'bg-warning-amber/30' :
'bg-performance-green/30'
}
animate={shouldReduceMotion ? {} : step.status === 'active' ? {
opacity: [0.5, 1, 0.5]
} : {}}
transition={{ duration: 2, repeat: Infinity }}
/>
</Box>
{i < steps.length - 1 && (
<Box
// eslint-disable-next-line gridpilot-rules/component-classification
className="hidden md:block"
position="relative"
ml={1}
ml={2}
mr={2}
>
<Box as="svg" w={{ base: 3, sm: 4, md: 5 }} h={{ base: 3, sm: 4, md: 5 }} viewBox="0 0 24 24" fill="none">
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#43C9E6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#198CFF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" opacity={0.5} />
</Box>
</Box>
)}
@@ -225,9 +179,9 @@ export function ProtestWorkflowMockup() {
animate={{ opacity: 1, scaleX: 1 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.8, duration: 0.6 }}
position="relative"
h={{ base: 0.5, md: 1 }}
bg="bg-charcoal-outline"
rounded="full"
h="1"
bg="white/5"
rounded="none"
overflow="hidden"
>
<Box
@@ -235,8 +189,7 @@ export function ProtestWorkflowMockup() {
position="absolute"
insetY="0"
left="0"
bg="bg-gradient-to-r from-neon-aqua to-primary-blue"
rounded="full"
bg="primary-accent"
initial={{ width: '0%' }}
animate={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
transition={{ duration: 0.5, ease: 'easeOut' }}

View File

@@ -26,7 +26,7 @@ export function RaceHistoryMockup() {
// Simple, elegant mobile version - just the core story
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Box
as={motion.div}
initial="hidden"
@@ -40,14 +40,14 @@ export function RaceHistoryMockup() {
<Stack gap={4}>
{/* Race result - clean and simple */}
<Box as={motion.div} variants={itemVariants}>
<Box bg="bg-iron-gray/60" rounded="xl" p={4} border borderWidth="2px" borderColor="border-primary-blue/40">
<Box bg="panel-gray/60" rounded="none" p={4} border borderWidth="1px" borderColor="primary-accent/40">
<Box display="flex" alignItems="center" gap={3}>
<Box h="14" w="14" bg="bg-primary-blue/20" rounded="lg" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
<Text size="2xl" weight="bold" color="text-primary-blue">P3</Text>
<Box h="14" w="14" bg="primary-accent/10" rounded="none" display="flex" alignItems="center" justifyContent="center" flexShrink={0} border borderColor="primary-accent/30">
<Text size="2xl" weight="bold" color="text-primary-accent" font="mono">P3</Text>
</Box>
<Box>
<Text size="base" weight="semibold" color="text-white" block>Watkins Glen</Text>
<Text size="xs" color="text-white" opacity={0.6} block>GT3 Sprint</Text>
<Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">Watkins Glen</Text>
<Text size="xs" color="text-gray-500" block font="mono">GT3 SPRINT</Text>
</Box>
</Box>
</Box>
@@ -55,30 +55,32 @@ export function RaceHistoryMockup() {
{/* Simple arrow */}
<Box as={motion.div} variants={itemVariants} display="flex" justifyContent="center">
<Text color="text-primary-blue" size="2xl"></Text>
<Text color="text-primary-accent" size="2xl"></Text>
</Box>
{/* Updates - minimal */}
<Box as={motion.div} variants={itemVariants}>
<Box bg="bg-iron-gray/40" rounded="xl" p={3} border borderColor="border-charcoal-outline">
<Box bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
<Stack gap={2}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.7}
color="text-gray-500"
textAlign="center"
mb={2}
block
font="mono"
uppercase
className="tracking-widest"
>
Profile Updated
</Text>
<Box display="flex" gap={2}>
<Box flexGrow={1} bg="bg-deep-graphite/50" rounded="sm" py={2} textAlign="center" border borderColor="border-primary-blue/30">
<Text size="xs" color="text-primary-blue" weight="semibold" block>Stats </Text>
<Box flexGrow={1} bg="graphite-black" rounded="none" py={2} textAlign="center" border borderColor="primary-accent/30">
<Text size="xs" color="text-primary-accent" weight="bold" block font="mono">STATS </Text>
</Box>
<Box flexGrow={1} bg="bg-deep-graphite/50" rounded="sm" py={2} textAlign="center" border borderColor="border-performance-green/30">
<Text size="xs" color="text-performance-green" weight="semibold" block>+12</Text>
<Box flexGrow={1} bg="graphite-black" rounded="none" py={2} textAlign="center" border borderColor="success-green/30">
<Text size="xs" color="text-success-green" weight="bold" block font="mono">+12</Text>
</Box>
</Box>
</Stack>
@@ -92,7 +94,7 @@ export function RaceHistoryMockup() {
// Desktop version - richer with more updates
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
<Box
as={motion.div}
initial="hidden"
@@ -104,60 +106,43 @@ export function RaceHistoryMockup() {
<Stack gap={{ base: 2, sm: 3, md: 4, lg: 5 }}>
{/* Race Result Card - Enhanced */}
<Box as={motion.div} variants={itemVariants}>
<Box bg="bg-iron-gray" rounded="lg" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderWidth="2px" borderColor="border-primary-blue/40">
<Box bg="panel-gray/40" rounded="none" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderWidth="1px" borderColor="primary-accent/40">
<Box display="flex" alignItems="center" gap={{ base: 2, sm: 3, md: 4 }}>
<Box position="relative" h={{ base: 12, sm: 14, md: 16, lg: 20 }} w={{ base: 12, sm: 14, md: 16, lg: 20 }} bg="bg-charcoal-outline" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/30" overflow="hidden" flexShrink={0}>
<Box position="absolute" inset="0" bg="bg-gradient-to-br from-primary-blue/20 to-performance-green/20" />
<Box position="relative" h={{ base: 12, sm: 14, md: 16, lg: 20 }} w={{ base: 12, sm: 14, md: 16, lg: 20 }} bg="graphite-black" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" overflow="hidden" flexShrink={0}>
<Box position="absolute" inset="0" bg="gradient-to-br from-primary-accent/10 to-success-green/10" />
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center">
<Text color="text-white" size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold">P3</Text>
<Text color="text-white" size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold" font="mono">P3</Text>
</Box>
</Box>
<Box flexGrow={1} minWidth="0">
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }} mb={1}>
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }}>🏁</Text>
<Heading level={3} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="semibold" color="text-white" truncate>Watkins Glen</Heading>
<Box w="1" h="4" bg="primary-accent" />
<Heading level={3} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" truncate className="uppercase tracking-widest">Watkins Glen</Heading>
</Box>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} color="text-white" opacity={0.6} mb={1} block>GT3 Sprint Race</Text>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} color="text-gray-500" mb={1} block font="mono">GT3 SPRINT RACE</Text>
<Box display="flex" alignItems="center" gap={{ base: 2, sm: 3, md: 4 }}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
color="text-gray-600"
font="mono"
>
24 drivers
24 DRIVERS
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
color="text-gray-600"
>
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
color="text-gray-600"
font="mono"
>
45 min
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
>
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
>
Just finished
45 MIN
</Text>
</Box>
</Box>
@@ -178,12 +163,13 @@ export function RaceHistoryMockup() {
}}
transition={{ duration: 2, repeat: Infinity }}
>
<Text color="text-primary-blue" size={{ base: '2xl', sm: '3xl', md: '4xl' }}></Text>
<Text color="text-primary-accent" size={{ base: '2xl', sm: '3xl', md: '4xl' }}></Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-primary-blue"
opacity={0.7}
color="text-primary-accent"
weight="bold"
className="uppercase tracking-widest"
>
Auto-sync
</Text>
@@ -192,27 +178,28 @@ export function RaceHistoryMockup() {
{/* Profile Updates Grid - More detailed */}
<Box as={motion.div} variants={itemVariants}>
<Box bg="bg-iron-gray/80" rounded="lg" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderColor="border-charcoal-outline">
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.8} textAlign="center" mb={{ base: 2, sm: 3, md: 4 }} block>
<Box bg="panel-gray/20" rounded="none" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderColor="border-gray/50">
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-gray-500" textAlign="center" mb={{ base: 2, sm: 3, md: 4 }} block className="uppercase tracking-[0.2em]">
Profile Updates
</Text>
<Box display="grid" gridCols={2} gap={{ base: 2, sm: 3, md: 4 }}>
{/* Career Stats Update */}
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-primary-blue/30">
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="primary-accent/30">
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.7}
color="text-gray-500"
weight="bold"
className="uppercase tracking-widest"
>
Career Stats
</Text>
<Box
as={motion.span}
color="text-performance-green"
weight="semibold"
color="text-success-green"
weight="bold"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
animate={shouldReduceMotion ? {} : { scale: [1, 1.2, 1] }}
@@ -227,26 +214,28 @@ export function RaceHistoryMockup() {
color="text-white"
opacity={0.5}
block
font="mono"
>
Wins: 24 25
WINS: 24 25
</Text>
</Box>
{/* Rating Update */}
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-performance-green/30">
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="success-green/30">
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.7}
color="text-gray-500"
weight="bold"
className="uppercase tracking-widest"
>
Rating
</Text>
<Box
as={motion.span}
color="text-performance-green"
weight="semibold"
color="text-success-green"
weight="bold"
font="mono"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -262,26 +251,28 @@ export function RaceHistoryMockup() {
color="text-white"
opacity={0.5}
block
font="mono"
>
1342 1354
</Text>
</Box>
{/* Season Points Update */}
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-warning-amber/30">
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="warning-amber/30">
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.7}
color="text-gray-500"
weight="bold"
className="uppercase tracking-widest"
>
Season
</Text>
<Box
as={motion.span}
color="text-warning-amber"
weight="semibold"
weight="bold"
font="mono"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -297,26 +288,28 @@ export function RaceHistoryMockup() {
color="text-white"
opacity={0.5}
block
font="mono"
>
248 266 pts
248 266 PTS
</Text>
</Box>
{/* Team Points Update */}
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-neon-aqua/30">
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="telemetry-aqua/30">
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.7}
color="text-gray-500"
weight="bold"
className="uppercase tracking-widest"
>
Team
</Text>
<Box
as={motion.span}
color="text-neon-aqua"
weight="semibold"
color="text-telemetry-aqua"
weight="bold"
font="mono"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -332,8 +325,9 @@ export function RaceHistoryMockup() {
color="text-white"
opacity={0.5}
block
font="mono"
>
Contributing
CONTRIBUTING
</Text>
</Box>
</Box>

View File

@@ -18,30 +18,30 @@ export function SimPlatformMockup() {
// Simple mobile version - just the essence of cross-platform
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
<Stack gap={3} w="full">
{/* Active Platform - Clean */}
<Box bg="bg-iron-gray/60" border borderWidth="2px" borderColor="border-primary-blue" rounded="xl" p={4} position="relative">
<Box bg="panel-gray/60" border borderWidth="1px" borderColor="primary-accent" rounded="none" p={4} position="relative">
<Box position="absolute" top="3" right="3">
<Box w="2" h="2" rounded="full" bg="bg-performance-green"
<Box w="1.5" h="1.5" rounded="full" bg="success-green"
// eslint-disable-next-line gridpilot-rules/component-classification
className="animate-pulse"
/>
</Box>
<Box display="flex" alignItems="center" gap={3}>
<Box w="12" h="12" rounded="lg" bg="bg-primary-blue/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
<Text size="2xl" weight="bold" color="text-primary-blue">iR</Text>
<Box w="12" h="12" rounded="none" bg="primary-accent/10" display="flex" alignItems="center" justifyContent="center" flexShrink={0} border borderColor="primary-accent/30">
<Text size="2xl" weight="bold" color="text-primary-accent" font="mono">iR</Text>
</Box>
<Box>
<Text size="base" weight="semibold" color="text-white" block>iRacing</Text>
<Text size="xs" color="text-performance-green" block>Active</Text>
<Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">iRacing</Text>
<Text size="xs" color="text-success-green" block font="mono">ACTIVE</Text>
</Box>
</Box>
</Box>
{/* Simple "more coming" indicator */}
<Box bg="bg-iron-gray/30" rounded="xl" p={3} border borderColor="border-charcoal-outline/50">
<Text size="xs" textAlign="center" color="text-slate-500" block>More platforms coming</Text>
<Box bg="panel-gray/20" rounded="none" p={3} border borderStyle="dashed" borderColor="border-gray/30">
<Text size="xs" textAlign="center" color="text-gray-600" block font="mono" uppercase tracking-widest>More platforms coming</Text>
</Box>
</Stack>
</Box>
@@ -51,150 +51,161 @@ export function SimPlatformMockup() {
// Desktop version
return (
<Box position="relative" fullWidth maxWidth="3xl" mx="auto">
<Box bg="bg-iron-gray" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} shadow="2xl">
<Box bg="panel-gray/40" border borderColor="border-gray/50" rounded="none" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} shadow="2xl">
<Stack gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
<Box display="flex" alignItems="center" justifyContent="between" pb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderBottom borderColor="border-charcoal-outline">
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-300">Platform Support</Text>
<Box display="flex" alignItems="center" justifyContent="between" pb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderBottom borderColor="border-gray/30">
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-400" className="uppercase tracking-widest">Platform Support</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-500"
color="text-gray-600"
font="mono"
>
Active: 1 | Planned: 3
ACTIVE: 1 | PLANNED: 3
</Text>
</Box>
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={{ base: 1.5, sm: 2, md: 3 }}>
{/* iRacing - Active */}
<Box bg="bg-deep-graphite" border borderWidth="2px" borderColor="border-primary-blue" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} position="relative" overflow="hidden">
<Box bg="graphite-black" border borderWidth="1px" borderColor="primary-accent/40" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} position="relative" overflow="hidden">
<Box position="absolute" top={{ base: 1, sm: 1.5, md: 2 }} right={{ base: 1, sm: 1.5, md: 2 }}>
<Box w={{ base: 1, sm: 1.5, md: 2 }} h={{ base: 1, sm: 1.5, md: 2 }} rounded="full" bg="bg-performance-green"
<Box w="1.5" h="1.5" rounded="full" bg="success-green"
// eslint-disable-next-line gridpilot-rules/component-classification
className="animate-pulse"
/>
</Box>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-primary-blue/10" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-primary-blue">iR</Text>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="primary-accent/10" display="flex" alignItems="center" justifyContent="center" border borderColor="primary-accent/20">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-primary-accent" font="mono">iR</Text>
</Box>
<Box>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" block>iRacing</Text>
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-white" block className="uppercase tracking-widest">iRacing</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-performance-green"
color="text-success-green"
block
font="mono"
>
Active
ACTIVE
</Text>
</Box>
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-400"
color="text-gray-500"
mt={{ base: 1.5, sm: 2, md: 3 }}
block
font="mono"
>
Full integration
FULL INTEGRATION
</Text>
</Box>
{/* ACC - Future */}
<Box bg="bg-deep-graphite" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.4}>
<Box bg="panel-gray/20" border borderColor="border-gray/30" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.6}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-slate-700/20" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-slate-600">AC</Text>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="gray-800/20" display="flex" alignItems="center" justifyContent="center" border borderColor="gray-800/30">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">AC</Text>
</Box>
<Box>
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-500" block>ACC</Text>
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-600" block className="uppercase tracking-widest">ACC</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-600"
color="text-gray-700"
block
font="mono"
>
Planned
PLANNED
</Text>
</Box>
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-600"
color="text-gray-700"
mt={{ base: 1.5, sm: 2, md: 3 }}
block
font="mono"
>
Coming later
COMING LATER
</Text>
</Box>
{/* rFactor 2 - Future */}
<Box bg="bg-deep-graphite" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.4}>
<Box bg="panel-gray/20" border borderColor="border-gray/30" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.6}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-slate-700/20" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-slate-600">rF</Text>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="gray-800/20" display="flex" alignItems="center" justifyContent="center" border borderColor="gray-800/30">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">rF</Text>
</Box>
<Box>
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-500" block>rFactor 2</Text>
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-600" block className="uppercase tracking-widest">rFactor 2</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-600"
color="text-gray-700"
block
font="mono"
>
Planned
PLANNED
</Text>
</Box>
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-600"
color="text-gray-700"
mt={{ base: 1.5, sm: 2, md: 3 }}
block
font="mono"
>
Coming later
COMING LATER
</Text>
</Box>
{/* LMU - Future */}
<Box bg="bg-deep-graphite" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.4}>
<Box bg="panel-gray/20" border borderColor="border-gray/30" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.6}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-slate-700/20" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-slate-600">LM</Text>
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="gray-800/20" display="flex" alignItems="center" justifyContent="center" border borderColor="gray-800/30">
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">LM</Text>
</Box>
<Box>
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-500" block>Le Mans Ult.</Text>
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-600" block className="uppercase tracking-widest">Le Mans Ult.</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-600"
color="text-gray-700"
block
font="mono"
>
Planned
PLANNED
</Text>
</Box>
</Box>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-600"
color="text-gray-700"
mt={{ base: 1.5, sm: 2, md: 3 }}
block
font="mono"
>
Coming later
COMING LATER
</Text>
</Box>
</Box>
<Box pt={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderTop borderColor="border-charcoal-outline">
<Box pt={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderTop borderStyle="dashed" borderColor="border-gray/30">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-slate-500"
color="text-gray-600"
textAlign="center"
block
font="mono"
className="uppercase tracking-widest"
>
Your identity stays with you across platforms
</Text>

View File

@@ -17,30 +17,30 @@ export function StandingsTableMockup() {
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
<Box mb={3}>
<Box display="flex" alignItems="center" gap={2} pb={2} borderBottom borderColor="border-charcoal-outline">
<Box display="flex" alignItems="center" gap={2} pb={2} borderBottom borderColor="border-gray/30">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
color="text-gray-600"
>
#
POS
</Text>
<Text size="xs" flexGrow={1} weight="semibold" color="text-white">Driver</Text>
<Text size="xs" flexGrow={1} weight="bold" color="text-gray-500" className="uppercase tracking-widest">Driver</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
color="text-gray-600"
>
Pts
PTS
</Text>
</Box>
</Box>
<Stack gap={2}>
<Stack gap={1}>
{[1, 2, 3, 4, 5].map((i) => (
<Box
key={i}
@@ -49,49 +49,41 @@ export function StandingsTableMockup() {
gap={2}
py={2}
px={2}
rounded="lg"
rounded="none"
border
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/10 to-iron-gray' : 'bg-iron-gray'}
borderColor={i <= 3 ? 'border-performance-green/20' : 'border-charcoal-outline'}
bg={i <= 3 ? 'panel-gray/40' : 'transparent'}
borderColor={i <= 3 ? 'primary-accent/20' : 'border-gray/20'}
>
<Box
h="7"
w="7"
rounded="full"
h="6"
w="6"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
weight="semibold"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
bg={i <= 3 ? 'bg-primary-blue' : 'bg-charcoal-outline'}
color={i <= 3 ? 'text-white' : 'text-gray-400'}
shadow={i <= 3 ? '0_0_12px_rgba(25,140,255,0.3)' : undefined}
style={{ fontSize: '10px' }}
bg={i <= 3 ? 'primary-accent' : 'panel-gray'}
color={i <= 3 ? 'text-white' : 'text-gray-500'}
font="mono"
weight="bold"
>
{i}
</Box>
<Box flexGrow={1} display="flex" alignItems="center" gap={2}>
<Box h="5" w="5" rounded="full" bg="bg-charcoal-outline" border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
>
🏎
</Text>
<Box h="1.5" fullWidth maxWidth="80px" bg="white/10" rounded="none" />
</Box>
<Box h="2.5" fullWidth maxWidth="100px" bg="bg-white/10" rounded="sm" />
</Box>
<Box position="relative" w="16" h="5" bg="bg-charcoal-outline" rounded="sm" border borderColor="border-primary-blue/20" overflow="hidden">
<Box position="relative" w="12" h="4" bg="graphite-black" rounded="none" border borderColor="border-gray/30" overflow="hidden">
<Box
position="absolute"
insetY="0"
left="0"
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20' : 'bg-gradient-to-r from-iron-gray to-charcoal-outline'}
bg={i <= 3 ? 'primary-accent/40' : 'gray-700/40'}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ width: `${100 - (i - 1) * 15}%` }}
/>
<Box position="relative" h="full" display="flex" alignItems="center" justifyContent="center">
<Text size="xs" font="mono" weight="semibold" color="text-white">
<Text size="xs" font="mono" weight="bold" color="text-white">
{300 - i * 20}
</Text>
</Box>
@@ -104,47 +96,48 @@ export function StandingsTableMockup() {
}
const getRowAnimation = (i: number) => ({
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
hidden: { opacity: 0, x: shouldReduceMotion ? 0 : -10 },
visible: {
opacity: 1,
y: 0,
x: 0,
transition: {
delay: shouldReduceMotion ? 0 : i * 0.05,
type: 'spring' as const,
stiffness: 300,
damping: 24
duration: 0.3
}
}
});
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
<Box mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
color="text-gray-600"
mb={{ base: 1.5, sm: 2, md: 3 }}
block
font="mono"
uppercase
className="tracking-widest"
>
Real-time standings updated after every race
</Text>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-charcoal-outline">
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-gray/30">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
color="text-gray-500"
>
#
POS
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
flexGrow={1}
weight="semibold"
color="text-white"
weight="bold"
color="text-gray-400"
className="uppercase tracking-widest"
>
Driver
</Text>
@@ -152,9 +145,8 @@ export function StandingsTableMockup() {
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
// eslint-disable-next-line gridpilot-rules/component-classification
className="hidden md:block"
color="text-gray-500"
className="hidden md:block uppercase tracking-widest"
>
Wins
</Text>
@@ -163,21 +155,16 @@ export function StandingsTableMockup() {
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
color="text-gray-500"
className="uppercase tracking-widest"
>
Points
</Text>
<Text color="text-performance-green"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
>
</Text>
</Box>
</Box>
</Box>
<Stack gap={0.5}>
<Stack gap={1}>
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
<Box
key={i}
@@ -189,18 +176,18 @@ export function StandingsTableMockup() {
display="flex"
alignItems="center"
gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
py={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }}
py={{ base: 1.5, sm: 2, md: 2.5 }}
px={{ base: 1.5, sm: 2, md: 3 }}
rounded="lg"
rounded="none"
border
transition
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/10 to-iron-gray' : 'bg-iron-gray'}
borderColor={i <= 3 ? 'border-performance-green/20' : 'border-charcoal-outline'}
bg={i <= 3 ? 'panel-gray/40' : 'transparent'}
borderColor={i <= 3 ? 'primary-accent/20' : 'border-gray/20'}
onHoverStart={() => !shouldReduceMotion && setHoveredRow(i)}
onHoverEnd={() => setHoveredRow(null)}
whileHover={shouldReduceMotion ? {} : {
scale: 1.01,
boxShadow: '0 0 20px rgba(25,140,255,0.3)',
x: 4,
borderColor: '#198CFF30',
transition: { duration: 0.15 }
}}
>
@@ -208,30 +195,23 @@ export function StandingsTableMockup() {
as={motion.div}
h={{ base: 6, sm: 7 }}
w={{ base: 6, sm: 7 }}
rounded="full"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
weight="semibold"
weight="bold"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
bg={i <= 3 ? 'bg-primary-blue' : 'bg-charcoal-outline'}
color={i <= 3 ? 'text-white' : 'text-gray-400'}
animate={
shouldReduceMotion ? {} : i <= 3 && hoveredRow === i
? { scale: 1.15, boxShadow: '0 0 28px rgba(25,140,255,0.5)' }
: {}
}
style={{ fontSize: '11px' }}
bg={i <= 3 ? 'primary-accent' : 'panel-gray'}
color={i <= 3 ? 'text-white' : 'text-gray-500'}
font="mono"
>
{i}
</Box>
<Box flexGrow={1} display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }}>
<Box h={{ base: 4, sm: 5, md: 6 }} w={{ base: 4, sm: 5, md: 6 }} rounded="full" bg="bg-charcoal-outline" border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'xs', sm: 'sm' }}>🏎</Text>
<Box h="1.5" fullWidth maxWidth={{ base: '80px', sm: '100px', md: '140px' }} bg="white/10" rounded="none" />
</Box>
<Box h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} fullWidth maxWidth={{ base: '80px', sm: '100px', md: '140px' }} bg="bg-white/10" rounded="sm" />
</Box>
<Box h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} w={{ base: 10, sm: 12, md: 16 }} bg="bg-white/5" rounded="sm" font="mono"
<Box h="1" w={{ base: 10, sm: 12, md: 16 }} bg="white/5" rounded="none"
// eslint-disable-next-line gridpilot-rules/component-classification
className="hidden md:block"
/>
@@ -272,13 +252,13 @@ function AnimatedPoints({
const percentage = (points / 300) * 100;
return (
<Box position="relative" w={{ base: 12, sm: 16, md: 20, lg: 24 }} h={{ base: 4, sm: 5, md: 6, lg: 7 }} bg="bg-charcoal-outline" rounded="sm" border borderColor="border-primary-blue/20" overflow="hidden">
<Box position="relative" w={{ base: 12, sm: 16, md: 20, lg: 24 }} h={{ base: 4, sm: 5, md: 6 }} bg="graphite-black" rounded="none" border borderColor="border-gray/30" overflow="hidden">
<Box
as={motion.div}
position="absolute"
insetY="0"
left="0"
bg={position <= 3 ? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20' : 'bg-gradient-to-r from-iron-gray to-charcoal-outline'}
bg={position <= 3 ? 'primary-accent/40' : 'gray-700/40'}
initial={{ width: '0%' }}
animate={{ width: `${percentage}%` }}
transition={{ duration: shouldReduceMotion ? 0 : 0.8, ease: 'easeOut', delay: 0.1 + position * 0.05 }}
@@ -288,7 +268,7 @@ function AnimatedPoints({
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
weight="semibold"
weight="bold"
color="text-white"
>
{shouldReduceMotion ? points : <Box as={motion.span}>{spring}</Box>}

View File

@@ -16,15 +16,15 @@ export function TeamCompetitionMockup() {
setIsMobile(window.innerWidth < 768);
}, []);
const teamColors = ['#198CFF', '#6FE37A', '#FFC556', '#43C9E6', '#9333EA'];
const teamColors = ['#198CFF', '#6FE37A', '#FFBE4D', '#4ED4E0', '#9333EA'];
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
<Stack gap={4}>
<Box>
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Drivers</Text>
<Stack gap={2}>
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Drivers</Text>
<Stack gap={1}>
{[1, 2, 3].map((i) => (
<Box
key={i}
@@ -32,11 +32,11 @@ export function TeamCompetitionMockup() {
display="flex"
alignItems="center"
gap={2}
bg="bg-iron-gray"
rounded="lg"
bg="panel-gray/40"
rounded="none"
p={2}
border
borderColor="border-charcoal-outline"
borderColor="border-gray/20"
overflow="hidden"
>
<Box
@@ -44,54 +44,40 @@ export function TeamCompetitionMockup() {
left="0"
top="0"
bottom="0"
w="1"
// eslint-disable-next-line gridpilot-rules/component-classification
w="0.5"
style={{ backgroundColor: teamColors[i-1] }}
/>
<Box
h="5"
w="5"
rounded="full"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
weight="semibold"
// eslint-disable-next-line gridpilot-rules/component-classification
weight="bold"
style={{
fontSize: '10px',
borderColor: teamColors[i-1],
backgroundColor: `${teamColors[i-1]}20`,
borderWidth: '2px'
backgroundColor: `${teamColors[i-1]}10`,
borderWidth: '1px'
}}
>
<Text color="text-white">{i}</Text>
</Box>
<Box h="6" w="6" rounded="full" display="flex" alignItems="center" justifyContent="center"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ backgroundColor: `${teamColors[i-1]}20`, borderWidth: '1px', borderColor: teamColors[i-1], fontSize: '14px' }}
>
<Text>🏎</Text>
<Text color="text-white" font="mono">{i}</Text>
</Box>
<Box flexGrow={1}>
<Box h="2.5" w="full" bg="bg-white/10" rounded="sm" />
<Box h="1.5" w="full" bg="white/10" rounded="none" />
</Box>
<Box h="3" w="10" bg="bg-charcoal-outline" rounded="sm" display="flex" alignItems="center" justifyContent="center" color="text-white" opacity={0.7}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
/>
<Box h="3" w="10" bg="graphite-black" rounded="none" border borderColor="border-gray/30" />
</Box>
))}
</Stack>
</Box>
<Box h="px"
// eslint-disable-next-line gridpilot-rules/component-classification
className="bg-gradient-to-r from-transparent via-charcoal-outline to-transparent"
/>
<Box h="px" bg="border-gray/30" />
<Box>
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Constructors</Text>
<Stack gap={2}>
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Constructors</Text>
<Stack gap={1}>
{[1, 2, 3].map((i) => (
<Box
key={i}
@@ -99,11 +85,11 @@ export function TeamCompetitionMockup() {
display="flex"
alignItems="center"
gap={2}
bg="bg-iron-gray"
rounded="lg"
bg="panel-gray/40"
rounded="none"
p={2}
border
borderColor="border-charcoal-outline"
borderColor="border-gray/20"
overflow="hidden"
>
<Box
@@ -111,37 +97,16 @@ export function TeamCompetitionMockup() {
left="0"
top="0"
bottom="0"
w="1"
// eslint-disable-next-line gridpilot-rules/component-classification
w="0.5"
style={{ backgroundColor: teamColors[i-1] }}
/>
<Box
h="6"
w="6"
rounded="sm"
display="flex"
alignItems="center"
justifyContent="center"
border
borderWidth="2px"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{
borderColor: teamColors[i-1],
backgroundColor: `${teamColors[i-1]}20`,
fontSize: '14px'
}}
>
<Text>🏁</Text>
</Box>
<Box flexGrow={1}>
<Box h="2.5" w="full" bg="bg-white/10" rounded="sm" mb={1} />
<Box position="relative" h="1.5" bg="bg-charcoal-outline" rounded="full" overflow="hidden">
<Box h="1.5" w="full" bg="white/10" rounded="none" mb={1.5} />
<Box position="relative" h="1" bg="graphite-black" rounded="none" overflow="hidden">
<Box
position="absolute"
insetY="0"
left="0"
rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ backgroundColor: teamColors[i-1], width: `${100 - (i-1) * 15}%` }}
/>
</Box>
@@ -160,11 +125,7 @@ export function TeamCompetitionMockup() {
visible: {
opacity: 1,
x: 0,
transition: {
type: 'spring' as const,
stiffness: 100,
damping: 20
}
transition: { duration: 0.4 }
}
};
@@ -173,30 +134,24 @@ export function TeamCompetitionMockup() {
visible: {
opacity: 1,
x: 0,
transition: {
type: 'spring' as const,
stiffness: 100,
damping: 20
}
transition: { duration: 0.4 }
}
};
const rowVariants = {
hidden: { opacity: 0, scale: shouldReduceMotion ? 1 : 0.95 },
hidden: { opacity: 0, x: shouldReduceMotion ? 0 : -10 },
visible: (i: number) => ({
opacity: 1,
scale: 1,
x: 0,
transition: {
delay: shouldReduceMotion ? 0 : 0.3 + i * 0.05,
type: 'spring' as const,
stiffness: 300,
damping: 25
duration: 0.3
}
})
};
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
<Box display="grid" gridCols={2} gap={{ base: 2, sm: 3, md: 4, lg: 6 }} fullHeight>
<Box
as={motion.div}
@@ -205,17 +160,10 @@ export function TeamCompetitionMockup() {
animate="visible"
position="relative"
>
<Box h={{ base: 3, sm: 4, md: 5 }} w={{ base: 16, sm: 20, md: 24 }} bg="bg-white/10" rounded="sm" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} display="flex" alignItems="center" justifyContent="center">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
weight="semibold"
>
Drivers
</Text>
<Box h="6" w="24" bg="panel-gray" rounded="none" mb={4} display="flex" alignItems="center" justifyContent="center" border borderColor="border-gray/30">
<Text size="xs" color="text-gray-400" weight="bold" className="uppercase tracking-widest">DRIVERS</Text>
</Box>
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
<Stack gap={1}>
{[1, 2, 3, 4, 5].map((i) => (
<Box
key={i}
@@ -227,18 +175,18 @@ export function TeamCompetitionMockup() {
position="relative"
display="flex"
alignItems="center"
gap={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }}
bg="bg-iron-gray"
rounded="lg"
p={{ base: 1, sm: 1.5, md: 2, lg: 2.5 }}
gap={3}
bg="panel-gray/20"
rounded="none"
p={2}
border
borderColor="border-charcoal-outline"
borderColor="border-gray/20"
overflow="hidden"
onHoverStart={() => !shouldReduceMotion && setHoveredDriver(i)}
onHoverEnd={() => setHoveredDriver(null)}
whileHover={shouldReduceMotion ? {} : {
scale: 1.02,
boxShadow: `0 0 20px ${teamColors[i-1]}40`,
x: 4,
borderColor: `${teamColors[i-1]}40`,
transition: { duration: 0.15 }
}}
>
@@ -248,65 +196,35 @@ export function TeamCompetitionMockup() {
top="0"
bottom="0"
w="0.5"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ backgroundColor: teamColors[i-1] }}
/>
<Box
h={{ base: 3.5, sm: 4, md: 5 }}
w={{ base: 3.5, sm: 4, md: 5 }}
rounded="full"
h="5"
w="5"
rounded="none"
display="flex"
alignItems="center"
justifyContent="center"
weight="semibold"
weight="bold"
border
borderWidth="2px"
// eslint-disable-next-line gridpilot-rules/component-classification
borderWidth="1px"
style={{
fontSize: '10px',
borderColor: teamColors[i-1],
backgroundColor: `${teamColors[i-1]}20`
backgroundColor: `${teamColors[i-1]}10`
}}
>
<Text color="text-white">{i}</Text>
</Box>
<Box h={{ base: 5, sm: 6, md: 7 }} w={{ base: 5, sm: 6, md: 7 }} rounded="full" display="flex" alignItems="center" justifyContent="center"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ backgroundColor: `${teamColors[i-1]}20`, borderWidth: '1px', borderColor: teamColors[i-1], fontSize: '12px' }}
>
<Text>🏎</Text>
<Text color="text-white" font="mono">{i}</Text>
</Box>
<Box flexGrow={1} minWidth="0">
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w="full" bg="bg-white/10" rounded="sm" />
<Box h="1.5" w="full" bg="white/10" rounded="none" />
</Box>
<Box h={{ base: 2, sm: 2.5, md: 3 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-charcoal-outline" rounded="sm" font="mono" display="flex" alignItems="center" justifyContent="center" color="text-white" opacity={0.7}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
/>
{hoveredDriver === i && (
<Box
as={motion.div}
position="absolute"
inset="0"
pointerEvents="none"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{
background: `linear-gradient(90deg, ${teamColors[i-1]}10 0%, transparent 100%)`
}}
/>
)}
<Box h="3" w="12" bg="graphite-black" rounded="none" border borderColor="border-gray/30" />
</Box>
))}
</Stack>
</Box>
<Box position="absolute" left="1/2" top="8" bottom="8" w="px"
// eslint-disable-next-line gridpilot-rules/component-classification
className="bg-gradient-to-b from-transparent via-charcoal-outline to-transparent backdrop-blur-sm"
/>
<Box
as={motion.div}
variants={rightColumnVariants}
@@ -314,17 +232,10 @@ export function TeamCompetitionMockup() {
animate="visible"
position="relative"
>
<Box h={{ base: 3, sm: 4, md: 5 }} w={{ base: 20, sm: 24, md: 32 }} bg="bg-white/10" rounded="sm" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} display="flex" alignItems="center" justifyContent="center">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
weight="semibold"
>
Constructors
</Text>
<Box h="6" w="32" bg="panel-gray" rounded="none" mb={4} display="flex" alignItems="center" justifyContent="center" border borderColor="border-gray/30">
<Text size="xs" color="text-gray-400" weight="bold" className="uppercase tracking-widest">CONSTRUCTORS</Text>
</Box>
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
<Stack gap={1}>
{[1, 2, 3, 4, 5].map((i) => (
<Box
key={i}
@@ -336,58 +247,37 @@ export function TeamCompetitionMockup() {
position="relative"
display="flex"
alignItems="center"
gap={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }}
bg="bg-iron-gray"
rounded="lg"
p={{ base: 1, sm: 1.5, md: 2, lg: 2.5 }}
gap={3}
bg="panel-gray/20"
rounded="none"
p={2}
border
borderColor="border-charcoal-outline"
borderColor="border-gray/20"
overflow="hidden"
onHoverStart={() => !shouldReduceMotion && setHoveredTeam(i)}
onHoverEnd={() => setHoveredTeam(null)}
whileHover={shouldReduceMotion ? {} : {
scale: 1.02,
boxShadow: `0 0 20px ${teamColors[i-1]}40`,
x: -4,
borderColor: `${teamColors[i-1]}40`,
transition: { duration: 0.15 }
}}
>
<Box
position="absolute"
left="0"
right="0"
top="0"
bottom="0"
w="0.5"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ backgroundColor: teamColors[i-1] }}
/>
<Box
h={{ base: 5, sm: 6, md: 7 }}
w={{ base: 5, sm: 6, md: 7 }}
rounded="sm"
display="flex"
alignItems="center"
justifyContent="center"
border
borderWidth="2px"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{
fontSize: '12px',
borderColor: teamColors[i-1],
backgroundColor: `${teamColors[i-1]}20`
}}
>
<Text>🏁</Text>
</Box>
<Box flexGrow={1} minWidth="0">
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w="full" bg="bg-white/10" rounded="sm" mb={{ base: 0.5, sm: 1, md: 1.5 }} />
<Box position="relative" h={{ base: 0.5, sm: 1, md: 1.5 }} bg="bg-charcoal-outline" rounded="full" overflow="hidden">
<Box h="1.5" w="full" bg="white/10" rounded="none" mb={1.5} />
<Box position="relative" h="1" bg="graphite-black" rounded="none" overflow="hidden">
<Box
as={motion.div}
position="absolute"
insetY="0"
left="0"
rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ backgroundColor: teamColors[i-1] }}
initial={{ width: '0%' }}
animate={{ width: `${100 - (i-1) * 15}%` }}
@@ -395,30 +285,6 @@ export function TeamCompetitionMockup() {
/>
</Box>
</Box>
{i === 3 && (
<Box h={{ base: 3, sm: 3.5, md: 4 }} px={{ base: 0.5, sm: 1, md: 1.5 }} bg="bg-warning-amber/20" rounded="sm" display="flex" alignItems="center" justifyContent="center" color="text-warning-amber" weight="semibold" border borderColor="border-warning-amber/30">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
>
=
</Text>
</Box>
)}
{hoveredTeam === i && (
<Box
as={motion.div}
position="absolute"
inset="0"
pointerEvents="none"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{
background: `linear-gradient(90deg, ${teamColors[i-1]}10 0%, transparent 100%)`
}}
/>
)}
</Box>
))}
</Stack>

View File

@@ -1,4 +1,4 @@
'use client';
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
import { CheckCircle2, LucideIcon } from 'lucide-react';
@@ -43,16 +43,16 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
if (!isMounted) {
return (
<Box position="relative" fullWidth>
<Surface variant="muted" rounded="2xl" border={true} padding={6}>
<Surface variant="muted" rounded="none" border={true} padding={6} bg="panel-gray/40">
<Stack direction="row" justify="between" gap={2}>
{steps.map((step) => (
<Stack key={step.id} align="center" center direction="col">
<Box width="10" height="10" rounded="lg" bg="bg-iron-gray" border={true} borderColor="border-charcoal-outline" display="flex" center mb={2}>
<Box width="10" height="10" rounded="none" bg="graphite-black" border={true} borderColor="border-gray/50" display="flex" center mb={2}>
<Text color={step.color}>
<Icon icon={step.icon} size={4} />
</Text>
</Box>
<Text size="xs" weight="medium" color="text-white">{step.title}</Text>
<Text size="xs" weight="bold" color="text-white" className="uppercase tracking-widest">{step.title}</Text>
</Stack>
))}
</Stack>
@@ -63,15 +63,15 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
return (
<Box position="relative" fullWidth>
<Surface variant="muted" rounded="2xl" border={true} padding={6} overflow="hidden">
<Surface variant="muted" rounded="none" border={true} padding={6} overflow="hidden" bg="panel-gray/40">
{/* Connection Lines */}
<Box position="absolute" top="3.5rem" left="8%" right="8%" display={{ base: 'none', sm: 'block' }}>
<Box height="0.5" bg="bg-charcoal-outline" position="relative">
<Box height="0.5" bg="white/5" position="relative">
<Box
as={motion.div}
position="absolute"
fullHeight
bg="bg-gradient-to-r from-primary-blue to-performance-green"
bg="primary-accent"
initial={{ width: '0%' }}
animate={{ width: `${(activeStep / (steps.length - 1)) * 100}%` }}
transition={{ duration: 0.5, ease: 'easeInOut' }}
@@ -104,35 +104,39 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
as={motion.div}
w={{ base: '10', sm: '12' }}
h={{ base: '10', sm: '12' }}
rounded="lg"
rounded="none"
border={true}
display="flex"
alignItems="center"
justifyContent="center"
mb={2}
transition
bg={isActive ? 'bg-primary-blue/20' : isCompleted ? 'bg-performance-green/20' : 'bg-iron-gray'}
borderColor={isActive ? 'border-primary-blue' : isCompleted ? 'border-performance-green/50' : 'border-charcoal-outline'}
shadow={isActive ? 'shadow-[0_0_15px_rgba(25,140,255,0.3)]' : 'none'}
bg={isActive ? 'primary-accent/10' : isCompleted ? 'success-green/10' : 'graphite-black'}
borderColor={isActive ? 'primary-accent' : isCompleted ? 'success-green/50' : 'border-gray/50'}
animate={isActive && !shouldReduceMotion ? {
scale: [1, 1.08, 1],
transition: { duration: 1, repeat: Infinity }
opacity: [0.7, 1, 0.7],
transition: { duration: 1.5, repeat: Infinity }
} : {}}
className="relative"
>
{isActive && (
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
)}
{isCompleted ? (
<Icon icon={CheckCircle2} size={5} color="text-performance-green" />
<Icon icon={CheckCircle2} size={5} color="text-success-green" />
) : (
<Text color={isActive ? step.color : 'text-gray-500'}>
<Text color={isActive ? 'text-primary-accent' : 'text-gray-600'}>
<Icon icon={StepIcon} size={5} />
</Text>
)}
</Box>
<Text
size="xs"
weight="medium"
color={isActive ? 'text-white' : 'text-gray-400'}
weight="bold"
color={isActive ? 'text-white' : 'text-gray-500'}
display={{ base: 'none', sm: 'block' }}
transition
className="uppercase tracking-widest"
>
{step.title}
</Text>
@@ -153,15 +157,15 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
mt={4}
pt={4}
borderTop={true}
borderColor="border-charcoal-outline"
borderColor="border-gray/30"
display={{ base: 'block', sm: 'none' }}
>
<Box textAlign="center">
<Text size="xs" color="text-gray-400" block mb={1}>
Step {activeStep + 1}: {steps[activeStep]?.title || ''}
<Text size="xs" color="text-gray-400" block mb={1} font="mono" weight="bold" className="uppercase tracking-widest">
STEP {activeStep + 1}: {steps[activeStep]?.title || ''}
</Text>
<Text size="xs" color="text-gray-500" block>
{steps[activeStep]?.description || ''}
<Text size="xs" color="text-gray-600" block font="mono">
{steps[activeStep]?.description.toUpperCase() || ''}
</Text>
</Box>
</Box>

View File

@@ -44,40 +44,40 @@ const notificationIcons: Record<string, typeof Bell> = {
const notificationColors: Record<string, { bg: string; border: string; text: string; glow: string }> = {
protest_filed: {
bg: 'bg-red-500/10',
border: 'border-red-500/50',
text: 'text-red-400',
glow: 'shadow-[0_0_60px_rgba(239,68,68,0.3)]',
bg: 'bg-critical-red/10',
border: 'border-critical-red/50',
text: 'text-critical-red',
glow: 'shadow-[0_0_60px_rgba(227,92,92,0.3)]',
},
protest_defense_requested: {
bg: 'bg-warning-amber/10',
border: 'border-warning-amber/50',
text: 'text-warning-amber',
glow: 'shadow-[0_0_60px_rgba(245,158,11,0.3)]',
glow: 'shadow-[0_0_60px_rgba(255,197,86,0.3)]',
},
protest_vote_required: {
bg: 'bg-primary-blue/10',
border: 'border-primary-blue/50',
text: 'text-primary-blue',
bg: 'bg-primary-accent/10',
border: 'border-primary-accent/50',
text: 'text-primary-accent',
glow: 'shadow-[0_0_60px_rgba(25,140,255,0.3)]',
},
penalty_issued: {
bg: 'bg-red-500/10',
border: 'border-red-500/50',
text: 'text-red-400',
glow: 'shadow-[0_0_60px_rgba(239,68,68,0.3)]',
bg: 'bg-critical-red/10',
border: 'border-critical-red/50',
text: 'text-critical-red',
glow: 'shadow-[0_0_60px_rgba(227,92,92,0.3)]',
},
race_performance_summary: {
bg: 'bg-gradient-to-br from-yellow-400/20 via-orange-500/20 to-red-500/20',
border: 'border-yellow-400/60',
text: 'text-yellow-400',
glow: 'shadow-[0_0_80px_rgba(251,191,36,0.4)]',
bg: 'bg-panel-gray',
border: 'border-warning-amber/60',
text: 'text-warning-amber',
glow: 'shadow-[0_0_80px_rgba(255,197,86,0.2)]',
},
race_final_results: {
bg: 'bg-gradient-to-br from-purple-500/20 via-pink-500/20 to-indigo-500/20',
border: 'border-purple-400/60',
text: 'text-purple-400',
glow: 'shadow-[0_0_80px_rgba(168,85,247,0.4)]',
bg: 'bg-panel-gray',
border: 'border-primary-accent/60',
text: 'text-primary-accent',
glow: 'shadow-[0_0_80px_rgba(25,140,255,0.2)]',
},
};
@@ -123,10 +123,10 @@ export function ModalNotification({
const NotificationIcon = notificationIcons[notification.type] || AlertCircle;
const colors = notificationColors[notification.type] || {
bg: 'bg-warning-amber/10',
border: 'border-warning-amber/50',
text: 'text-warning-amber',
glow: 'shadow-[0_0_60px_rgba(245,158,11,0.3)]',
bg: 'bg-panel-gray',
border: 'border-border-gray',
text: 'text-gray-400',
glow: 'shadow-card',
};
const data: Record<string, unknown> = notification.data ?? {};
@@ -160,7 +160,6 @@ export function ModalNotification({
// Special celebratory styling for race notifications
const isRaceNotification = notification.type.startsWith('race_');
const isPerformanceSummary = notification.type === 'race_performance_summary';
const provisionalRatingChange = getNumber(data.provisionalRatingChange) ?? 0;
const finalRatingChange = getNumber(data.finalRatingChange) ?? 0;
@@ -177,10 +176,8 @@ export function ModalNotification({
justifyContent="center"
p={4}
transition
bg={isVisible ? 'bg-black/70' : 'bg-transparent'}
// eslint-disable-next-line gridpilot-rules/component-classification
bg={isVisible ? 'bg-black/80' : 'bg-transparent'}
className={isVisible ? 'backdrop-blur-sm' : ''}
hoverBg={isRaceNotification ? 'bg-gradient-to-br from-black/80 via-indigo-900/10 to-black/80' : undefined}
>
<Box
w="full"
@@ -188,55 +185,45 @@ export function ModalNotification({
transform
transition
opacity={isVisible ? 1 : 0}
// eslint-disable-next-line gridpilot-rules/component-classification
className={isVisible ? 'scale-100' : 'scale-95'}
>
<Box
rounded="2xl"
rounded="sm"
border
borderWidth="2px"
borderColor={colors.border}
bg={colors.bg}
bg="panel-gray"
shadow={colors.glow}
overflow="hidden"
position={isRaceNotification ? 'relative' : undefined}
// eslint-disable-next-line gridpilot-rules/component-classification
className="backdrop-blur-md"
>
{/* Header with pulse animation */}
{/* Header */}
<Box
px={6}
py={4}
bg={colors.bg}
bg="graphite-black"
borderBottom
borderColor={colors.border}
// eslint-disable-next-line gridpilot-rules/component-classification
className={isRaceNotification ? 'bg-gradient-to-r from-transparent via-yellow-500/10 to-transparent' : ''}
>
<Box display="flex" alignItems="center" justifyContent="between">
<Box display="flex" alignItems="center" gap={4}>
<Box
p={3}
rounded="xl"
bg={colors.bg}
p={2}
rounded="sm"
bg="panel-gray"
border
borderColor={colors.border}
shadow={isRaceNotification ? 'lg' : undefined}
>
<Icon icon={NotificationIcon} size={6} color={colors.text} />
<Icon icon={NotificationIcon} size={5} color={colors.text} />
</Box>
<Box>
<Text
size="xs"
weight="semibold"
transform="uppercase"
// eslint-disable-next-line gridpilot-rules/component-classification
className="tracking-wide"
color={isRaceNotification ? 'text-yellow-400' : 'text-gray-400'}
weight="bold"
className="uppercase tracking-widest"
color="text-gray-500"
>
{isRaceNotification ? (isPerformanceSummary ? '🏁 Race Complete!' : '🏆 Championship Update') : 'Action Required'}
{isRaceNotification ? 'Race Update' : 'Action Required'}
</Text>
<Heading level={2} fontSize="xl" weight="bold" color="text-white">
<Heading level={3} weight="bold" color="text-white">
{notification.title}
</Heading>
</Box>
@@ -249,7 +236,7 @@ export function ModalNotification({
onClick={() => onDismiss(notification)}
variant="ghost"
size="md"
color="text-gray-400"
color="text-gray-500"
title="Dismiss notification"
/>
)}
@@ -257,17 +244,11 @@ export function ModalNotification({
</Box>
{/* Body */}
<Box
px={6}
py={5}
// eslint-disable-next-line gridpilot-rules/component-classification
className={isRaceNotification ? 'bg-gradient-to-b from-transparent to-yellow-500/5' : ''}
>
<Box px={6} py={6}>
<Text
leading="relaxed"
size={isRaceNotification ? 'lg' : 'base'}
weight={isRaceNotification ? 'medium' : 'normal'}
color={isRaceNotification ? 'text-white' : 'text-gray-300'}
size="base"
color="text-gray-300"
block
>
{notification.message}
@@ -275,16 +256,16 @@ export function ModalNotification({
{/* Race performance stats */}
{isRaceNotification && (
<Box display="grid" gridCols={2} gap={3} mt={4}>
<Box bg="bg-black/20" rounded="lg" p={3} border borderColor="border-yellow-400/20">
<Text size="xs" color="text-yellow-300" weight="medium" block mb={1}>POSITION</Text>
<Box display="grid" gridCols={2} gap={4} mt={6}>
<Box bg="graphite-black" rounded="sm" p={4} border borderColor="border-border-gray">
<Text size="xs" color="text-gray-500" weight="bold" block mb={1} className="uppercase tracking-widest">POSITION</Text>
<Text size="2xl" weight="bold" color="text-white" block>
{notification.data?.position === 'DNF' ? 'DNF' : `P${notification.data?.position || '?'}`}
</Text>
</Box>
<Box bg="bg-black/20" rounded="lg" p={3} border borderColor="border-yellow-400/20">
<Text size="xs" color="text-yellow-300" weight="medium" block mb={1}>RATING CHANGE</Text>
<Text size="2xl" weight="bold" color={ratingChange >= 0 ? 'text-green-400' : 'text-red-400'} block>
<Box bg="graphite-black" rounded="sm" p={4} border borderColor="border-border-gray">
<Text size="xs" color="text-gray-500" weight="bold" block mb={1} className="uppercase tracking-widest">RATING</Text>
<Text size="2xl" weight="bold" color={ratingChange >= 0 ? 'text-success-green' : 'text-critical-red'} block>
{ratingChange >= 0 ? '+' : ''}
{ratingChange}
</Text>
@@ -294,12 +275,12 @@ export function ModalNotification({
{/* Deadline warning */}
{hasDeadline && !isRaceNotification && (
<Box mt={4} display="flex" alignItems="center" gap={2} px={4} py={3} rounded="lg" bg="bg-warning-amber/10" border borderColor="border-warning-amber/30">
<Box mt={6} display="flex" alignItems="center" gap={3} px={4} py={3} rounded="sm" bg="warning-amber/5" border borderColor="border-warning-amber/20">
<Icon icon={Clock} size={5} color="text-warning-amber" />
<Box>
<Text size="sm" weight="medium" color="text-warning-amber" block>Response Required</Text>
<Text size="xs" color="text-gray-400" block>
Please respond by {deadline ? deadline.toLocaleDateString() : ''} at {deadline ? deadline.toLocaleTimeString() : ''}
<Text size="sm" weight="bold" color="text-warning-amber" block className="uppercase tracking-wider">Response Required</Text>
<Text size="xs" color="text-gray-500" block mt={0.5}>
By {deadline ? deadline.toLocaleDateString() : ''} {deadline ? deadline.toLocaleTimeString() : ''}
</Text>
</Box>
</Box>
@@ -307,9 +288,9 @@ export function ModalNotification({
{/* Additional context from data */}
{protestId && (
<Box mt={4} p={3} rounded="lg" bg="bg-iron-gray/50" border borderColor="border-charcoal-outline">
<Text size="xs" color="text-gray-500" block mb={1}>Related Protest</Text>
<Text size="sm" color="text-gray-300" font="mono" block>
<Box mt={6} p={3} rounded="sm" bg="graphite-black" border borderColor="border-border-gray">
<Text size="xs" color="text-gray-500" weight="bold" block mb={1} className="uppercase tracking-widest text-[10px]">PROTEST ID</Text>
<Text size="xs" color="text-gray-400" font="mono" block>
{protestId}
</Text>
</Box>
@@ -321,10 +302,8 @@ export function ModalNotification({
px={6}
py={4}
borderTop
borderColor={isRaceNotification ? (isPerformanceSummary ? 'border-yellow-400/60' : 'border-purple-400/60') : 'border-charcoal-outline'}
bg={isRaceNotification ? undefined : 'bg-iron-gray/30'}
// eslint-disable-next-line gridpilot-rules/component-classification
className={isRaceNotification ? (isPerformanceSummary ? 'bg-gradient-to-r from-yellow-500/10 to-orange-500/10' : 'bg-gradient-to-r from-purple-500/10 to-pink-500/10') : ''}
borderColor="border-border-gray"
bg="graphite-black"
>
<Box display="flex" flexWrap="wrap" gap={3} justifyContent="end">
{notification.actions && notification.actions.length > 0 ? (
@@ -333,9 +312,7 @@ export function ModalNotification({
key={index}
variant={action.type === 'primary' ? 'primary' : 'secondary'}
onClick={() => handleAction(action)}
bg={action.type === 'danger' ? 'bg-red-500' : undefined}
color={action.type === 'danger' ? 'text-white' : undefined}
shadow={isRaceNotification ? 'lg' : undefined}
className={action.type === 'danger' ? 'bg-critical-red hover:bg-critical-red/90' : ''}
>
{action.label}
</Button>
@@ -346,22 +323,14 @@ export function ModalNotification({
<Button
variant="secondary"
onClick={() => (onDismiss ? onDismiss(notification) : onAction(notification, 'dismiss'))}
shadow="lg"
>
Dismiss
</Button>
<Button
variant="secondary"
onClick={() => handleAction({ id: 'share', label: 'Share Achievement', type: 'secondary' })}
shadow="lg"
>
🎉 Share
</Button>
<Button
variant={isPerformanceSummary ? 'race-performance' : 'race-final'}
variant="primary"
onClick={handlePrimaryAction}
>
{isPerformanceSummary ? '🏁 View Race Results' : '🏆 View Standings'}
{notification.type === 'race_performance_summary' ? 'View Results' : 'View Standings'}
</Button>
</>
) : (
@@ -375,9 +344,9 @@ export function ModalNotification({
{/* Cannot dismiss warning */}
{notification.requiresResponse && !isRaceNotification && (
<Box px={6} py={2} bg="bg-red-500/10" borderTop borderColor="border-red-500/20">
<Text size="xs" color="text-red-400" textAlign="center" block>
This notification requires your action and cannot be dismissed
<Box px={6} py={2} bg="critical-red/5" borderTop borderColor="border-critical-red/10">
<Text size="xs" color="text-critical-red" textAlign="center" block weight="medium">
This action is required to continue
</Text>
</Box>
)}

View File

@@ -10,39 +10,37 @@ module.exports = {
],
theme: {
extend: {
backgroundImage: {
'radial-gradient': 'radial-gradient(var(--tw-gradient-stops))',
},
colors: {
'deep-graphite': '#0E0F11',
'iron-gray': '#181B1F',
'charcoal-outline': '#22262A',
'graphite-black': '#0C0D0F',
'panel-gray': '#141619',
'border-gray': '#23272B',
'primary-accent': '#198CFF',
'telemetry-aqua': '#4ED4E0',
'warning-amber': '#FFBE4D',
'success-green': '#6FE37A',
'critical-red': '#E35C5C',
// Legacy mappings for compatibility during transition
'deep-graphite': '#0C0D0F',
'iron-gray': '#141619',
'charcoal-outline': '#23272B',
'primary-blue': '#198CFF',
'performance-green': '#6FE37A',
'warning-amber': '#FFC556',
'neon-aqua': '#43C9E6',
'racing-red': '#E31E24',
'carbon-black': '#0A0A0A',
'metallic-silver': '#C0C0C8',
'racing-red': '#E35C5C',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
boxShadow: {
'glow': '0 0 20px rgba(25, 140, 255, 0.3)',
'glow-strong': '0 0 28px rgba(25, 140, 255, 0.5)',
'card': '0 8px 24px rgba(0, 0, 0, 0.12)',
'racing': '0 4px 16px rgba(227, 30, 36, 0.15)',
'card': '0 4px 12px rgba(0, 0, 0, 0.2)',
},
transitionDuration: {
'smooth': '150ms',
},
transitionTimingFunction: {
'spring': 'cubic-bezier(0.34, 1.56, 0.64, 1)',
'speed': 'cubic-bezier(0.22, 1, 0.36, 1)',
},
animation: {
'speed-pulse': 'speed-pulse 2s ease-in-out infinite',
},
keyframes: {
'speed-pulse': {
'0%, 100%': { opacity: '0.5' },
'50%': { opacity: '1' },
},
'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)',
},
},
},

View File

@@ -4,27 +4,20 @@ import { AlternatingSection } from '@/components/landing/AlternatingSection';
import { FAQ } from '@/components/landing/FAQ';
import { FeatureGrid } from '@/components/landing/FeatureGrid';
import { LandingHero } from '@/components/landing/LandingHero';
import { FeatureItem, ResultItem, StepItem } from '@/components/landing/LandingItems';
import { DiscoverySection } from '@/components/landing/DiscoverySection';
import { FeatureItem, ResultItem, StepItem } from '@/ui/LandingItems';
import { CareerProgressionMockup } from '@/components/mockups/CareerProgressionMockup';
import { CompanionAutomationMockup } from '@/components/mockups/CompanionAutomationMockup';
import { RaceHistoryMockup } from '@/components/mockups/RaceHistoryMockup';
import { SimPlatformMockup } from '@/components/mockups/SimPlatformMockup';
import { ModeGuard } from '@/components/shared/ModeGuard';
import { routes } from '@/lib/routing/RouteConfig';
import { getMediaUrl } from '@/lib/utilities/media';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Card } from '@/ui/Card';
import { Container } from '@/ui/Container';
import { DiscordCTA } from '@/ui/DiscordCTA';
import { Footer } from '@/ui/Footer';
import { Grid } from '@/ui/Grid';
import { Heading } from '@/ui/Heading';
import { Image } from '@/ui/Image';
import { Link } from '@/ui/Link';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
import { TelemetryLine } from '@/ui/TelemetryLine';
import { Glow } from '@/ui/Glow';
export interface HomeViewData {
isAlpha: boolean;
@@ -53,220 +46,132 @@ interface HomeTemplateProps {
export function HomeTemplate({ viewData }: HomeTemplateProps) {
return (
<Box as="main">
<Box as="main" bg="graphite-black" position="relative" overflow="hidden">
<Glow color="primary" size="xl" position="top-right" opacity={0.05} />
<LandingHero />
<TelemetryLine color="primary" height="1px" opacity={0.3} />
{/* Section 1: A Persistent Identity */}
<Box position="relative" bg="graphite-black">
<Glow color="aqua" size="lg" position="bottom-left" opacity={0.03} />
<AlternatingSection
heading="A Persistent Identity"
backgroundVideo="/gameplay.mp4"
description={
<Stack gap={4}>
<Text>
<Stack gap={8}>
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
Your races, your seasons, your progress &mdash; finally in one place.
</Text>
<Stack gap={3}>
<Box display="grid" gridCols={1} gap={3}>
<FeatureItem text="Lifetime stats and season history across all your leagues" />
<FeatureItem text="Track your performance, consistency, and team contributions" />
<FeatureItem text="Your own rating that reflects real league competition" />
</Stack>
<Text>
</Box>
<Box borderLeft borderStyle="solid" borderColor="primary-accent" pl={4} py={1} bg="primary-accent/5">
<Text color="text-gray-500" font="mono" size="xs" uppercase letterSpacing="widest">
iRacing gives you physics. GridPilot gives you a career.
</Text>
</Box>
</Stack>
}
mockup={<CareerProgressionMockup />}
layout="text-left"
/>
</Box>
<FeatureGrid />
{/* Section 2: Results That Actually Stay */}
<Box position="relative" bg="graphite-black">
<Glow color="primary" size="lg" position="top-right" opacity={0.03} />
<AlternatingSection
heading="Results That Actually Stay"
backgroundImage="/images/ff1600.jpeg"
description={
<Stack gap={4}>
<Text size="sm">
<Stack gap={8}>
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
Every race you run stays with you.
</Text>
<Stack gap={3}>
<ResultItem text="Your stats, your team, your story &mdash; all connected" color="#ef4444" />
<ResultItem text="One race result updates your profile, team points, rating, and season history" color="#ef4444" />
<ResultItem text="No more fragmented data across spreadsheets and forums" color="#ef4444" />
</Stack>
<Text size="sm">
<Box display="grid" gridCols={1} gap={3}>
<ResultItem text="Your stats, your team, your story &mdash; all connected" color="#198CFF" />
<ResultItem text="One race result updates your profile, team points, rating, and season history" color="#198CFF" />
<ResultItem text="No more fragmented data across spreadsheets and forums" color="#198CFF" />
</Box>
<Box borderLeft borderStyle="solid" borderColor="telemetry-aqua" pl={4} py={1} bg="telemetry-aqua/5">
<Text color="text-gray-500" font="mono" size="xs" uppercase letterSpacing="widest">
Your racing career, finally in one place.
</Text>
</Box>
</Stack>
}
mockup={<RaceHistoryMockup />}
layout="text-right"
/>
</Box>
<TelemetryLine color="aqua" height="1px" opacity={0.2} />
{/* Section 3: Automatic Session Creation */}
<Box position="relative" bg="graphite-black">
<Glow color="amber" size="lg" position="bottom-right" opacity={0.02} />
<AlternatingSection
heading="Automatic Session Creation"
description={
<Stack gap={4}>
<Text size="sm">
<Stack gap={8}>
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
Setting up league races used to mean clicking through iRacing&apos;s wizard 20 times.
</Text>
<Stack gap={3}>
<Box display="grid" gridCols={1} gap={3}>
<StepItem step={1} text="Our companion app syncs with your league schedule" />
<StepItem step={2} text="When it's race time, it creates the iRacing session automatically" />
<StepItem step={3} text="No clicking through wizards. No manual setup" />
</Stack>
<Text size="sm">
</Box>
<Box borderLeft borderStyle="solid" borderColor="warning-amber" pl={4} py={1} bg="warning-amber/5">
<Text color="text-gray-500" font="mono" size="xs" uppercase letterSpacing="widest">
Automation instead of repetition.
</Text>
</Box>
</Stack>
}
mockup={<CompanionAutomationMockup />}
layout="text-left"
/>
</Box>
{/* Section 4: Game-Agnostic Platform */}
<Box position="relative" bg="graphite-black">
<Glow color="primary" size="xl" position="center" opacity={0.03} />
<AlternatingSection
heading="Built for iRacing. Ready for the future."
backgroundImage="/images/lmp3.jpeg"
description={
<Stack gap={4}>
<Text size="sm">
<Stack gap={8}>
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
Right now, we&apos;re focused on making iRacing league racing better.
</Text>
<Text size="sm">
<Text color="text-gray-400" leading="relaxed">
But sims come and go. Your leagues, your teams, your rating &mdash; those stay.
</Text>
<Text size="sm">
GridPilot is built to outlast any single platform.
</Text>
<Text size="sm">
When the next sim arrives, your competitive identity moves with you.
<Box borderLeft borderStyle="solid" borderColor="border-gray" pl={4} py={1} bg="white/5">
<Text color="text-gray-500" font="mono" size="xs" uppercase letterSpacing="widest" leading="relaxed">
GridPilot is built to outlast any single platform. When the next sim arrives, your competitive identity moves with you.
</Text>
</Box>
</Stack>
}
mockup={<SimPlatformMockup />}
layout="text-right"
/>
</Box>
{/* Alpha-only discovery section */}
<ModeGuard feature="alpha_discovery">
<Container size="lg" py={12}>
<Stack gap={8}>
<Box>
<Heading level={2}>Discover the grid</Heading>
<Text size="sm" color="text-gray-400" block mt={2}>
Explore leagues, teams, and races that make up the GridPilot ecosystem.
</Text>
<Box bg="panel-gray/20" py={20} borderTop borderBottom borderColor="border-gray/50" position="relative">
<Glow color="aqua" size="xl" position="center" opacity={0.02} />
<DiscoverySection viewData={viewData} />
</Box>
<Grid cols={3} gap={8}>
{/* Top leagues */}
<Card>
<Stack gap={4}>
<Stack direction="row" align="center" justify="between">
<Heading level={3} fontSize="sm">Featured leagues</Heading>
<Link href={routes.public.leagues}>
<Button variant="secondary" size="sm">
View all
</Button>
</Link>
</Stack>
<Stack gap={3}>
{viewData.topLeagues.slice(0, 4).map((league) => (
<Box key={league.id}>
<Stack direction="row" align="start" gap={3}>
<Surface variant="muted" rounded="md" border padding={1} w="2.5rem" h="2.5rem" bg="bg-blue-500/10" borderColor="border-blue-500/30" display="flex" alignItems="center" justifyContent="center">
<Text size="xs" weight="bold" color="text-primary-blue">
{league.name.split(' ').map((word) => word[0]).join('').slice(0, 3).toUpperCase()}
</Text>
</Surface>
<Box flex={1} minWidth="0">
<Text color="text-white" block truncate>{league.name}</Text>
<Text size="xs" color="text-gray-400" block mt={1} truncate>{league.description}</Text>
</Box>
</Stack>
</Box>
))}
</Stack>
</Stack>
</Card>
{/* Teams */}
<Card>
<Stack gap={4}>
<Stack direction="row" align="center" justify="between">
<Heading level={3} fontSize="sm">Teams on the grid</Heading>
<Link href={routes.public.teams}>
<Button variant="secondary" size="sm">
Browse teams
</Button>
</Link>
</Stack>
<Stack gap={3}>
{viewData.teams.slice(0, 4).map(team => (
<Box key={team.id}>
<Stack direction="row" align="start" gap={3}>
<Surface variant="muted" rounded="md" border padding={1} w="2.5rem" h="2.5rem" overflow="hidden" bg="bg-neutral-800">
<Image
src={team.logoUrl || getMediaUrl('team-logo', team.id)}
alt={team.name}
width={40}
height={40}
objectFit="cover"
fullWidth
fullHeight
/>
</Surface>
<Box flex={1} minWidth="0">
<Text color="text-white" block truncate>{team.name}</Text>
<Text size="xs" color="text-gray-400" block mt={1} truncate>{team.description}</Text>
</Box>
</Stack>
</Box>
))}
</Stack>
</Stack>
</Card>
{/* Upcoming races */}
<Card>
<Stack gap={4}>
<Stack direction="row" align="center" justify="between">
<Heading level={3} fontSize="sm">Upcoming races</Heading>
<Link href={routes.public.races}>
<Button variant="secondary" size="sm">
View schedule
</Button>
</Link>
</Stack>
{viewData.upcomingRaces.length === 0 ? (
<Text size="xs" color="text-gray-400">
No races scheduled in this demo snapshot.
</Text>
) : (
<Stack gap={3}>
{viewData.upcomingRaces.map(race => (
<Box key={race.id}>
<Stack direction="row" align="start" justify="between" gap={3}>
<Box flex={1} minWidth="0">
<Text color="text-white" block truncate>{race.track}</Text>
<Text size="xs" color="text-gray-400" block mt={1} truncate>{race.car}</Text>
</Box>
<Text size="xs" color="text-gray-500">
{race.formattedDate}
</Text>
</Stack>
</Box>
))}
</Stack>
)}
</Stack>
</Card>
</Grid>
</Stack>
</Container>
</ModeGuard>
<DiscordCTA />

View File

@@ -16,21 +16,21 @@ interface BadgeProps {
}
export function Badge({ children, className = '', variant = 'default', size = 'sm', icon, style, bg, color, borderColor }: BadgeProps) {
const baseClasses = 'flex items-center gap-1.5 rounded-full border font-medium';
const baseClasses = 'flex items-center gap-1.5 rounded-none border font-bold uppercase tracking-widest';
const sizeClasses = {
xs: 'px-1.5 py-0.5 text-[10px]',
sm: 'px-2.5 py-1 text-xs',
md: 'px-3 py-1.5 text-sm'
xs: 'px-1.5 py-0.5 text-[9px]',
sm: 'px-2 py-0.5 text-[10px]',
md: 'px-3 py-1 text-xs'
};
const variantClasses = {
default: 'bg-gray-500/10 border-gray-500/30 text-gray-400',
primary: 'bg-primary-blue/10 border-primary-blue/30 text-primary-blue',
success: 'bg-performance-green/10 border-performance-green/30 text-performance-green',
primary: 'bg-primary-accent/10 border-primary-accent/30 text-primary-accent',
success: 'bg-success-green/10 border-success-green/30 text-success-green',
warning: 'bg-warning-amber/10 border-warning-amber/30 text-warning-amber',
danger: 'bg-red-600/10 border-red-600/30 text-red-500',
info: 'bg-neon-aqua/10 border-neon-aqua/30 text-neon-aqua'
danger: 'bg-critical-red/10 border-critical-red/30 text-critical-red',
info: 'bg-telemetry-aqua/10 border-telemetry-aqua/30 text-telemetry-aqua'
};
const classes = [

View File

@@ -6,7 +6,7 @@ interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'as'
children: ReactNode;
onClick?: MouseEventHandler<HTMLButtonElement>;
className?: string;
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'race-performance' | 'race-final' | 'discord';
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'race-final' | 'discord';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
type?: 'button' | 'submit' | 'reset';
@@ -34,25 +34,24 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
rel,
...props
}, ref) => {
const baseClasses = 'inline-flex items-center rounded-lg transition-all duration-75 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 hover:scale-[1.02] active:scale-95';
const baseClasses = 'inline-flex items-center justify-center rounded-none transition-all duration-150 ease-smooth focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 active:opacity-80 uppercase tracking-widest font-bold';
const variantClasses = {
primary: 'bg-primary-blue text-white hover:bg-primary-blue/80 focus-visible:outline-primary-blue shadow-[0_0_15px_rgba(25,140,255,0.4)]',
secondary: 'bg-iron-gray text-white border border-charcoal-outline hover:bg-iron-gray/80 focus-visible:outline-primary-blue',
danger: 'bg-red-600 text-white hover:bg-red-700 focus-visible:outline-red-600',
ghost: 'bg-transparent text-gray-400 hover:bg-gray-800 focus-visible:outline-gray-400',
'race-performance': 'bg-gradient-to-r from-yellow-400 to-orange-500 text-white shadow-[0_0_15px_rgba(251,191,36,0.4)] hover:from-yellow-500 hover:to-orange-600 focus-visible:outline-yellow-400',
'race-final': 'bg-gradient-to-r from-purple-400 to-pink-500 text-white shadow-[0_0_15px_rgba(168,85,247,0.4)] hover:from-purple-500 hover:to-pink-600 focus-visible:outline-purple-400',
discord: 'bg-[#5865F2] text-white hover:bg-[#4752C4] shadow-[0_0_20px_rgba(88,101,242,0.3)] hover:shadow-[0_0_30px_rgba(88,101,242,0.6)] focus-visible:outline-[#5865F2]'
primary: 'bg-primary-accent text-white hover:bg-primary-accent/90 focus-visible:outline-primary-accent shadow-[0_0_15px_rgba(25,140,255,0.3)] hover:shadow-[0_0_25px_rgba(25,140,255,0.5)]',
secondary: 'bg-panel-gray text-white border border-border-gray hover:bg-border-gray/50 focus-visible:outline-primary-accent',
danger: 'bg-critical-red text-white hover:bg-critical-red/90 focus-visible:outline-critical-red',
ghost: 'bg-transparent text-gray-400 hover:text-white hover:bg-white/5 focus-visible:outline-gray-400',
'race-final': 'bg-success-green text-graphite-black hover:bg-success-green/90 focus-visible:outline-success-green',
discord: 'bg-[#5865F2] text-white hover:bg-[#4752C4] focus-visible:outline-[#5865F2]',
};
const sizeClasses = {
sm: 'min-h-[36px] px-3 py-1.5 text-xs',
md: 'min-h-[44px] px-4 py-2 text-sm',
lg: 'min-h-[52px] px-6 py-3 text-base'
sm: 'min-h-[32px] px-3 py-1 text-xs font-medium',
md: 'min-h-[40px] px-4 py-2 text-sm font-medium',
lg: 'min-h-[48px] px-6 py-3 text-base font-medium'
};
const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer';
const disabledClasses = disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer';
const widthClasses = fullWidth ? 'w-full' : '';
const classes = [

View File

@@ -13,7 +13,7 @@ interface CardProps extends Omit<BoxProps<'div'>, 'children' | 'className' | 'on
children: ReactNode;
className?: string;
onClick?: MouseEventHandler<HTMLDivElement>;
variant?: 'default' | 'highlight';
variant?: 'default' | 'outline' | 'ghost';
p?: Spacing | ResponsiveSpacing;
px?: Spacing | ResponsiveSpacing;
py?: Spacing | ResponsiveSpacing;
@@ -30,17 +30,18 @@ export function Card({
variant = 'default',
...props
}: CardProps) {
const baseClasses = 'rounded-lg shadow-card border duration-200';
const baseClasses = 'rounded-none transition-all duration-150 ease-smooth';
const variantClasses = {
default: 'bg-iron-gray border-charcoal-outline',
highlight: 'bg-gradient-to-r from-blue-900/20 to-blue-700/10 border-blue-500/30'
default: 'bg-panel-gray border border-border-gray shadow-card',
outline: 'bg-transparent border border-border-gray',
ghost: 'bg-transparent border-none'
};
const classes = [
baseClasses,
variantClasses[variant],
onClick ? 'cursor-pointer hover:scale-[1.02]' : '',
onClick ? 'cursor-pointer hover:bg-border-gray/30' : '',
className
].filter(Boolean).join(' ');
@@ -52,7 +53,7 @@ export function Card({
<Box
className={classes}
onClick={onClick}
p={hasPadding ? undefined : 6}
p={hasPadding ? undefined : 4}
{...props}
>
{children}

View File

@@ -15,11 +15,11 @@ export function DecorativeBlur({
opacity = 10
}: DecorativeBlurProps) {
const colorClasses = {
blue: 'bg-primary-blue',
green: 'bg-performance-green',
blue: 'bg-primary-accent',
green: 'bg-success-green',
purple: 'bg-purple-600',
yellow: 'bg-yellow-400',
red: 'bg-racing-red'
yellow: 'bg-warning-amber',
red: 'bg-critical-red'
};
const sizeClasses = {

View File

@@ -2,6 +2,9 @@
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading';
import { Glow } from '@/ui/Glow';
import { Icon } from '@/ui/Icon';
import { DiscordIcon } from '@/ui/icons/DiscordIcon';
import { Stack } from '@/ui/Stack';
@@ -14,21 +17,25 @@ export function DiscordCTA() {
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#';
return (
<Surface
<Box
as="section"
variant="discord"
padding={4}
bg="graphite-black"
position="relative"
py={{ base: 4, md: 12, lg: 16 }}
py={{ base: 20, md: 32 }}
borderBottom
borderColor="border-gray/50"
overflow="hidden"
>
<Box maxWidth="896px" mx="auto" px={2}>
<Glow color="primary" size="xl" position="center" opacity={0.05} />
<Container size="lg" position="relative" zIndex={10}>
<Surface
variant="discord-inner"
padding={3}
variant="default"
padding={12}
border
rounded="xl"
rounded="none"
position="relative"
shadow="discord"
className="overflow-hidden bg-panel-gray/40"
>
{/* Discord brand accent */}
<Box
@@ -37,120 +44,107 @@ export function DiscordCTA() {
left={0}
right={0}
height="1"
backgroundColor="[#5865F2]"
opacity={0.6}
className="bg-gradient-to-r from-transparent via-[#5865F2]/60 to-transparent"
bg="primary-accent"
/>
<Stack align="center" gap={6} center>
<Stack align="center" gap={12} center>
{/* Header */}
<Stack align="center" gap={4}>
<Stack align="center" gap={6}>
<Box
display="flex"
center
rounded="full"
w={{ base: "10", md: "14", lg: "18" }}
h={{ base: "10", md: "14", lg: "18" }}
backgroundColor="[#5865F2]"
opacity={0.2}
rounded="none"
w={{ base: "16", md: "20" }}
h={{ base: "16", md: "20" }}
bg="primary-accent/10"
border
borderColor="[#5865F2]"
borderColor="primary-accent/30"
className="relative"
>
<DiscordIcon color="text-[#5865F2]" size={32} />
<DiscordIcon color="text-primary-accent" size={40} />
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
<Box position="absolute" bottom="-1px" right="-1px" w="2" h="2" borderBottom borderRight borderColor="primary-accent" />
</Box>
<Stack gap={2}>
<Text as="h2" size="2xl" weight="semibold" color="text-white">
Join us on Discord
</Text>
<Stack gap={4} align="center">
<Heading level={2} weight="bold" color="text-white" fontSize={{ base: '2xl', md: '4xl' }} className="tracking-tight">
Join the Grid on Discord
</Heading>
<Box
mx="auto"
rounded="full"
w={{ base: "16", md: "24", lg: "32" }}
h={{ base: "0.5", md: "1" }}
backgroundColor="[#5865F2]"
className="bg-gradient-to-r from-[#5865F2] to-[#7289DA]"
w="16"
h="1"
bg="primary-accent"
/>
</Stack>
</Stack>
{/* Personal message */}
<Box maxWidth="672px" mx="auto">
<Stack gap={3}>
<Text size="sm" color="text-gray-300" weight="normal" leading="relaxed">
GridPilot is a <Text weight="bold" color="text-white">solo developer project</Text>, and I&apos;m building it because I got tired of the chaos in league racing.
<Box maxWidth="2xl" mx="auto" textAlign="center">
<Stack gap={6}>
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
GridPilot is a <span className="text-white font-bold">solo developer project</span> built for the community.
</Text>
<Text size="sm" color="text-gray-400" weight="normal" leading="relaxed">
This is <Text weight="bold" color="text-gray-300">early days</Text>, and I need your help. Join the Discord to:
<Text size="base" color="text-gray-400" weight="normal" leading="relaxed">
We are in early alpha. Join us to help shape the future of motorsport infrastructure. Your feedback directly influences the roadmap.
</Text>
</Stack>
</Box>
{/* Benefits grid */}
<Box
maxWidth="2xl"
maxWidth="4xl"
mx="auto"
mt={4}
fullWidth
>
<Grid cols={2} gap={3} className="md:grid-cols-2">
<Grid cols={1} mdCols={2} gap={6}>
<BenefitItem
icon={MessageSquare}
title="Share your pain points"
description="Tell me what frustrates you about league racing today"
description="Tell us what frustrates you about league racing today."
/>
<BenefitItem
icon={Lightbulb}
title="Shape the product"
description="Your ideas will directly influence what gets built"
description="Your ideas directly influence our roadmap."
/>
<BenefitItem
icon={Users}
title="Be part of the community"
description="Connect with other league racers who get it"
title="Connect with racers"
description="Join a community of like-minded competitive drivers."
/>
<BenefitItem
icon={Code}
title="Get early access"
description="Test features first and help iron out the rough edges"
title="Early Access"
description="Test new features before they go public."
/>
</Grid>
</Box>
{/* CTA Button */}
<Stack gap={3} pt={4}>
<Stack gap={6} pt={4} align="center">
<Button
as="a"
href={discordUrl}
target="_blank"
rel="noopener noreferrer"
variant="discord"
variant="primary"
size="lg"
icon={<DiscordIcon size={28} />}
className="px-16 py-4"
icon={<DiscordIcon size={24} />}
>
Join us on Discord
Join Discord
</Button>
<Text size="xs" color="text-primary-blue" weight="light">
💡 Get a link to our early alpha view in the Discord
</Text>
{!process.env.NEXT_PUBLIC_DISCORD_URL && (
<Text size="xs" color="text-gray-500">
Note: Configure NEXT_PUBLIC_DISCORD_URL in your environment variables
</Text>
)}
</Stack>
{/* Footer note */}
<Box maxWidth="xl" mx="auto" pt={4}>
<Text size="xs" color="text-gray-500" weight="light" leading="relaxed" align="center" block>
This is a community effort. Every voice matters. Let&apos;s build something that actually works for league racing.
<Box border borderStyle="dashed" borderColor="primary-accent/50" px={4} py={1}>
<Text size="xs" color="text-primary-accent" weight="bold" font="mono" uppercase letterSpacing="widest">
Early Alpha Access Available
</Text>
</Box>
</Stack>
</Stack>
</Surface>
</Container>
</Box>
</Surface>
);
}
@@ -159,30 +153,29 @@ function BenefitItem({ icon, title, description }: { icon: LucideIcon, title: st
<Surface
variant="muted"
border
padding={3}
rounded="lg"
padding={6}
rounded="none"
display="flex"
gap={3}
className="items-start hover:border-[#5865F2]/30 transition-all"
gap={5}
className="items-start hover:border-primary-accent/30 transition-all bg-panel-gray/20 group"
>
<Box
display="flex"
center
rounded="lg"
rounded="none"
flexShrink={0}
w="6"
h="6"
backgroundColor="[#5865F2]"
opacity={0.2}
w="10"
h="10"
bg="primary-accent/5"
border
borderColor="[#5865F2]"
mt={0.5}
borderColor="border-gray/50"
className="group-hover:border-primary-accent/30 transition-colors"
>
<Icon icon={icon} size={4} color="text-[#5865F2]" />
<Icon icon={icon} size={5} color="text-primary-accent" />
</Box>
<Stack gap={0.5}>
<Text size="xs" weight="medium" color="text-white">{title}</Text>
<Text size="xs" color="text-gray-400" leading="relaxed">{description}</Text>
<Stack gap={2}>
<Text size="base" weight="bold" color="text-white" className="tracking-wide">{title}</Text>
<Text size="sm" color="text-gray-400" leading="relaxed">{description}</Text>
</Stack>
</Surface>
);

View File

@@ -8,41 +8,31 @@ const xUrl = process.env.NEXT_PUBLIC_X_URL || '#';
export function Footer() {
return (
<Box as="footer" position="relative" bg="bg-deep-graphite">
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, var(--primary-blue), transparent)" />
<Box as="footer" position="relative" bg="graphite-black" borderTop borderColor="border-gray/50">
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, #198CFF, transparent)" opacity={0.3} />
<Box maxWidth="4xl" mx="auto" px={{ base: 'calc(1.5rem+var(--sal))', lg: 8 }} py={{ base: 2, md: 8, lg: 12 }} pb={{ base: 'calc(0.5rem+var(--sab))', md: 'calc(1.5rem+var(--sab))' }}>
<Box maxWidth="7xl" mx="auto" px={{ base: 6, lg: 8 }} py={{ base: 12, md: 16 }}>
{/* Racing stripe accent */}
<Box
display="flex"
gap={1}
mb={{ base: 2, md: 4, lg: 6 }}
gap={2}
mb={8}
justifyContent="center"
>
<Box w={{ base: "12", md: "20", lg: "28" }} h={{ base: "0.5", md: "0.5", lg: "1" }} bg="bg-white" rounded="full" />
<Box w={{ base: "12", md: "20", lg: "28" }} h={{ base: "0.5", md: "0.5", lg: "1" }} bg="bg-primary-blue" rounded="full" />
<Box w={{ base: "12", md: "20", lg: "28" }} h={{ base: "0.5", md: "0.5", lg: "1" }} bg="bg-white" rounded="full" />
<Box w="12" h="1" bg="white" opacity={0.1} />
<Box w="12" h="1" bg="primary-accent" />
<Box w="12" h="1" bg="white" opacity={0.1} />
</Box>
{/* Personal message */}
<Box
textAlign="center"
mb={{ base: 3, md: 6, lg: 8 }}
mb={12}
>
<Box mb={2} display="flex" justifyContent="center">
<Image
src="/images/logos/icon-square-dark.svg"
alt="GridPilot"
width={40}
height={40}
fullHeight
style={{ width: 'auto' }}
/>
</Box>
<Text size={{ base: 'xs', lg: 'sm' }} color="text-gray-300" block mb={{ base: 1, md: 2 }}>
<Text size="sm" color="text-gray-300" block mb={2} weight="bold" className="tracking-wide">
🏁 Built by a sim racer, for sim racers
</Text>
<Text size={{ base: 'xs', md: 'xs' }} color="text-gray-400" weight="light" maxWidth="2xl" mx="auto" block>
<Text size="xs" color="text-gray-500" weight="normal" maxWidth="2xl" mx="auto" block leading="relaxed">
Just a fellow racer tired of spreadsheets and chaos. GridPilot is my passion project to make league racing actually fun again.
</Text>
</Box>
@@ -51,50 +41,39 @@ export function Footer() {
<Box
display="flex"
justifyContent="center"
gap={{ base: 4, md: 6, lg: 8 }}
mb={{ base: 3, md: 6, lg: 8 }}
gap={8}
mb={12}
>
<Link
href={discordUrl}
variant="ghost"
size="xs"
hoverTextColor="text-neon-aqua"
transition
px={3}
py={2}
minHeight="44px"
minWidth="44px"
size="sm"
className="text-gray-400 hover:text-primary-accent transition-colors font-bold uppercase tracking-widest"
>
💬 Join Discord
💬 Discord
</Link>
<Link
href={xUrl}
variant="ghost"
size="xs"
color="text-gray-300"
hoverTextColor="text-neon-aqua"
transition
px={3}
py={2}
minHeight="44px"
minWidth="44px"
size="sm"
className="text-gray-400 hover:text-primary-accent transition-colors font-bold uppercase tracking-widest"
>
𝕏 Follow on X
𝕏 Twitter
</Link>
</Box>
{/* Development status */}
<Box
textAlign="center"
pt={{ base: 2, md: 4, lg: 6 }}
pt={8}
borderTop
borderColor="border-charcoal-outline"
borderColor="border-gray/30"
>
<Text size={{ base: 'xs', lg: 'sm' }} color="text-gray-500" block mb={{ base: 1, md: 2 }}>
<Text size="xs" color="text-gray-600" block mb={1} font="mono" uppercase letterSpacing="widest">
Early development Feedback welcome
</Text>
<Text size={{ base: 'xs', md: 'xs' }} color="text-gray-600" block>
Questions? Find me on Discord
<Text size="xs" color="text-gray-700" block font="mono">
&copy; {new Date().getFullYear()} GridPilot
</Text>
</Box>
</Box>

49
apps/website/ui/Glow.tsx Normal file
View File

@@ -0,0 +1,49 @@
import React from 'react';
import { Box } from './Box';
interface GlowProps {
color?: 'primary' | 'aqua' | 'purple' | 'amber';
size?: 'sm' | 'md' | 'lg' | 'xl';
opacity?: number;
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center';
className?: string;
}
export function Glow({
color = 'primary',
size = 'md',
opacity = 0.1,
position = 'center',
className = ''
}: GlowProps) {
const colorMap = {
primary: 'from-primary-accent/20',
aqua: 'from-telemetry-aqua/20',
purple: 'from-purple-500/20',
amber: 'from-warning-amber/20',
};
const sizeMap = {
sm: 'w-64 h-64',
md: 'w-96 h-96',
lg: 'w-[32rem] h-[32rem]',
xl: 'w-[48rem] h-[48rem]',
};
const positionMap = {
'top-left': '-top-32 -left-32',
'top-right': '-top-32 -right-32',
'bottom-left': '-bottom-32 -left-32',
'bottom-right': '-bottom-32 -right-32',
'center': 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',
};
return (
<Box
position="absolute"
className={`${sizeMap[size]} ${positionMap[position]} bg-radial-gradient ${colorMap[color]} to-transparent blur-[100px] pointer-events-none ${className}`}
style={{ opacity }}
zIndex={0}
/>
);
}

View File

@@ -7,7 +7,7 @@ interface HeaderProps {
export function Header({ children }: HeaderProps) {
return (
<header className="fixed top-0 left-0 right-0 z-50 bg-deep-graphite/80 backdrop-blur-sm border-b border-white/5">
<header className="fixed top-0 left-0 right-0 z-50 bg-graphite-black/80 backdrop-blur-md border-b border-border-gray/50">
<Container>
{children}
</Container>

View File

@@ -26,12 +26,12 @@ export function Heading({ level, children, icon, groupHoverColor, truncate, font
const Tag = `h${level}` as ElementType;
const levelClasses = {
1: 'text-3xl md:text-4xl font-bold text-white',
2: 'text-xl font-semibold text-white',
3: 'text-lg font-semibold text-white',
4: 'text-base font-semibold text-white',
5: 'text-sm font-semibold text-white',
6: 'text-xs font-semibold text-white',
1: 'text-3xl md:text-4xl font-bold text-white tracking-tight',
2: 'text-xl md:text-2xl font-bold text-white tracking-tight',
3: 'text-lg font-bold text-white tracking-tight',
4: 'text-base font-bold text-white tracking-tight',
5: 'text-sm font-bold text-white tracking-tight uppercase tracking-wider',
6: 'text-xs font-bold text-white tracking-tight uppercase tracking-widest',
};
const weightClasses = {

View File

@@ -12,15 +12,15 @@ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ className = '', variant = 'default', errorMessage, icon, label, ...props }, ref) => {
const baseClasses = 'px-3 py-2 border rounded-lg text-white bg-deep-graphite focus:outline-none focus:border-primary-blue transition-colors w-full';
const variantClasses = (variant === 'error' || errorMessage) ? 'border-racing-red' : 'border-charcoal-outline';
const baseClasses = 'px-3 py-2 border rounded-sm text-white bg-graphite-black focus:outline-none focus:border-primary-accent transition-all duration-150 ease-smooth w-full text-sm placeholder:text-gray-600';
const variantClasses = (variant === 'error' || errorMessage) ? 'border-critical-red' : 'border-border-gray';
const iconClasses = icon ? 'pl-10' : '';
const classes = `${baseClasses} ${variantClasses} ${iconClasses} ${className}`;
return (
<Stack gap={1.5} fullWidth>
{label && (
<Text as="label" size="sm" weight="medium" color="text-gray-300">
<Text as="label" size="xs" weight="bold" color="text-gray-500" className="uppercase tracking-wider">
{label}
</Text>
)}
@@ -34,13 +34,14 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
zIndex={10}
display="flex"
center
className="text-gray-500"
>
{icon}
</Box>
)}
<input ref={ref} className={classes} {...props} />
{errorMessage && (
<Text size="xs" color="text-error-red" block mt={1}>
<Text size="xs" color="text-critical-red" block mt={1}>
{errorMessage}
</Text>
)}

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Surface } from '@/ui/Surface';
import { Box } from '@/ui/Box';
export function FeatureItem({ text }: { text: string }) {
return (
<Surface variant="muted" rounded="none" border padding={4} bg="panel-gray/40" borderColor="border-gray/50" className="group hover:border-primary-accent/30 transition-colors">
<Stack direction="row" align="center" gap={4}>
<Box w="1" h="4" bg="primary-accent" className="group-hover:h-6 transition-all" />
<Text color="text-gray-300" leading="relaxed" weight="normal" size="sm" className="tracking-wide">
{text}
</Text>
</Stack>
</Surface>
);
}
export function ResultItem({ text, color }: { text: string, color: string }) {
return (
<Surface variant="muted" rounded="none" border padding={4} bg="panel-gray/40" borderColor="border-gray/50" className="group hover:border-primary-accent/30 transition-colors">
<Stack direction="row" align="center" gap={4}>
<Box w="1" h="4" style={{ backgroundColor: color }} className="group-hover:h-6 transition-all" />
<Text color="text-gray-300" leading="relaxed" weight="normal" size="sm" className="tracking-wide">
{text}
</Text>
</Stack>
</Surface>
);
}
export function StepItem({ step, text }: { step: number, text: string }) {
return (
<Surface variant="muted" rounded="none" border padding={4} bg="panel-gray/40" borderColor="border-gray/50" className="group hover:border-primary-accent/30 transition-colors">
<Stack direction="row" align="center" gap={4}>
<Box w="8" h="8" display="flex" center border borderColor="border-gray" className="group-hover:border-primary-accent/50 transition-colors">
<Text weight="bold" size="xs" color="text-primary-accent" font="mono">{step.toString().padStart(2, '0')}</Text>
</Box>
<Text color="text-gray-300" leading="relaxed" weight="normal" size="sm" className="tracking-wide">
{text}
</Text>
</Stack>
</Surface>
);
}

View File

@@ -5,6 +5,7 @@ import { Box } from './Box';
import { Heading } from './Heading';
import { Icon } from './Icon';
import { Text } from './Text';
import { Stack } from './Stack';
import { Image } from './Image';
import { PlaceholderImage } from './PlaceholderImage';
import { Calendar as LucideCalendar, ChevronRight as LucideChevronRight, Users as LucideUsers } from 'lucide-react';
@@ -60,13 +61,13 @@ export function LeagueCard({
<Box
position="relative"
h="full"
rounded="xl"
bg="bg-iron-gray"
rounded="none"
bg="panel-gray/40"
border
borderColor="border-charcoal-outline"
borderColor="border-gray/50"
overflow="hidden"
transition
className="hover:border-primary-blue/50 hover:shadow-[0_0_30px_rgba(25,140,255,0.15)] hover:bg-iron-gray/80"
className="hover:border-primary-accent/30 hover:bg-panel-gray/60 transition-all duration-300"
>
{/* Cover Image */}
<Box position="relative" h="32" overflow="hidden">
@@ -76,10 +77,10 @@ export function LeagueCard({
fullWidth
fullHeight
objectFit="cover"
className="transition-transform duration-300 group-hover:scale-105"
className="transition-transform duration-500 group-hover:scale-105 opacity-60"
/>
{/* Gradient Overlay */}
<Box position="absolute" inset="0" bg="bg-gradient-to-t from-iron-gray via-iron-gray/60 to-transparent" />
<Box position="absolute" inset="0" bg="linear-gradient(to top, #0C0D0F, transparent)" />
{/* Badges - Top Left */}
<Box position="absolute" top="3" left="3" display="flex" alignItems="center" gap={2}>
@@ -93,7 +94,7 @@ export function LeagueCard({
{/* Logo */}
<Box position="absolute" left="4" bottom="-6" zIndex={10}>
<Box w="12" h="12" rounded="lg" overflow="hidden" border borderColor="border-iron-gray" bg="bg-deep-graphite" shadow="xl">
<Box w="12" h="12" rounded="none" overflow="hidden" border borderColor="border-gray/50" bg="graphite-black" shadow="xl">
{logoUrl ? (
<Image
src={logoUrl}
@@ -114,34 +115,37 @@ export function LeagueCard({
{/* Content */}
<Box pt={8} px={4} pb={4} display="flex" flexDirection="col" fullHeight>
{/* Title & Description */}
<Heading level={3} className="line-clamp-1 group-hover:text-primary-blue transition-colors" mb={1}>
<Stack direction="row" align="center" gap={2} mb={1}>
<Box w="1" h="4" bg="primary-accent" />
<Heading level={3} fontSize="lg" weight="bold" className="line-clamp-1 group-hover:text-primary-accent transition-colors tracking-tight">
{name}
</Heading>
<Text size="xs" color="text-gray-500" lineClamp={2} mb={3} style={{ height: '2rem' }} block>
</Stack>
<Text size="xs" color="text-gray-500" lineClamp={2} mb={4} style={{ height: '2.5rem' }} block leading="relaxed">
{description || 'No description available'}
</Text>
{/* Stats Row */}
<Box display="flex" alignItems="center" gap={3} mb={3}>
<Box display="flex" alignItems="center" gap={3} mb={4}>
{/* Primary Slots (Drivers/Teams/Nations) */}
<Box flexGrow={1}>
<Box display="flex" alignItems="center" justifyContent="between" mb={1}>
<Text size="xs" color="text-gray-500">{slotLabel}</Text>
<Text size="xs" color="text-gray-400">
<Box display="flex" alignItems="center" justifyContent="between" mb={1.5}>
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">{slotLabel}</Text>
<Text size="xs" color="text-gray-400" font="mono">
{usedSlots}/{maxSlots || '∞'}
</Text>
</Box>
<Box h="1.5" rounded="full" bg="bg-charcoal-outline" overflow="hidden">
<Box h="1" rounded="none" bg="border-gray/30" overflow="hidden">
<Box
h="full"
rounded="full"
rounded="none"
transition
bg={
fillPercentage >= 90
? 'bg-warning-amber'
? 'warning-amber'
: fillPercentage >= 70
? 'bg-primary-blue'
: 'bg-performance-green'
? 'primary-accent'
: 'success-green'
}
style={{ width: `${Math.min(fillPercentage, 100)}%` }}
/>
@@ -150,35 +154,25 @@ export function LeagueCard({
{/* Open Slots Badge */}
{hasOpenSlots && (
<Box display="flex" alignItems="center" gap={1} px={2} py={1} rounded="lg" bg="bg-neon-aqua/10" border borderColor="border-neon-aqua/20">
<Box w="1.5" h="1.5" rounded="full" bg="bg-neon-aqua" animate="pulse" />
<Text size="xs" color="text-neon-aqua" weight="medium">
{openSlotsCount} open
<Box display="flex" alignItems="center" gap={1.5} px={2} py={1} rounded="none" bg="primary-accent/5" border borderColor="primary-accent/20">
<Box w="1.5" h="1.5" rounded="full" bg="primary-accent" className="animate-pulse" />
<Text size="xs" color="text-primary-accent" weight="bold" className="uppercase tracking-tighter">
{openSlotsCount} OPEN
</Text>
</Box>
)}
</Box>
{/* Driver count for team leagues */}
{isTeamLeague && (
<Box display="flex" alignItems="center" gap={2} mb={3}>
<Icon icon={LucideUsers} size={3} color="text-gray-500" />
<Text size="xs" color="text-gray-500">
{usedDriverSlots ?? 0}/{maxDrivers ?? '∞'} drivers
</Text>
</Box>
)}
{/* Spacer to push footer to bottom */}
<Box flexGrow={1} />
{/* Footer Info */}
<Box display="flex" alignItems="center" justifyContent="between" pt={3} borderTop style={{ borderColor: 'rgba(38, 38, 38, 0.5)' }} mt="auto">
<Box display="flex" alignItems="center" justifyContent="between" pt={3} borderTop borderColor="border-gray/30" mt="auto">
<Box display="flex" alignItems="center" gap={3}>
{timingSummary && (
<Box display="flex" alignItems="center" gap={1}>
<Box display="flex" alignItems="center" gap={2}>
<Icon icon={LucideCalendar} size={3} color="text-gray-500" />
<Text size="xs" color="text-gray-500">
<Text size="xs" color="text-gray-500" font="mono">
{timingSummary.split('•')[1]?.trim() || timingSummary}
</Text>
</Box>
@@ -186,8 +180,8 @@ export function LeagueCard({
</Box>
{/* View Arrow */}
<Box display="flex" alignItems="center" gap={1} className="group-hover:text-primary-blue transition-colors">
<Text size="xs" color="text-gray-500">View</Text>
<Box display="flex" alignItems="center" gap={1} className="group-hover:text-primary-accent transition-colors">
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">VIEW</Text>
<Icon icon={LucideChevronRight} size={3} color="text-gray-500" className="transition-transform group-hover:translate-x-0.5" />
</Box>
</Box>

View File

@@ -34,8 +34,8 @@ export function Link({
const baseClasses = 'inline-flex items-center transition-colors';
const variantClasses = {
primary: 'text-primary-blue hover:text-primary-blue/80',
secondary: 'text-purple-300 hover:text-purple-400',
primary: 'text-primary-accent hover:text-primary-accent/80',
secondary: 'text-telemetry-aqua hover:text-telemetry-aqua/80',
ghost: 'text-gray-400 hover:text-gray-300'
};

View File

@@ -5,5 +5,5 @@ interface MainContentProps {
}
export function MainContent({ children }: MainContentProps) {
return <div className="pt-16">{children}</div>;
return <div className="pt-16 md:pt-20">{children}</div>;
}

View File

@@ -28,10 +28,10 @@ export function Section({
}: SectionProps) {
const variantClasses = {
default: '',
card: 'bg-iron-gray rounded-lg p-6 border border-charcoal-outline',
highlight: 'bg-gradient-to-r from-blue-900/20 to-blue-700/10 rounded-lg p-6 border border-blue-500/30',
dark: 'bg-iron-gray',
light: 'bg-charcoal-outline'
card: 'bg-panel-gray rounded-none p-6 border border-border-gray',
highlight: 'bg-gradient-to-r from-primary-accent/10 to-transparent rounded-none p-6 border border-primary-accent/30',
dark: 'bg-graphite-black',
light: 'bg-panel-gray'
};
const classes = [

View File

@@ -17,7 +17,7 @@ export function Surface<T extends ElementType = 'div'>({
as,
children,
variant = 'default',
rounded = 'lg',
rounded = 'none',
border = false,
padding = 0,
className = '',
@@ -26,16 +26,16 @@ export function Surface<T extends ElementType = 'div'>({
...props
}: SurfaceProps<T> & ComponentPropsWithoutRef<T>) {
const variantClasses: Record<string, string> = {
default: 'bg-iron-gray',
muted: 'bg-iron-gray/50',
dark: 'bg-deep-graphite',
glass: 'bg-deep-graphite/60 backdrop-blur-md',
'gradient-blue': 'bg-gradient-to-br from-primary-blue/20 via-iron-gray/80 to-deep-graphite',
'gradient-gold': 'bg-gradient-to-br from-yellow-600/20 via-iron-gray/80 to-deep-graphite',
'gradient-purple': 'bg-gradient-to-br from-purple-600/20 via-iron-gray/80 to-deep-graphite',
'gradient-green': 'bg-gradient-to-br from-green-600/20 via-iron-gray/80 to-deep-graphite',
'discord': 'bg-gradient-to-b from-deep-graphite to-iron-gray',
'discord-inner': 'bg-gradient-to-br from-iron-gray via-deep-graphite to-iron-gray'
default: 'bg-panel-gray',
muted: 'bg-panel-gray/40',
dark: 'bg-graphite-black',
glass: 'bg-graphite-black/60 backdrop-blur-md',
'gradient-blue': 'bg-gradient-to-br from-primary-accent/10 via-panel-gray/80 to-graphite-black',
'gradient-gold': 'bg-gradient-to-br from-warning-amber/10 via-panel-gray/80 to-graphite-black',
'gradient-purple': 'bg-gradient-to-br from-purple-600/10 via-panel-gray/80 to-graphite-black',
'gradient-green': 'bg-gradient-to-br from-success-green/10 via-panel-gray/80 to-graphite-black',
'discord': 'bg-gradient-to-b from-graphite-black to-panel-gray',
'discord-inner': 'bg-gradient-to-br from-panel-gray via-graphite-black to-panel-gray'
};
const shadowClasses: Record<string, string> = {
@@ -72,7 +72,7 @@ export function Surface<T extends ElementType = 'div'>({
const classes = [
variantClasses[variant],
roundedClasses[rounded],
border ? 'border border-charcoal-outline' : '',
border ? 'border border-border-gray' : '',
paddingClasses[padding] || 'p-0',
shadowClasses[shadow],
display ? display : '',

View File

@@ -8,8 +8,8 @@ interface TableProps extends HTMLAttributes<HTMLTableElement> {
export function Table({ children, className = '', ...props }: TableProps) {
return (
<Box overflow="auto">
<table className={`w-full ${className}`} {...props}>
<Box overflow="auto" className="border border-border-gray rounded-sm">
<table className={`w-full border-collapse text-left ${className}`} {...props}>
{children}
</table>
</Box>
@@ -22,7 +22,7 @@ interface TableHeadProps extends HTMLAttributes<HTMLTableSectionElement> {
export function TableHead({ children, ...props }: TableHeadProps) {
return (
<thead {...props}>
<thead className="bg-graphite-black border-b border-border-gray" {...props}>
{children}
</thead>
);
@@ -34,7 +34,7 @@ interface TableBodyProps extends HTMLAttributes<HTMLTableSectionElement> {
export function TableBody({ children, ...props }: TableBodyProps) {
return (
<tbody {...props}>
<tbody className="divide-y divide-border-gray/50" {...props}>
{children}
</tbody>
);
@@ -47,8 +47,8 @@ interface TableRowProps extends BoxProps<'tr'> {
}
export function TableRow({ children, className = '', clickable = false, variant = 'default', ...props }: TableRowProps) {
const baseClasses = 'border-b border-charcoal-outline/50 transition-colors';
const variantClasses = variant === 'highlight' ? 'bg-primary-blue/5' : '';
const baseClasses = 'transition-colors duration-150 ease-smooth';
const variantClasses = variant === 'highlight' ? 'bg-primary-accent/5' : 'hover:bg-white/[0.02]';
const classes = [
baseClasses,
variantClasses,
@@ -68,7 +68,7 @@ interface TableHeaderProps extends BoxProps<'th'> {
}
export function TableHeader({ children, className = '', ...props }: TableHeaderProps) {
const baseClasses = 'py-3 px-4 text-xs font-medium text-gray-400 uppercase';
const baseClasses = 'py-2.5 px-4 text-[11px] font-bold text-gray-500 uppercase tracking-wider';
const classes = [baseClasses, className].filter(Boolean).join(' ');
return (
@@ -83,7 +83,7 @@ interface TableCellProps extends BoxProps<'td'> {
}
export function TableCell({ children, className = '', ...props }: TableCellProps) {
const baseClasses = 'py-3 px-4';
const baseClasses = 'py-3 px-4 text-sm text-gray-300';
const classes = [baseClasses, className].filter(Boolean).join(' ');
return (

View File

@@ -47,53 +47,55 @@ export function TeamCard({
onClick,
}: TeamCardProps) {
return (
<Box onClick={onClick} h="full" cursor={onClick ? 'pointer' : 'default'}>
<Card h="full" p={0} display="flex" flexDirection="col" overflow="hidden">
<Box onClick={onClick} h="full" cursor={onClick ? 'pointer' : 'default'} className="group">
<Card h="full" p={0} display="flex" flexDirection="col" overflow="hidden" className="bg-panel-gray/40 border-border-gray/50 hover:border-primary-accent/30 hover:bg-panel-gray/60 transition-all duration-300">
{/* Header with Logo */}
<Box p={4} pb={0}>
<Box p={5} pb={0}>
<Stack direction="row" align="start" gap={4}>
{/* Logo */}
<Box
w="14"
h="14"
rounded="lg"
bg="bg-deep-graphite"
w="16"
h="16"
rounded="none"
bg="graphite-black"
display="flex"
center
overflow="hidden"
border
borderColor="border-charcoal-outline"
borderColor="border-gray/50"
className="relative"
>
{logo ? (
<Image
src={logo}
alt={name}
width={56}
height={56}
width={64}
height={64}
fullWidth
fullHeight
objectFit="cover"
/>
) : (
<PlaceholderImage size={56} />
<PlaceholderImage size={64} />
)}
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent/30" />
</Box>
{/* Title & Badges */}
<Box flexGrow={1} minWidth="0">
<Stack direction="row" align="start" justify="between" gap={2}>
<Heading level={4}>
<Heading level={4} weight="bold" fontSize="lg" className="tracking-tight group-hover:text-primary-accent transition-colors">
{name}
</Heading>
{isRecruiting && (
<Badge variant="success" icon={UserPlus}>
Recruiting
<Badge variant="success" size="xs">
RECRUITING
</Badge>
)}
</Stack>
{/* Performance Level & Category */}
<Stack direction="row" align="center" gap={2} wrap mt={1.5}>
<Stack direction="row" align="center" gap={2} wrap mt={2}>
{performanceBadge}
{specializationContent}
{categoryBadge}
@@ -103,48 +105,43 @@ export function TeamCard({
</Box>
{/* Content */}
<Box p={4} display="flex" flexDirection="col" flexGrow={1}>
<Box p={5} display="flex" flexDirection="col" flexGrow={1}>
{/* Description */}
<Text
size="xs"
color="text-gray-500"
mb={3}
mb={4}
lineClamp={2}
block
leading="relaxed"
style={{ height: '2.5rem' }}
>
{description || 'No description available'}
</Text>
{/* Region & Languages */}
{(region || languagesContent) && (
<Stack direction="row" align="center" gap={2} wrap mb={3}>
<Stack direction="row" align="center" gap={2} wrap mb={4}>
{region && (
<Box
display="flex"
alignItems="center"
gap={1.5}
gap={2}
px={2}
py={1}
rounded="md"
bg="bg-iron-gray/50"
rounded="none"
bg="panel-gray/20"
border
style={{ borderColor: 'rgba(38, 38, 38, 0.3)' }}
borderColor="border-gray/30"
>
<Icon icon={Globe} size={3} color="var(--neon-aqua)" />
<Text size="xs" color="text-gray-400">{region}</Text>
<Icon icon={Globe} size={3} color="text-primary-accent" />
<Text size="xs" color="text-gray-400" weight="bold" className="uppercase tracking-widest">{region}</Text>
</Box>
)}
{languagesContent}
</Stack>
)}
{/* Stats Grid */}
{statsContent && (
<Box display="grid" gridCols={3} gap={2} mb={4}>
{statsContent}
</Box>
)}
{/* Spacer */}
<Box flexGrow={1} />
@@ -153,21 +150,21 @@ export function TeamCard({
display="flex"
alignItems="center"
justifyContent="between"
pt={3}
pt={4}
borderTop
style={{ borderColor: 'rgba(38, 38, 38, 0.5)' }}
borderColor="border-gray/30"
mt="auto"
>
<Stack direction="row" align="center" gap={2}>
<Icon icon={Users} size={3} color="var(--text-gray-600)" />
<Text size="xs" color="text-gray-500">
{memberCount} {memberCount === 1 ? 'member' : 'members'}
<Icon icon={Users} size={3} color="text-gray-500" />
<Text size="xs" color="text-gray-500" font="mono">
{memberCount} {memberCount === 1 ? 'MEMBER' : 'MEMBERS'}
</Text>
</Stack>
<Stack direction="row" align="center" gap={1}>
<Text size="xs" color="text-gray-500">View</Text>
<Icon icon={ChevronRight} size={3} color="var(--text-gray-600)" />
<Stack direction="row" align="center" gap={1} className="group-hover:text-primary-accent transition-colors">
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">VIEW</Text>
<Icon icon={ChevronRight} size={3} color="text-gray-500" className="transition-transform group-hover:translate-x-0.5" />
</Stack>
</Box>
</Box>

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Box } from './Box';
interface TelemetryLineProps {
color?: 'primary' | 'aqua' | 'amber' | 'green' | 'red';
height?: number | string;
animate?: boolean;
opacity?: number;
className?: string;
}
export function TelemetryLine({
color = 'primary',
height = '2px',
animate = false,
opacity = 1,
className = ''
}: TelemetryLineProps) {
const colorMap = {
primary: 'bg-primary-accent',
aqua: 'bg-telemetry-aqua',
amber: 'bg-warning-amber',
green: 'bg-success-green',
red: 'bg-critical-red',
};
return (
<Box
height={height}
fullWidth
className={`${colorMap[color]} ${animate ? 'animate-pulse' : ''} ${className}`}
style={{
opacity,
boxShadow: `0 0 8px ${
color === 'primary' ? '#198CFF' :
color === 'aqua' ? '#4ED4E0' :
color === 'amber' ? '#FFBE4D' :
color === 'green' ? '#6FE37A' : '#E35C5C'
}4D`
}}
/>
);
}

View File

@@ -24,30 +24,35 @@ export function UpcomingRaceItem({
return (
<Surface
variant="muted"
padding={3}
rounded="lg"
padding={4}
rounded="none"
border
borderColor="border-gray/30"
className="hover:border-primary-accent/30 transition-colors bg-panel-gray/20 group"
>
<Text color="text-white" weight="medium" block>
<Stack direction="row" align="center" gap={4}>
<Box w="1" h="8" bg="primary-accent" opacity={0.3} className="group-hover:opacity-100 transition-opacity" />
<Box flexGrow={1}>
<Text color="text-white" weight="bold" block className="tracking-tight">
{track}
</Text>
<Text size="sm" color="text-gray-400" block>
<Text size="xs" color="text-gray-500" block weight="medium" className="uppercase tracking-widest mt-0.5">
{car}
</Text>
<Stack direction="row" align="center" gap={2} mt={1}>
<Text size="xs" color="text-gray-500">
</Box>
<Stack align="end" gap={1}>
<Text size="xs" color="text-gray-400" font="mono" weight="bold">
{formattedDate}
</Text>
<Text size="xs" color="text-gray-500">
</Text>
<Text size="xs" color="text-gray-500">
<Text size="xs" color="text-gray-600" font="mono">
{formattedTime}
</Text>
</Stack>
</Stack>
{isMyLeague && (
<Box mt={1}>
<Badge variant="success">
Your League
<Box mt={3} display="flex" justifyContent="end">
<Badge variant="success" size="xs">
YOUR LEAGUE
</Badge>
</Box>
)}

View File

@@ -1,141 +1,124 @@
# GridPilot Theme — “Motorsport Infrastructure, Smoothly Engineered
🎨 GridPilot Dev Theme — “Precision Racing Minimal
*A precise, professional motorsport interface with premium smoothness — engineered for trust, control, and long-term use.*
The core idea:
A UI that feels like a race engineers dashboard — not a game launcher, not corporate SaaS.
---
## 1. Design Philosophy
1. Identity & Mood
GridPilot should feel like:
- **race control software**, not a game UI
- **infrastructure**, not a startup product
- **engineered**, not styled
- **stable and authoritative**, yet pleasant to use
- **built for years**, not trends
The theme should communicate:
• Precision (like telemetry screens)
• Calm intensity (the feeling before a qualifying lap)
• Authority without ego (used by serious racers)
• Modern minimalism (dev-friendly, clean structure)
• Soft gaming hints (subtle neon touches, not RGB vomit)
The goal is not excitement.
The goal is **confidence**.
It appeals equally to:
• Devs (clean, modular, readable components)
• Gamers (immersive, familiar atmosphere)
• Simracers (motorsport seriousness)
Think:
**“FIA race control x timing screens x modern tooling — with smooth interaction.”**
---
2. Visual Language
## 2. Visual Style
Primary Look
• Dark, matte surfaces
• Thin, crisp separators
• Soft glow accents in blue / cyan / purple
• Avoid aggressive neon or gamer-overload
• Subtle gradient tints (barely visible)
### Core Aesthetic:
- dark, neutral background
- minimal gradients (almost invisible)
- restrained highlights only for meaning
- strict hierarchy
- dense, readable layouts
- smooth transitions only where state changes
Color System
• Base: Near-black graphite (#0C0D0F)
• Surface: Deep charcoal (#141619)
• Outline: Soft steel grey (#23272B)
• Primary Accent: Electric blue (#198CFF)
• Telemetry Accent: Aqua (#4ED4E0)
• Warning: Motorsport amber (#FFBE4D)
Everything should look:
**intentional, measured, and calm.**
Everything should feel instrument-grade, not decorative.
---
### Color Palette (refined)
3. Component Philosophy
- **Graphite Black:** `#0E0F11`
- **Panel Gray:** `#171A1E`
- **Border Gray:** `#22262A`
- **Primary Accent:** `#198CFF` (used sparingly)
- **Success Green:** `#6FE37A`
- **Warning Amber:** `#FFC556`
- **Critical Red:** `#E35C5C`
Cards
• Functional over flashy
• Slight bevel or inset shadow (hint of cockpit panels)
• Dense info, clean hierarchy
No neon.
No playful colors.
Color = meaning, not decoration.
Tables
• High-density
• Thin row dividers
• Highlight on hover only
• Telemetry-coded status colors
---
Buttons
• Flat by default
• Glow + gradient only on hover
• Snappy micro-animation (motorsport feedback)
## 3. Motion & Interaction
Modals
• Soft frosted blur
• Fast open/close animation
• Dimmed pit-lane-lighting vibe
### Animation Philosophy
Motion exists only to:
- confirm an action
- show hierarchy
- indicate state change
Never to impress.
4. Motion & Feedback
### Characteristics:
- short durations (120200ms)
- low amplitude
- no exaggerated easing
- no elastic bounces
- no decorative movement
Racing-inspired, but minimal:
• Short accelerations (fast ease-out)
• No bounces — this isnt a game store
• Hover = slight lift + color pulse
• Loading = progress line (like pit limiter light)
• Switching tabs = sliding underline (chicane motion)
**Motion should feel like a well-damped suspension — not a show car.**
Devs should feel the UI is responsive, not playful.
---
## 4. Where Motion Is Allowed
5. Layout Structure
- button press feedback
- panel open / close
- table row expansion
- state changes (pending → approved → completed)
- subtle loading indicators
Think “Telemetry Workspace”:
• Sidebar = dashboard rail
• Header = control bar
• Content = track map / data table zone
• Right panel = session or context info
If motion does not improve clarity → remove it.
Everything modular, easily replaceable, easy to reason about.
---
## 5. What We Explicitly Avoid
6. Tone for Dev-Facing Text
• Short
• Neutral
• Calm
• Technical
• Real-world language (not corporate slang)
- hero animations
- animated backgrounds
- glowing UI chrome
- playful hover gimmicks
- “app store” aesthetics
- anything that reduces trust
Examples:
• “Add race”
• “Review protest”
• “Session updated”
• “Export standings”
• “Sponsor payout queued”
GridPilot must feel:
**reliable before it feels beautiful.**
No hype. No marketing tone inside the app.
---
## 6. Components
7. Emotional Target
### Tables
- primary UI element
- dense, readable
- fixed column logic
- no playful effects
Users should feel:
• In control
• Supported
• Efficient
• Connected to motorsport culture
• Trusting (nothing looks cheap or gimmicky)
### Cards
- functional grouping
- no visual dominance
- secondary to tables
Its basically:
### Modals
- simple
- fast
- decisive
---
## 7. Typography
- neutral sans-serif
- excellent numeric readability
- no personality fonts
Primary goal:
**information clarity, not brand expression.**
---
## 8. Design Principle Summary
GridPilot is:
- not gamer UI
- not esports branding
- not corporate SaaS
It is:
**modern motorsport infrastructure software.**
“A premium cockpit dashboard… built by people who race and code.”