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 { useParallax } from "@/hooks/useScrollProgress";
import { Box } from '@/ui/Box'; import { Box } from '@/ui/Box';
import { Container } from '@/ui/Container'; import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading'; import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text'; import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { useRef } from 'react'; import { useRef } from 'react';
interface AlternatingSectionProps { interface AlternatingSectionProps {
@@ -25,8 +24,7 @@ export function AlternatingSection({
backgroundVideo backgroundVideo
}: AlternatingSectionProps) { }: AlternatingSectionProps) {
const sectionRef = useRef<HTMLElement>(null); const sectionRef = useRef<HTMLElement>(null);
const bgParallax = useParallax(sectionRef, 0.1);
const bgParallax = useParallax(sectionRef, 0.2);
return ( return (
<Box <Box
@@ -34,96 +32,100 @@ export function AlternatingSection({
ref={sectionRef} ref={sectionRef}
position="relative" position="relative"
overflow="hidden" overflow="hidden"
bg="bg-deep-graphite" bg="graphite-black"
px={{ base: 'calc(1rem+var(--sal))', lg: 8 }} py={{ base: 20, md: 32 }}
py={{ base: 20, sm: 24, md: 32 }} className="border-b border-border-gray"
> >
{backgroundVideo && ( {backgroundVideo && (
<> <Box
position="absolute"
inset="0"
fullWidth
fullHeight
overflow="hidden"
>
<Box <Box
as="video" as="video"
autoPlay autoPlay
loop loop
muted muted
playsInline playsInline
position="absolute"
inset="0"
fullWidth fullWidth
fullHeight fullHeight
objectFit="cover" objectFit="cover"
opacity={0.2} opacity={0.1}
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%)"
> >
<Box as="source" src={backgroundVideo} type="video/mp4" /> <Box as="source" src={backgroundVideo} type="video/mp4" />
</Box> </Box>
{/* Racing red accent for sections with background videos */} {/* Dark overlay to ensure readability */}
<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 position="absolute" inset="0" bg="linear-gradient(to bottom, #0C0D0F, transparent, #0C0D0F)" />
</> </Box>
)} )}
{backgroundImage && !backgroundVideo && ( {backgroundImage && !backgroundVideo && (
<> <Box
position="absolute"
inset="0"
fullWidth
fullHeight
overflow="hidden"
>
<Box <Box
position="absolute" position="absolute"
inset="0" inset="0"
bg={`url(${backgroundImage})`} bg={`url(${backgroundImage})`}
backgroundSize="cover" backgroundSize="cover"
backgroundPosition="center" backgroundPosition="center"
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)" opacity={0.1}
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)" style={{ transform: `translateY(${bgParallax * 0.3}px)` }}
transform={`translateY(${bgParallax * 0.3}px)`}
/> />
{/* Racing red accent for sections with background images */} {/* Dark overlay to ensure readability */}
<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 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}> <Container size="lg" position="relative" zIndex={10}>
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={{ base: 8, md: 12, lg: 16 }} alignItems="center"> <Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={{ base: 12, lg: 24 }} alignItems="center">
{/* Text Content - Always first on mobile, respects layout on desktop */} {/* Text Content */}
<Box <Box
display="flex" display="flex"
flexDirection="column" flexDirection="column"
gap={{ base: 4, md: 6, lg: 8 }} gap={8}
order={{ lg: layout === 'text-right' ? 2 : 1 }} 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}>
{heading} <Box w="12" h="1" bg="primary-accent" />
</Heading> <Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" className="tracking-tight">
<Box display="flex" flexDirection="column" gap={{ base: 3, md: 5 }}> {heading}
<Text size={{ base: 'sm', md: 'base', lg: 'lg' }} color="text-slate-400" weight="light" leading="relaxed"> </Heading>
{description} </Stack>
</Text> <Box className="text-gray-400">
{typeof description === 'string' ? (
<Text size="lg" leading="relaxed" weight="normal">{description}</Text>
) : (
description
)}
</Box> </Box>
</Box> </Box>
{/* Mockup - Always second on mobile, respects layout on desktop */} {/* Mockup */}
<Box <Box
position="relative" position="relative"
order={{ lg: layout === 'text-right' ? 1 : 2 }} 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 <Box
fullWidth fullWidth
minHeight={{ base: '240px', md: '380px', lg: '440px' }} minHeight={{ base: '240px', md: '380px' }}
transition className="overflow-hidden rounded-none border border-border-gray/30 bg-graphite-black"
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%)`}
> >
{mockup} {mockup}
</Box> </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>
</Box> </Box>
</Container> </Container>
</Box> </Box>
); );
} }

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

View File

@@ -53,50 +53,64 @@ function FeatureCard({ feature, index }: { feature: typeof features[0], index: n
display="flex" display="flex"
flexDirection="column" flexDirection="column"
gap={6} 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 aspectRatio="video" fullWidth position="relative" className="bg-graphite-black rounded-none overflow-hidden border border-border-gray/30">
<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" /> <MockupStack index={index}>
<Box position="relative"> <feature.MockupComponent />
<MockupStack index={index}> </MockupStack>
<feature.MockupComponent />
</MockupStack>
</Box>
</Box> </Box>
<Stack gap={3}> <Stack gap={4}>
<Box display="flex" alignItems="center" gap={2}> <Box display="flex" alignItems="center" gap={3}>
<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)' }}> <Box w="1" h="4" bg="primary-accent" />
<Heading level={3} weight="bold" fontSize="xl" className="tracking-tight">
{feature.title} {feature.title}
</Heading> </Heading>
</Box> </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} {feature.description}
</Text> </Text>
</Stack> </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> </Box>
); );
} }
export function FeatureGrid() { export function FeatureGrid() {
return ( return (
<Section variant="default"> <Section className="bg-graphite-black border-b border-border-gray/50 py-24">
<Container position="relative" zIndex={10}> <Container position="relative" zIndex={10}>
<Container size="sm" center> <Stack gap={16}>
<Box> <Box maxWidth="2xl">
<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)' }}> <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>
<Heading level={2} weight="bold" fontSize={{ base: '3xl', md: '4xl' }} className="tracking-tight">
Building for League Racing Building for League Racing
</Heading> </Heading>
<Text size={{ base: 'base', sm: 'lg' }} color="text-gray-400" block mt={{ base: 4, sm: 6 }}> <Text size="lg" color="text-gray-400" block mt={6} leading="relaxed">
These features are in development. Join the community to help shape what gets built first Every feature is designed to reduce friction and increase immersion. Join our Discord to help shape the future of the platform.
</Text> </Text>
</Box> </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' }}> <Box display="grid" gridCols={{ base: 1, md: 2, lg: 3 }} gap={8}>
{features.map((feature, index) => ( {features.map((feature, index) => (
<FeatureCard key={feature.title} feature={feature} index={index} /> <FeatureCard key={feature.title} feature={feature} index={index} />
))} ))}
</Box> </Box>
</Stack>
</Container> </Container>
</Section> </Section>
); );
} }

View File

@@ -1,4 +1,3 @@
import { useRef } from 'react'; import { useRef } from 'react';
import { useParallax } from '@/hooks/useScrollProgress'; import { useParallax } from '@/hooks/useScrollProgress';
import { Box } from '@/ui/Box'; import { Box } from '@/ui/Box';
@@ -7,17 +6,13 @@ import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading'; import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack'; import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text'; import { Text } from '@/ui/Text';
import { Glow } from '@/ui/Glow';
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#'; 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() { export function LandingHero() {
const sectionRef = useRef<HTMLElement>(null); const sectionRef = useRef<HTMLElement>(null);
const bgParallax = useParallax(sectionRef, 0.2);
const bgParallax = useParallax(sectionRef, 0.3);
return ( return (
<Box <Box
@@ -25,11 +20,10 @@ export function LandingHero() {
ref={sectionRef} ref={sectionRef}
position="relative" position="relative"
overflow="hidden" overflow="hidden"
bg="bg-deep-graphite" bg="graphite-black"
px={{ base: 'calc(1.5rem+var(--sal))', lg: 8 }} pt={{ base: 24, md: 32, lg: 40 }}
pt={{ base: 'calc(3rem+var(--sat))', sm: 'calc(4rem+var(--sat))' }} pb={{ base: 16, md: 24, lg: 32 }}
pb={{ base: 16, sm: 24 }} className="border-b border-border-gray"
py={{ md: 32 }}
> >
{/* Background image layer with parallax */} {/* Background image layer with parallax */}
<Box <Box
@@ -38,131 +32,93 @@ export function LandingHero() {
bg="url(/images/header.jpeg)" bg="url(/images/header.jpeg)"
backgroundSize="cover" backgroundSize="cover"
backgroundPosition="center" backgroundPosition="center"
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.35) 40%, transparent 70%)" opacity={0.2}
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.35) 40%, transparent 70%)" style={{ transform: `translateY(${bgParallax * 0.5}px)` }}
transform={`translateY(${bgParallax * 0.5}px)`}
/> />
{/* Racing red accent gradient */} {/* Robust gradient overlay */}
<Box position="absolute" top="0" left="0" right="0" h="1" bg="linear-gradient(to right, transparent, rgba(220, 38, 38, 0.4), transparent)" /> <Box
position="absolute"
inset="0"
bg="linear-gradient(to bottom, #0C0D0F 0%, transparent 50%, #0C0D0F 100%)"
/>
{/* Racing stripes background */} <Box
<Box position="absolute" inset="0" opacity={0.3} bg="racing-stripes" /> position="absolute"
inset="0"
bg="radial-gradient(circle at center, transparent 0%, #0C0D0F 100%)"
opacity={0.6}
/>
{/* Checkered pattern overlay */} <Glow color="primary" size="xl" position="center" opacity={0.1} />
<Box position="absolute" inset="0" opacity={0.2} bg="checkered-pattern" />
{/* Speed lines - left side */} <Container size="lg" position="relative" zIndex={10}>
<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' }} /> <Stack gap={{ base: 8, md: 12 }}>
<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' }} /> <Stack gap={6} maxWidth="3xl">
<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' }} /> <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]">
{/* Carbon fiber accent - bottom */} Precision Racing Infrastructure
<Box position="absolute" bottom="0" left="0" right="0" h="32" opacity={0.5} bg="carbon-fiber" /> </Text>
{/* 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 }}>
<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)'
}}
>
League racing is incredible. What's missing is everything around it.
</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>
{/* 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> </Box>
<Text size={{ base: 'sm', md: 'lg' }} align={{ base: 'left', md: 'center' }} color="text-slate-200" weight="light"> <Heading
The ecosystem isn't built for this. level={1}
fontSize={{ base: '3xl', sm: '4xl', md: '5xl', lg: '7xl' }}
weight="bold"
className="text-white leading-[1.1] tracking-tight"
>
Modern Motorsport <br />
<span className="text-primary-accent">Infrastructure.</span>
</Heading>
<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> </Text>
<Text size={{ base: 'sm', md: 'lg' }} align={{ base: 'left', md: 'center' }} color="text-white" weight="semibold"> </Stack>
GridPilot gives your league racing a real home.
</Text> <Box display="flex" flexDirection={{ base: 'column', sm: 'row' }} gap={4}>
</Box>
<Box display="flex" alignItems="center" justifyContent="center">
<Button <Button
as="a" as="a"
href={discordUrl} href={discordUrl}
variant="primary" variant="primary"
px={8}
py={4}
size="lg" size="lg"
bg="bg-[#5865F2]" className="px-10 rounded-none uppercase tracking-widest text-xs font-bold"
hoverBg="bg-[#4752C4]"
shadow="0 0 20px rgba(88,101,242,0.3)"
hoverScale
transition
aria-label="Join us on Discord"
> >
{/* Discord Logo SVG */} Join the Grid
<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>
</Button> </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> </Box>
</Stack> </Stack>
</Container> </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,21 +5,34 @@ import { Text } from '@/ui/Text';
export function HeaderContent() { export function HeaderContent() {
return ( return (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between h-16 md:h-20">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-6">
<Link href="/" className="inline-flex items-center"> <Link href="/" className="inline-flex items-center group">
<Image <div className="relative">
src="/images/logos/wordmark-rectangle-dark.svg" <Image
alt="GridPilot" src="/images/logos/wordmark-rectangle-dark.svg"
width={160} alt="GridPilot"
height={30} width={160}
className="h-6 w-auto md:h-8" height={30}
priority 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> </Link>
<Text size="sm" color="text-gray-400" className="hidden sm:block font-light"> <div className="hidden sm:flex items-center space-x-2 border-l border-border-gray/50 pl-6">
Making league racing less chaotic <div className="w-1.5 h-1.5 rounded-full bg-primary-accent animate-pulse" />
</Text> <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>
</div> </div>
); );

View File

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

View File

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

View File

@@ -11,7 +11,10 @@ export function DriverProfileMockup() {
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
useEffect(() => { useEffect(() => {
setIsMobile(window.innerWidth < 768); const checkMobile = () => setIsMobile(window.innerWidth < 768);
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []); }, []);
const stats = [ const stats = [
@@ -26,77 +29,58 @@ export function DriverProfileMockup() {
if (isMobile) { if (isMobile) {
return ( 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}> <Stack gap={4}>
<Box> <Box>
<Box display="flex" alignItems="center" justifyContent="between" mb={2}> <Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Box display="flex" alignItems="center" gap={3}> <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="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">
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center"> <Text size="2xl">🏎</Text>
<Text size="2xl">🏎</Text> <Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box>
</Box> </Box>
<Box> <Box>
<Text weight="bold" color="text-white" block>Driver Profile</Text> <Text weight="bold" color="text-white" block className="uppercase tracking-widest">Driver Profile</Text>
<Text size="xs" color="text-white" opacity={0.5} block>Cross-league</Text> <Text size="xs" color="text-gray-500" block font="mono">CROSS-LEAGUE</Text>
</Box> </Box>
</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>
<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 <Box
position="absolute" position="absolute"
insetY="0" insetY="0"
left="0" left="0"
bg="bg-gradient-to-r from-primary-blue to-neon-aqua" bg="primary-accent"
rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ width: '86%' }} style={{ width: '86%' }}
/> />
</Box> </Box>
<Box display="flex" justifyContent="end"> <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> </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}> <Box display="grid" gridCols={3} gap={2}>
{stats.slice(0, 3).map((stat) => ( {stats.slice(0, 3).map((stat) => (
<Box <Box
key={stat.label} key={stat.label}
bg="bg-iron-gray/50" bg="panel-gray/40"
border border
borderColor="border-charcoal-outline" borderColor="border-gray/50"
rounded="lg" rounded="none"
p={2} p={2}
textAlign="center" textAlign="center"
> >
<Text weight="bold" color="text-white" font="mono" block> <Text weight="bold" color="text-white" font="mono" block>
{stat.value}{stat.suffix} {stat.value}{stat.suffix}
</Text> </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>
</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> </Stack>
</Box> </Box>
); );
@@ -120,7 +104,7 @@ export function DriverProfileMockup() {
}; };
return ( 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 <Box
as={motion.div} as={motion.div}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }} 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" 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 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="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">
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center"> <Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏎</Text>
<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> </Box>
<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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-white" color="text-gray-500"
opacity={0.5}
block block
font="mono"
className="uppercase tracking-widest"
> >
Cross-league racing identity Cross-league racing identity
</Text> </Text>
</Box> </Box>
</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>
<Box display="flex" flexWrap="wrap" alignItems="center" gap={{ base: 1, sm: 2, md: 3, lg: 4 }} mb={{ base: 1, sm: 1.5, md: 2 }}> <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} /> <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} /> <AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={3200} />
</Box> </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 <Box
as={motion.div} as={motion.div}
position="absolute" position="absolute"
insetY="0" insetY="0"
left="0" left="0"
bg="bg-gradient-to-r from-primary-blue to-neon-aqua" bg="primary-accent"
rounded="full"
initial={{ width: '0%' }} initial={{ width: '0%' }}
animate={{ width: '86%' }} animate={{ width: '86%' }}
transition={{ delay: shouldReduceMotion ? 0 : 0.4, duration: 0.8, ease: 'easeOut' }} transition={{ delay: shouldReduceMotion ? 0 : 0.4, duration: 0.8, ease: 'easeOut' }}
/> />
</Box> </Box>
<Box display="flex" justifyContent="end" mt={1}>
<Text size="xs" color="text-gray-400">86%</Text>
</Box>
</Box> </Box>
<Box <Box
@@ -182,8 +162,7 @@ export function DriverProfileMockup() {
animate="visible" animate="visible"
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }} 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" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-[0.2em]">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>
<Box display="grid" gridCols={{ base: 2, md: 5 }} gap={{ base: 1.5, sm: 2, md: 3 }}> <Box display="grid" gridCols={{ base: 2, md: 5 }} gap={{ base: 1.5, sm: 2, md: 3 }}>
{stats.map((stat, index) => ( {stats.map((stat, index) => (
@@ -191,10 +170,10 @@ export function DriverProfileMockup() {
key={stat.label} key={stat.label}
as={motion.div} as={motion.div}
variants={itemVariants} variants={itemVariants}
bg="bg-iron-gray/50" bg="panel-gray/40"
border border
borderColor="border-charcoal-outline" borderColor="border-gray/50"
rounded="lg" rounded="none"
p={{ base: 1.5, sm: 2, md: 3 }} p={{ base: 1.5, sm: 2, md: 3 }}
textAlign="center" textAlign="center"
> >
@@ -204,7 +183,7 @@ export function DriverProfileMockup() {
delay={index * 0.1} delay={index * 0.1}
suffix={stat.suffix ?? ''} 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>
))} ))}
</Box> </Box>
@@ -212,22 +191,20 @@ export function DriverProfileMockup() {
<Box <Box
as={motion.div} as={motion.div}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.6 }} transition={{ delay: 0.6 }}
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }}
> >
<Text size="sm" weight="semibold" color="text-white" mb={1} block>Recent Form</Text> <Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-[0.2em]">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="panel-gray/20" border borderColor="border-gray/50" rounded="none" p={{ base: 1.5, sm: 2, md: 3 }} display="flex" alignItems="end" gap={1}>
<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}>
{formData.map((value, i) => ( {formData.map((value, i) => (
<Box <Box
key={i} key={i}
as={motion.div} as={motion.div}
flexGrow={1} flexGrow={1}
bg="bg-gradient-to-t from-performance-green to-primary-blue" bg="primary-accent"
rounded="sm" opacity={0.4 + (i * 0.06)}
rounded="none"
initial={{ height: 0 }} initial={{ height: 0 }}
animate={{ height: `${value}%` }} animate={{ height: `${value}%` }}
transition={{ transition={{
@@ -238,66 +215,6 @@ export function DriverProfileMockup() {
/> />
))} ))}
</Box> </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>
</Box> </Box>
); );
@@ -318,7 +235,7 @@ function AnimatedRating({ shouldReduceMotion, value }: { shouldReduceMotion: boo
}, [shouldReduceMotion, count, value]); }, [shouldReduceMotion, count, value]);
return ( 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>} {shouldReduceMotion ? value : <Box as={motion.span}>{rounded}</Box>}
</Box> </Box>
); );
@@ -348,7 +265,7 @@ function AnimatedCounter({
}, [shouldReduceMotion, count, value, delay]); }, [shouldReduceMotion, count, value, delay]);
return ( 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} {shouldReduceMotion ? value : <Box as={motion.span}>{rounded}</Box>}{suffix}
</Box> </Box>
); );

View File

@@ -43,86 +43,58 @@ export function LeagueDiscoveryMockup() {
if (isMobile) { if (isMobile) {
return ( 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 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"> <Box display="flex" gap={2} flexWrap="wrap">
{['Game', 'Region'].map((filter) => ( {['Game', 'Region'].map((filter) => (
<Box <Box
key={filter} key={filter}
h="6" h="5"
px={3} px={3}
bg="bg-charcoal-outline" bg="panel-gray"
border border
borderColor="border-primary-blue/30" borderColor="primary-accent/30"
rounded="full" rounded="none"
display="flex" display="flex"
alignItems="center" 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> </Box>
</Box> </Box>
<Stack gap={3}> <Stack gap={2}>
{leagues.map((league) => ( {leagues.map((league) => (
<Box <Box
key={league.name} key={league.name}
bg="bg-iron-gray/80" bg="panel-gray/40"
border border
borderColor="border-charcoal-outline" borderColor="border-gray/50"
rounded="lg" rounded="none"
p={3} p={3}
> >
<Box display="flex" alignItems="start" justifyContent="between" mb={2}> <Box display="flex" alignItems="start" justifyContent="between" mb={2}>
<Box display="flex" alignItems="center" gap={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> <Text size="xl">{league.icon}</Text>
</Box> </Box>
<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 display="flex" gap={1}>
<Box as="span" px={1.5} py={0.5} bg="bg-primary-blue/20" rounded="sm"> <Box as="span" px={1.5} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/20">
<Text size="xs" color="text-primary-blue">{league.carClass}</Text> <Text size="xs" color="text-primary-accent" weight="bold" font="mono">{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> </Box>
</Box> </Box>
</Box> </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" justifyContent="between">
<Box display="flex" alignItems="center" gap={1}> <Text size="xs" color="text-gray-500" font="mono">{league.drivers} DRIVERS {league.schedule.toUpperCase()}</Text>
{[...Array(5)].map((_, i) => ( <Box px={2} py={0.5} bg="primary-accent" rounded="none">
<Box <Text size="xs" color="text-white" weight="bold">JOIN</Text>
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>
</Box> </Box>
</Box> </Box>
</Box> </Box>
@@ -141,25 +113,25 @@ export function LeagueDiscoveryMockup() {
}; };
const cardVariants = { const cardVariants = {
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 20 }, hidden: { opacity: 0, x: shouldReduceMotion ? 0 : -20 },
visible: { visible: {
opacity: 1, opacity: 1,
y: 0, x: 0,
transition: { type: 'spring' as const, stiffness: 200, damping: 20 } transition: { duration: 0.4 }
} }
}; };
return ( 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 <Box
as={motion.div} as={motion.div}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }} initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }} 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) => ( {['Game', 'Region', 'Skill'].map((filter, i) => (
<Box <Box
key={filter} key={filter}
@@ -167,16 +139,16 @@ export function LeagueDiscoveryMockup() {
initial={{ opacity: 0, scale: shouldReduceMotion ? 1 : 0.9 }} initial={{ opacity: 0, scale: shouldReduceMotion ? 1 : 0.9 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ delay: shouldReduceMotion ? 0 : i * 0.1 }} transition={{ delay: shouldReduceMotion ? 0 : i * 0.1 }}
h={{ base: 5, sm: 6, md: 7, lg: 8 }} h="6"
px={{ base: 2, sm: 3, md: 4 }} px={4}
bg="bg-charcoal-outline" bg="panel-gray"
border border
borderColor="border-primary-blue/30" borderColor="primary-accent/30"
rounded="full" rounded="none"
display="flex" display="flex"
alignItems="center" 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>
))} ))}
</Box> </Box>
@@ -188,7 +160,7 @@ export function LeagueDiscoveryMockup() {
initial="hidden" initial="hidden"
animate="visible" animate="visible"
> >
<Stack gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}> <Stack gap={2}>
{leagues.map((league, index) => ( {leagues.map((league, index) => (
<Box <Box
key={league.name} key={league.name}
@@ -197,157 +169,90 @@ export function LeagueDiscoveryMockup() {
onHoverStart={() => !shouldReduceMotion && setHoveredIndex(index)} onHoverStart={() => !shouldReduceMotion && setHoveredIndex(index)}
onHoverEnd={() => setHoveredIndex(null)} onHoverEnd={() => setHoveredIndex(null)}
whileHover={shouldReduceMotion ? {} : { whileHover={shouldReduceMotion ? {} : {
scale: 1.02, x: 4,
y: -4, borderColor: '#198CFF30',
transition: { duration: 0.2 } transition: { duration: 0.2 }
}} }}
bg="bg-iron-gray/80" bg="panel-gray/20"
border border
borderColor="border-charcoal-outline" borderColor="border-gray/50"
rounded="lg" rounded="none"
p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} 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="start" justifyContent="between" mb={3}>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 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="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 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> <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> <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 h="2" w="40" bg="white/10" rounded="none" mb={2} />
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}> <Box display="flex" gap={2}>
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-primary-blue/20" rounded="sm"> <Box as="span" px={2} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/20">
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-primary-blue" color="text-primary-accent"
weight="bold"
font="mono"
> >
{league.carClass} {league.carClass}
</Text> </Text>
</Box> </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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-neon-aqua" color="text-telemetry-aqua"
weight="bold"
font="mono"
> >
{league.region} {league.region}
</Text> </Text>
</Box> </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> </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" justifyContent="between">
<Box display="flex" alignItems="center" gap={1}> <Box display="flex" alignItems="center" gap={4}>
{[...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>
))}
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-gray-400" color="text-gray-500"
ml={1} 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> </Text>
</Box> </Box>
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}> <Box display="flex" gap={2}>
<Box <Box
as={motion.button} as={motion.button}
whileHover={shouldReduceMotion ? {} : { scale: 1.05 }} whileHover={shouldReduceMotion ? {} : { scale: 1.05 }}
whileTap={shouldReduceMotion ? {} : { scale: 0.95 }} px={3}
px={{ base: 1.5, sm: 2, md: 3 }} py={1}
py={0.5} bg="primary-accent"
bg="bg-primary-blue"
color="text-white" color="text-white"
rounded="sm" rounded="none"
transition
hoverBg="bg-primary-blue/80"
> >
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
weight="bold"
> >
Join 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
</Text> </Text>
</Box> </Box>
</Box> </Box>

View File

@@ -17,47 +17,45 @@ export function LeagueHomeMockup() {
if (isMobile) { if (isMobile) {
return ( 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}> <Stack gap={4}>
<Box display="flex" alignItems="center" gap={3} mb={2}> <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> <Text size="2xl">🏆</Text>
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
</Box> </Box>
<Box> <Box>
<Text size="base" weight="bold" color="text-white" block>Super GT</Text> <Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">Super GT</Text>
<Text size="xs" color="text-gray-400" block>Round 8/12</Text> <Text size="xs" color="text-gray-500" block font="mono">ROUND 8/12</Text>
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Next Race</Text> <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="bg-iron-gray" rounded="lg" p={3} border borderColor="border-charcoal-outline"> <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="bg-charcoal-outline" rounded border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center"> <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> <Text size="base">🏁</Text>
</Box> </Box>
<Box flexGrow={1}> <Box flexGrow={1}>
<Box h="2.5" w="28" bg="bg-white/10" rounded="sm" mb={2} /> <Box h="2" w="28" bg="white/10" rounded="none" mb={2} />
<Box h="2" w="20" bg="bg-white/5" rounded="sm" /> <Box h="1.5" w="20" bg="white/5" rounded="none" />
</Box> </Box>
<Box w="2" h="2" bg="bg-primary-blue" rounded="full" <Box w="1.5" h="1.5" bg="primary-accent" rounded="full" className="animate-pulse" />
// eslint-disable-next-line gridpilot-rules/component-classification
className="shadow-glow"
/>
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Latest Result</Text> <Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Latest Result</Text>
<Box bg="bg-iron-gray" 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" gap={3} py={2} borderBottom borderColor="border-charcoal-outline"> <Box display="flex" alignItems="center" gap={3} py={2} borderBottom borderColor="border-gray/30">
<Box h="2.5" w="6" bg="bg-white/10" rounded="sm" /> <Box h="1.5" w="6" bg="white/10" rounded="none" />
<Box h="2.5" flexGrow={1} bg="bg-white/10" rounded="sm" /> <Box h="1.5" flexGrow={1} bg="white/10" rounded="none" />
<Box h="2.5" w="10" bg="bg-white/10" rounded="sm" /> <Box h="1.5" w="10" bg="white/10" rounded="none" />
</Box> </Box>
<Box display="flex" alignItems="center" gap={3} py={2}> <Box display="flex" alignItems="center" gap={3} py={2}>
<Box h="2" w="6" bg="bg-white/5" rounded="sm" /> <Box h="1.5" w="6" bg="white/5" rounded="none" />
<Box h="2" flexGrow={1} bg="bg-white/5" rounded="sm" /> <Box h="1.5" flexGrow={1} bg="white/5" rounded="none" />
<Box h="2" w="10" bg="bg-performance-green/20" rounded="sm" /> <Box h="1.5" w="10" bg="success-green/20" rounded="none" />
</Box> </Box>
</Box> </Box>
</Box> </Box>
@@ -80,7 +78,7 @@ export function LeagueHomeMockup() {
}; };
return ( 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 <Box
as={motion.div} as={motion.div}
variants={containerVariants} variants={containerVariants}
@@ -90,44 +88,27 @@ export function LeagueHomeMockup() {
<Stack gap={{ base: 1.5, sm: 3, md: 4, lg: 6 }}> <Stack gap={{ base: 1.5, sm: 3, md: 4, lg: 6 }}>
<Box as={motion.div} variants={itemVariants}> <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 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> <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>
<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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '9px' }} 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> </Text>
</Box> </Box>
</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>
<Box as={motion.div} variants={itemVariants}> <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 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>
<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>
<Stack gap={{ base: 1.5, sm: 2, md: 3 }}> <Stack gap={{ base: 1.5, sm: 2, md: 3 }}>
{[1, 2, 3].map((i) => ( {[1, 2, 3].map((i) => (
<Box <Box
@@ -137,25 +118,23 @@ export function LeagueHomeMockup() {
display="flex" display="flex"
alignItems="center" alignItems="center"
gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
bg="bg-iron-gray" bg="panel-gray/40"
rounded="lg" rounded="none"
p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} p={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
border border
borderColor="border-charcoal-outline" borderColor="border-gray/50"
shadow="inset_0_1px_2px_rgba(0,0,0,0.2)"
whileHover={shouldReduceMotion ? {} : { whileHover={shouldReduceMotion ? {} : {
y: -2, x: 4,
boxShadow: '0 4px 24px rgba(0,0,0,0.4), 0 0 20px rgba(25,140,255,0.3)', borderColor: '#198CFF50',
transition: { duration: 0.15 } 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> <Text size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }}>🏁</Text>
</Box> </Box>
<Box flexGrow={1}> <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="1.5" w={i === 1 ? "32" : "24"} bg="white/10" rounded="none" 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" w="16" bg="white/5" rounded="none" />
</Box> </Box>
{i === 1 && ( {i === 1 && (
<Box <Box
@@ -163,19 +142,11 @@ export function LeagueHomeMockup() {
position="absolute" position="absolute"
right="4" right="4"
animate={shouldReduceMotion ? {} : { animate={shouldReduceMotion ? {} : {
scale: [1, 1.2, 1], opacity: [1, 0.4, 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)'
]
}} }}
transition={{ duration: 2, repeat: Infinity }} 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" <Box w="1.5" h="1.5" bg="primary-accent" rounded="full" />
// eslint-disable-next-line gridpilot-rules/component-classification
className="shadow-glow"
/>
</Box> </Box>
)} )}
</Box> </Box>
@@ -184,28 +155,18 @@ export function LeagueHomeMockup() {
</Box> </Box>
<Box as={motion.div} variants={itemVariants}> <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 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>
<Text <Box bg="panel-gray/20" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} border borderColor="border-gray/50">
// eslint-disable-next-line gridpilot-rules/component-classification <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">
style={{ fontSize: '8px' }} <Box h="1" w="6" bg="white/10" rounded="none" />
color="text-white" <Box h="1" flexGrow={1} bg="white/10" rounded="none" />
opacity={0.5} <Box h="1" w="10" bg="white/10" rounded="none" />
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" />
</Box> </Box>
{[1, 2].map((i) => ( {[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 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="1" w="6" bg="white/5" rounded="none" />
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} flexGrow={1} bg="bg-white/5" rounded="sm" /> <Box h="1" flexGrow={1} bg="white/5" rounded="none" />
<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="10" bg={i === 1 ? "success-green/20" : "white/5"} rounded="none" />
</Box> </Box>
))} ))}
</Box> </Box>

View File

@@ -30,7 +30,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
return ( 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="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 <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={{ style={{
rotate: `${rotation1}deg`, rotate: `${rotation1}deg`,
zIndex: 1, zIndex: 1,
@@ -44,7 +44,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/> />
<div <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={{ style={{
rotate: `${rotation2}deg`, rotate: `${rotation2}deg`,
zIndex: 2, zIndex: 2,
@@ -58,7 +58,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/> />
<div <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={{ style={{
boxShadow: '0 20px 60px rgba(0,0,0,0.45)', boxShadow: '0 20px 60px rgba(0,0,0,0.45)',
}} }}
@@ -73,7 +73,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
return ( 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="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 <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={{ style={{
rotate: `${rotation1}deg`, rotate: `${rotation1}deg`,
zIndex: 1, zIndex: 1,
@@ -89,7 +89,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/> />
<motion.div <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={{ style={{
rotate: `${rotation2}deg`, rotate: `${rotation2}deg`,
zIndex: 2, zIndex: 2,
@@ -105,7 +105,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
/> />
<motion.div <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={{ style={{
boxShadow: '0 20px 60px rgba(0,0,0,0.45)', 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 }} transition={{ duration: 0.4, delay: 0.2 }}
> >
<motion.div <motion.div
className="absolute inset-0 pointer-events-none rounded-lg" className="absolute inset-0 pointer-events-none rounded-none"
whileHover={ whileHover={
shouldReduceMotion 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 }, transition: { duration: 0.2 },
} }
} }

View File

@@ -21,35 +21,32 @@ export function ProtestWorkflowMockup() {
{ {
name: 'Submit', name: 'Submit',
status: 'pending', 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' 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', name: 'Review',
status: 'active', 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' 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', name: 'Resolve',
status: 'resolved', status: 'resolved',
color: 'performance-green',
icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z' 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) { switch (status) {
case 'pending': 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/20 border-warning-amber text-warning-amber'; case 'active': return 'bg-warning-amber/10 border-warning-amber text-warning-amber';
case 'resolved': return 'bg-performance-green/20 border-performance-green text-performance-green'; case 'resolved': return 'bg-success-green/10 border-success-green text-success-green';
default: return 'bg-charcoal-outline border-charcoal-outline text-gray-500'; default: return 'bg-panel-gray border-gray-700 text-gray-600';
} }
}; };
if (isMobile) { if (isMobile) {
return ( 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}> <Box display="flex" alignItems="center" justifyContent="center" gap={3}>
{steps.map((step, i) => ( {steps.map((step, i) => (
<Box key={step.name} display="flex" alignItems="center"> <Box key={step.name} display="flex" alignItems="center">
@@ -57,41 +54,36 @@ export function ProtestWorkflowMockup() {
<Box <Box
w="10" w="10"
h="10" h="10"
rounded="lg" rounded="none"
display="flex" display="flex"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
mb={1} mb={1}
border border
borderWidth="2px" borderWidth="1px"
// eslint-disable-next-line gridpilot-rules/component-classification className={getStatusStyles(step.status)}
className={getStatusColor(step.status)}
> >
<Box as="svg" w="5" h="5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <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} /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={step.icon} />
</Box> </Box>
</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> </Box>
{i < steps.length - 1 && ( {i < steps.length - 1 && (
<Box as="svg" w="4" h="4" mx={1} viewBox="0 0 24 24" fill="none"> <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="#198CFF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#43C9E6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</Box> </Box>
)} )}
</Box> </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 <Box
position="absolute" position="absolute"
insetY="0" insetY="0"
left="0" left="0"
bg="bg-gradient-to-r from-neon-aqua to-primary-blue" bg="primary-accent"
rounded="full"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ width: `${((activeStep + 1) / steps.length) * 100}%` }} style={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
/> />
</Box> </Box>
@@ -100,21 +92,19 @@ export function ProtestWorkflowMockup() {
} }
const stepVariants = { const stepVariants = {
hidden: { opacity: 0, scale: shouldReduceMotion ? 1 : 0.8 }, hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
visible: (i: number) => ({ visible: (i: number) => ({
opacity: 1, opacity: 1,
scale: 1, y: 0,
transition: { transition: {
delay: shouldReduceMotion ? 0 : i * 0.2, delay: shouldReduceMotion ? 0 : i * 0.2,
type: 'spring' as const, duration: 0.4
stiffness: 200,
damping: 20
} }
}) })
}; };
return ( 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 }}> <Box display="flex" flexDirection={{ base: 'col', md: 'row' }} alignItems="center" justifyContent="center" gap={{ base: 2, sm: 3, md: 4 }}>
{steps.map((step, i) => ( {steps.map((step, i) => (
<Box key={step.name} display="flex" alignItems="center" flexShrink={0}> <Box key={step.name} display="flex" alignItems="center" flexShrink={0}>
@@ -134,84 +124,48 @@ export function ProtestWorkflowMockup() {
position="relative" position="relative"
w={{ base: 8, sm: 10, md: 12, lg: 14 }} w={{ base: 8, sm: 10, md: 12, lg: 14 }}
h={{ base: 8, sm: 10, md: 12, lg: 14 }} h={{ base: 8, sm: 10, md: 12, lg: 14 }}
rounded="lg" rounded="none"
display="flex" display="flex"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
mb={{ base: 1, sm: 1.5, md: 2 }} mb={{ base: 1, sm: 1.5, md: 2 }}
border border
borderWidth="2px" borderWidth="1px"
// eslint-disable-next-line gridpilot-rules/component-classification className={getStatusStyles(step.status)}
className={getStatusColor(step.status)}
whileHover={shouldReduceMotion ? {} : { whileHover={shouldReduceMotion ? {} : {
scale: 1.1, scale: 1.05,
boxShadow: step.status === 'active' borderColor: '#198CFF',
? '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)',
transition: { duration: 0.2 } 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"> <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} /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={step.icon} />
</Box> </Box>
{step.status === 'active' && ( {step.status === 'active' && (
<Box <Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="warning-amber" />
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> </Box>
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }} style={{ fontSize: '10px' }}
color="text-white" color="text-white"
opacity={0.7} weight="bold"
textAlign="center" textAlign="center"
mb={0.5} className="uppercase tracking-widest"
> >
{step.name} {step.name}
</Text> </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> </Box>
{i < steps.length - 1 && ( {i < steps.length - 1 && (
<Box <Box
// eslint-disable-next-line gridpilot-rules/component-classification
className="hidden md:block" className="hidden md:block"
position="relative" 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"> <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="#198CFF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" opacity={0.5} />
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#43C9E6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</Box> </Box>
</Box> </Box>
)} )}
@@ -225,9 +179,9 @@ export function ProtestWorkflowMockup() {
animate={{ opacity: 1, scaleX: 1 }} animate={{ opacity: 1, scaleX: 1 }}
transition={{ delay: shouldReduceMotion ? 0 : 0.8, duration: 0.6 }} transition={{ delay: shouldReduceMotion ? 0 : 0.8, duration: 0.6 }}
position="relative" position="relative"
h={{ base: 0.5, md: 1 }} h="1"
bg="bg-charcoal-outline" bg="white/5"
rounded="full" rounded="none"
overflow="hidden" overflow="hidden"
> >
<Box <Box
@@ -235,8 +189,7 @@ export function ProtestWorkflowMockup() {
position="absolute" position="absolute"
insetY="0" insetY="0"
left="0" left="0"
bg="bg-gradient-to-r from-neon-aqua to-primary-blue" bg="primary-accent"
rounded="full"
initial={{ width: '0%' }} initial={{ width: '0%' }}
animate={{ width: `${((activeStep + 1) / steps.length) * 100}%` }} animate={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
transition={{ duration: 0.5, ease: 'easeOut' }} transition={{ duration: 0.5, ease: 'easeOut' }}

View File

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

View File

@@ -18,30 +18,30 @@ export function SimPlatformMockup() {
// Simple mobile version - just the essence of cross-platform // Simple mobile version - just the essence of cross-platform
if (isMobile) { if (isMobile) {
return ( 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"> <Stack gap={3} w="full">
{/* Active Platform - Clean */} {/* 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 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 // eslint-disable-next-line gridpilot-rules/component-classification
className="animate-pulse" className="animate-pulse"
/> />
</Box> </Box>
<Box display="flex" alignItems="center" gap={3}> <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}> <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-blue">iR</Text> <Text size="2xl" weight="bold" color="text-primary-accent" font="mono">iR</Text>
</Box> </Box>
<Box> <Box>
<Text size="base" weight="semibold" color="text-white" block>iRacing</Text> <Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">iRacing</Text>
<Text size="xs" color="text-performance-green" block>Active</Text> <Text size="xs" color="text-success-green" block font="mono">ACTIVE</Text>
</Box> </Box>
</Box> </Box>
</Box> </Box>
{/* Simple "more coming" indicator */} {/* Simple "more coming" indicator */}
<Box bg="bg-iron-gray/30" rounded="xl" p={3} border borderColor="border-charcoal-outline/50"> <Box bg="panel-gray/20" rounded="none" p={3} border borderStyle="dashed" borderColor="border-gray/30">
<Text size="xs" textAlign="center" color="text-slate-500" block>More platforms coming</Text> <Text size="xs" textAlign="center" color="text-gray-600" block font="mono" uppercase tracking-widest>More platforms coming</Text>
</Box> </Box>
</Stack> </Stack>
</Box> </Box>
@@ -51,150 +51,161 @@ export function SimPlatformMockup() {
// Desktop version // Desktop version
return ( return (
<Box position="relative" fullWidth maxWidth="3xl" mx="auto"> <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 }}> <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"> <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="semibold" color="text-slate-300">Platform Support</Text> <Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-400" className="uppercase tracking-widest">Platform Support</Text>
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-500" color="text-gray-600"
font="mono"
> >
Active: 1 | Planned: 3 ACTIVE: 1 | PLANNED: 3
</Text> </Text>
</Box> </Box>
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={{ base: 1.5, sm: 2, md: 3 }}> <Box display="grid" gridCols={{ base: 1, md: 2 }} gap={{ base: 1.5, sm: 2, md: 3 }}>
{/* iRacing - Active */} {/* 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 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 // eslint-disable-next-line gridpilot-rules/component-classification
className="animate-pulse" className="animate-pulse"
/> />
</Box> </Box>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}> <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"> <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-blue">iR</Text> <Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-primary-accent" font="mono">iR</Text>
</Box> </Box>
<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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-performance-green" color="text-success-green"
block block
font="mono"
> >
Active ACTIVE
</Text> </Text>
</Box> </Box>
</Box> </Box>
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-400" color="text-gray-500"
mt={{ base: 1.5, sm: 2, md: 3 }} mt={{ base: 1.5, sm: 2, md: 3 }}
block block
font="mono"
> >
Full integration FULL INTEGRATION
</Text> </Text>
</Box> </Box>
{/* ACC - Future */} {/* 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 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"> <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-slate-600">AC</Text> <Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">AC</Text>
</Box> </Box>
<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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-600" color="text-gray-700"
block block
font="mono"
> >
Planned PLANNED
</Text> </Text>
</Box> </Box>
</Box> </Box>
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-600" color="text-gray-700"
mt={{ base: 1.5, sm: 2, md: 3 }} mt={{ base: 1.5, sm: 2, md: 3 }}
block block
font="mono"
> >
Coming later COMING LATER
</Text> </Text>
</Box> </Box>
{/* rFactor 2 - Future */} {/* 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 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"> <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-slate-600">rF</Text> <Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">rF</Text>
</Box> </Box>
<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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-600" color="text-gray-700"
block block
font="mono"
> >
Planned PLANNED
</Text> </Text>
</Box> </Box>
</Box> </Box>
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-600" color="text-gray-700"
mt={{ base: 1.5, sm: 2, md: 3 }} mt={{ base: 1.5, sm: 2, md: 3 }}
block block
font="mono"
> >
Coming later COMING LATER
</Text> </Text>
</Box> </Box>
{/* LMU - Future */} {/* 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 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"> <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-slate-600">LM</Text> <Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">LM</Text>
</Box> </Box>
<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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-600" color="text-gray-700"
block block
font="mono"
> >
Planned PLANNED
</Text> </Text>
</Box> </Box>
</Box> </Box>
<Text <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-600" color="text-gray-700"
mt={{ base: 1.5, sm: 2, md: 3 }} mt={{ base: 1.5, sm: 2, md: 3 }}
block block
font="mono"
> >
Coming later COMING LATER
</Text> </Text>
</Box> </Box>
</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 <Text
// eslint-disable-next-line gridpilot-rules/component-classification // eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }} style={{ fontSize: '10px' }}
color="text-slate-500" color="text-gray-600"
textAlign="center" textAlign="center"
block block
font="mono"
className="uppercase tracking-widest"
> >
Your identity stays with you across platforms Your identity stays with you across platforms
</Text> </Text>

View File

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

View File

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

View File

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

View File

@@ -10,41 +10,39 @@ module.exports = {
], ],
theme: { theme: {
extend: { extend: {
backgroundImage: {
'radial-gradient': 'radial-gradient(var(--tw-gradient-stops))',
},
colors: { colors: {
'deep-graphite': '#0E0F11', 'graphite-black': '#0C0D0F',
'iron-gray': '#181B1F', 'panel-gray': '#141619',
'charcoal-outline': '#22262A', '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', 'primary-blue': '#198CFF',
'performance-green': '#6FE37A', 'performance-green': '#6FE37A',
'warning-amber': '#FFC556', 'racing-red': '#E35C5C',
'neon-aqua': '#43C9E6',
'racing-red': '#E31E24',
'carbon-black': '#0A0A0A',
'metallic-silver': '#C0C0C8',
}, },
fontFamily: { fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'], sans: ['Inter', 'system-ui', 'sans-serif'],
}, },
boxShadow: { boxShadow: {
'glow': '0 0 20px rgba(25, 140, 255, 0.3)', 'card': '0 4px 12px rgba(0, 0, 0, 0.2)',
'glow-strong': '0 0 28px rgba(25, 140, 255, 0.5)', },
'card': '0 8px 24px rgba(0, 0, 0, 0.12)', transitionDuration: {
'racing': '0 4px 16px rgba(227, 30, 36, 0.15)', 'smooth': '150ms',
}, },
transitionTimingFunction: { transitionTimingFunction: {
'spring': 'cubic-bezier(0.34, 1.56, 0.64, 1)', 'smooth': 'cubic-bezier(0.4, 0, 0.2, 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' },
},
}, },
}, },
}, },
plugins: [], plugins: [],
} }

View File

@@ -4,27 +4,20 @@ import { AlternatingSection } from '@/components/landing/AlternatingSection';
import { FAQ } from '@/components/landing/FAQ'; import { FAQ } from '@/components/landing/FAQ';
import { FeatureGrid } from '@/components/landing/FeatureGrid'; import { FeatureGrid } from '@/components/landing/FeatureGrid';
import { LandingHero } from '@/components/landing/LandingHero'; 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 { CareerProgressionMockup } from '@/components/mockups/CareerProgressionMockup';
import { CompanionAutomationMockup } from '@/components/mockups/CompanionAutomationMockup'; import { CompanionAutomationMockup } from '@/components/mockups/CompanionAutomationMockup';
import { RaceHistoryMockup } from '@/components/mockups/RaceHistoryMockup'; import { RaceHistoryMockup } from '@/components/mockups/RaceHistoryMockup';
import { SimPlatformMockup } from '@/components/mockups/SimPlatformMockup'; import { SimPlatformMockup } from '@/components/mockups/SimPlatformMockup';
import { ModeGuard } from '@/components/shared/ModeGuard'; import { ModeGuard } from '@/components/shared/ModeGuard';
import { routes } from '@/lib/routing/RouteConfig';
import { getMediaUrl } from '@/lib/utilities/media';
import { Box } from '@/ui/Box'; 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 { DiscordCTA } from '@/ui/DiscordCTA';
import { Footer } from '@/ui/Footer'; 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 { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text'; import { Text } from '@/ui/Text';
import { TelemetryLine } from '@/ui/TelemetryLine';
import { Glow } from '@/ui/Glow';
export interface HomeViewData { export interface HomeViewData {
isAlpha: boolean; isAlpha: boolean;
@@ -53,220 +46,132 @@ interface HomeTemplateProps {
export function HomeTemplate({ viewData }: HomeTemplateProps) { export function HomeTemplate({ viewData }: HomeTemplateProps) {
return ( 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 /> <LandingHero />
<TelemetryLine color="primary" height="1px" opacity={0.3} />
{/* Section 1: A Persistent Identity */} {/* Section 1: A Persistent Identity */}
<AlternatingSection <Box position="relative" bg="graphite-black">
heading="A Persistent Identity" <Glow color="aqua" size="lg" position="bottom-left" opacity={0.03} />
backgroundVideo="/gameplay.mp4" <AlternatingSection
description={ heading="A Persistent Identity"
<Stack gap={4}> backgroundVideo="/gameplay.mp4"
<Text> description={
Your races, your seasons, your progress &mdash; finally in one place. <Stack gap={8}>
</Text> <Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
<Stack gap={3}> Your races, your seasons, your progress &mdash; finally in one place.
<FeatureItem text="Lifetime stats and season history across all your leagues" /> </Text>
<FeatureItem text="Track your performance, consistency, and team contributions" /> <Box display="grid" gridCols={1} gap={3}>
<FeatureItem text="Your own rating that reflects real league competition" /> <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" />
</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> </Stack>
<Text> }
iRacing gives you physics. GridPilot gives you a career. mockup={<CareerProgressionMockup />}
</Text> layout="text-left"
</Stack> />
} </Box>
mockup={<CareerProgressionMockup />}
layout="text-left"
/>
<FeatureGrid /> <FeatureGrid />
{/* Section 2: Results That Actually Stay */} {/* Section 2: Results That Actually Stay */}
<AlternatingSection <Box position="relative" bg="graphite-black">
heading="Results That Actually Stay" <Glow color="primary" size="lg" position="top-right" opacity={0.03} />
backgroundImage="/images/ff1600.jpeg" <AlternatingSection
description={ heading="Results That Actually Stay"
<Stack gap={4}> backgroundImage="/images/ff1600.jpeg"
<Text size="sm"> description={
Every race you run stays with you. <Stack gap={8}>
</Text> <Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
<Stack gap={3}> Every race you run stays with you.
<ResultItem text="Your stats, your team, your story &mdash; all connected" color="#ef4444" /> </Text>
<ResultItem text="One race result updates your profile, team points, rating, and season history" color="#ef4444" /> <Box display="grid" gridCols={1} gap={3}>
<ResultItem text="No more fragmented data across spreadsheets and forums" color="#ef4444" /> <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> </Stack>
<Text size="sm"> }
Your racing career, finally in one place. mockup={<RaceHistoryMockup />}
</Text> layout="text-right"
</Stack> />
} </Box>
mockup={<RaceHistoryMockup />}
layout="text-right" <TelemetryLine color="aqua" height="1px" opacity={0.2} />
/>
{/* Section 3: Automatic Session Creation */} {/* Section 3: Automatic Session Creation */}
<AlternatingSection <Box position="relative" bg="graphite-black">
heading="Automatic Session Creation" <Glow color="amber" size="lg" position="bottom-right" opacity={0.02} />
description={ <AlternatingSection
<Stack gap={4}> heading="Automatic Session Creation"
<Text size="sm"> description={
Setting up league races used to mean clicking through iRacing&apos;s wizard 20 times. <Stack gap={8}>
</Text> <Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
<Stack gap={3}> Setting up league races used to mean clicking through iRacing&apos;s wizard 20 times.
<StepItem step={1} text="Our companion app syncs with your league schedule" /> </Text>
<StepItem step={2} text="When it's race time, it creates the iRacing session automatically" /> <Box display="grid" gridCols={1} gap={3}>
<StepItem step={3} text="No clicking through wizards. No manual setup" /> <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" />
</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> </Stack>
<Text size="sm"> }
Automation instead of repetition. mockup={<CompanionAutomationMockup />}
</Text> layout="text-left"
</Stack> />
} </Box>
mockup={<CompanionAutomationMockup />}
layout="text-left"
/>
{/* Section 4: Game-Agnostic Platform */} {/* Section 4: Game-Agnostic Platform */}
<AlternatingSection <Box position="relative" bg="graphite-black">
heading="Built for iRacing. Ready for the future." <Glow color="primary" size="xl" position="center" opacity={0.03} />
backgroundImage="/images/lmp3.jpeg" <AlternatingSection
description={ heading="Built for iRacing. Ready for the future."
<Stack gap={4}> backgroundImage="/images/lmp3.jpeg"
<Text size="sm"> description={
Right now, we&apos;re focused on making iRacing league racing better. <Stack gap={8}>
</Text> <Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
<Text size="sm"> Right now, we&apos;re focused on making iRacing league racing better.
But sims come and go. Your leagues, your teams, your rating &mdash; those stay. </Text>
</Text> <Text color="text-gray-400" leading="relaxed">
<Text size="sm"> But sims come and go. Your leagues, your teams, your rating &mdash; those stay.
GridPilot is built to outlast any single platform. </Text>
</Text> <Box borderLeft borderStyle="solid" borderColor="border-gray" pl={4} py={1} bg="white/5">
<Text size="sm"> <Text color="text-gray-500" font="mono" size="xs" uppercase letterSpacing="widest" leading="relaxed">
When the next sim arrives, your competitive identity moves with you. GridPilot is built to outlast any single platform. When the next sim arrives, your competitive identity moves with you.
</Text> </Text>
</Stack> </Box>
} </Stack>
mockup={<SimPlatformMockup />} }
layout="text-right" mockup={<SimPlatformMockup />}
/> layout="text-right"
/>
</Box>
{/* Alpha-only discovery section */} {/* Alpha-only discovery section */}
<ModeGuard feature="alpha_discovery"> <ModeGuard feature="alpha_discovery">
<Container size="lg" py={12}> <Box bg="panel-gray/20" py={20} borderTop borderBottom borderColor="border-gray/50" position="relative">
<Stack gap={8}> <Glow color="aqua" size="xl" position="center" opacity={0.02} />
<Box> <DiscoverySection viewData={viewData} />
<Heading level={2}>Discover the grid</Heading> </Box>
<Text size="sm" color="text-gray-400" block mt={2}>
Explore leagues, teams, and races that make up the GridPilot ecosystem.
</Text>
</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> </ModeGuard>
<DiscordCTA /> <DiscordCTA />

View File

@@ -16,21 +16,21 @@ interface BadgeProps {
} }
export function Badge({ children, className = '', variant = 'default', size = 'sm', icon, style, bg, color, borderColor }: 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 = { const sizeClasses = {
xs: 'px-1.5 py-0.5 text-[10px]', xs: 'px-1.5 py-0.5 text-[9px]',
sm: 'px-2.5 py-1 text-xs', sm: 'px-2 py-0.5 text-[10px]',
md: 'px-3 py-1.5 text-sm' md: 'px-3 py-1 text-xs'
}; };
const variantClasses = { const variantClasses = {
default: 'bg-gray-500/10 border-gray-500/30 text-gray-400', default: 'bg-gray-500/10 border-gray-500/30 text-gray-400',
primary: 'bg-primary-blue/10 border-primary-blue/30 text-primary-blue', primary: 'bg-primary-accent/10 border-primary-accent/30 text-primary-accent',
success: 'bg-performance-green/10 border-performance-green/30 text-performance-green', success: 'bg-success-green/10 border-success-green/30 text-success-green',
warning: 'bg-warning-amber/10 border-warning-amber/30 text-warning-amber', warning: 'bg-warning-amber/10 border-warning-amber/30 text-warning-amber',
danger: 'bg-red-600/10 border-red-600/30 text-red-500', danger: 'bg-critical-red/10 border-critical-red/30 text-critical-red',
info: 'bg-neon-aqua/10 border-neon-aqua/30 text-neon-aqua' info: 'bg-telemetry-aqua/10 border-telemetry-aqua/30 text-telemetry-aqua'
}; };
const classes = [ const classes = [

View File

@@ -6,7 +6,7 @@ interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'as'
children: ReactNode; children: ReactNode;
onClick?: MouseEventHandler<HTMLButtonElement>; onClick?: MouseEventHandler<HTMLButtonElement>;
className?: string; className?: string;
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'race-performance' | 'race-final' | 'discord'; variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'race-final' | 'discord';
size?: 'sm' | 'md' | 'lg'; size?: 'sm' | 'md' | 'lg';
disabled?: boolean; disabled?: boolean;
type?: 'button' | 'submit' | 'reset'; type?: 'button' | 'submit' | 'reset';
@@ -34,25 +34,24 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
rel, rel,
...props ...props
}, ref) => { }, 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 = { 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)]', 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-iron-gray text-white border border-charcoal-outline hover:bg-iron-gray/80 focus-visible:outline-primary-blue', secondary: 'bg-panel-gray text-white border border-border-gray hover:bg-border-gray/50 focus-visible:outline-primary-accent',
danger: 'bg-red-600 text-white hover:bg-red-700 focus-visible:outline-red-600', danger: 'bg-critical-red text-white hover:bg-critical-red/90 focus-visible:outline-critical-red',
ghost: 'bg-transparent text-gray-400 hover:bg-gray-800 focus-visible:outline-gray-400', ghost: 'bg-transparent text-gray-400 hover:text-white hover:bg-white/5 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-success-green text-graphite-black hover:bg-success-green/90 focus-visible:outline-success-green',
'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] focus-visible:outline-[#5865F2]',
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]'
}; };
const sizeClasses = { const sizeClasses = {
sm: 'min-h-[36px] px-3 py-1.5 text-xs', sm: 'min-h-[32px] px-3 py-1 text-xs font-medium',
md: 'min-h-[44px] px-4 py-2 text-sm', md: 'min-h-[40px] px-4 py-2 text-sm font-medium',
lg: 'min-h-[52px] px-6 py-3 text-base' 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 widthClasses = fullWidth ? 'w-full' : '';
const classes = [ const classes = [

View File

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

View File

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

View File

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

View File

@@ -8,41 +8,31 @@ const xUrl = process.env.NEXT_PUBLIC_X_URL || '#';
export function Footer() { export function Footer() {
return ( return (
<Box as="footer" position="relative" bg="bg-deep-graphite"> <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, var(--primary-blue), transparent)" /> <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 */} {/* Racing stripe accent */}
<Box <Box
display="flex" display="flex"
gap={1} gap={2}
mb={{ base: 2, md: 4, lg: 6 }} mb={8}
justifyContent="center" 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="12" h="1" bg="white" opacity={0.1} />
<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="12" h="1" bg="primary-accent" />
<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> </Box>
{/* Personal message */} {/* Personal message */}
<Box <Box
textAlign="center" textAlign="center"
mb={{ base: 3, md: 6, lg: 8 }} mb={12}
> >
<Box mb={2} display="flex" justifyContent="center"> <Text size="sm" color="text-gray-300" block mb={2} weight="bold" className="tracking-wide">
<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 }}>
🏁 Built by a sim racer, for sim racers 🏁 Built by a sim racer, for sim racers
</Text> </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. Just a fellow racer tired of spreadsheets and chaos. GridPilot is my passion project to make league racing actually fun again.
</Text> </Text>
</Box> </Box>
@@ -51,50 +41,39 @@ export function Footer() {
<Box <Box
display="flex" display="flex"
justifyContent="center" justifyContent="center"
gap={{ base: 4, md: 6, lg: 8 }} gap={8}
mb={{ base: 3, md: 6, lg: 8 }} mb={12}
> >
<Link <Link
href={discordUrl} href={discordUrl}
variant="ghost" variant="ghost"
size="xs" size="sm"
hoverTextColor="text-neon-aqua" className="text-gray-400 hover:text-primary-accent transition-colors font-bold uppercase tracking-widest"
transition
px={3}
py={2}
minHeight="44px"
minWidth="44px"
> >
💬 Join Discord 💬 Discord
</Link> </Link>
<Link <Link
href={xUrl} href={xUrl}
variant="ghost" variant="ghost"
size="xs" size="sm"
color="text-gray-300" className="text-gray-400 hover:text-primary-accent transition-colors font-bold uppercase tracking-widest"
hoverTextColor="text-neon-aqua"
transition
px={3}
py={2}
minHeight="44px"
minWidth="44px"
> >
𝕏 Follow on X 𝕏 Twitter
</Link> </Link>
</Box> </Box>
{/* Development status */} {/* Development status */}
<Box <Box
textAlign="center" textAlign="center"
pt={{ base: 2, md: 4, lg: 6 }} pt={8}
borderTop 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 Early development Feedback welcome
</Text> </Text>
<Text size={{ base: 'xs', md: 'xs' }} color="text-gray-600" block> <Text size="xs" color="text-gray-700" block font="mono">
Questions? Find me on Discord &copy; {new Date().getFullYear()} GridPilot
</Text> </Text>
</Box> </Box>
</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) { export function Header({ children }: HeaderProps) {
return ( 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> <Container>
{children} {children}
</Container> </Container>

View File

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

View File

@@ -12,15 +12,15 @@ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
export const Input = forwardRef<HTMLInputElement, InputProps>( export const Input = forwardRef<HTMLInputElement, InputProps>(
({ className = '', variant = 'default', errorMessage, icon, label, ...props }, ref) => { ({ 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 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-racing-red' : 'border-charcoal-outline'; const variantClasses = (variant === 'error' || errorMessage) ? 'border-critical-red' : 'border-border-gray';
const iconClasses = icon ? 'pl-10' : ''; const iconClasses = icon ? 'pl-10' : '';
const classes = `${baseClasses} ${variantClasses} ${iconClasses} ${className}`; const classes = `${baseClasses} ${variantClasses} ${iconClasses} ${className}`;
return ( return (
<Stack gap={1.5} fullWidth> <Stack gap={1.5} fullWidth>
{label && ( {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} {label}
</Text> </Text>
)} )}
@@ -34,13 +34,14 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
zIndex={10} zIndex={10}
display="flex" display="flex"
center center
className="text-gray-500"
> >
{icon} {icon}
</Box> </Box>
)} )}
<input ref={ref} className={classes} {...props} /> <input ref={ref} className={classes} {...props} />
{errorMessage && ( {errorMessage && (
<Text size="xs" color="text-error-red" block mt={1}> <Text size="xs" color="text-critical-red" block mt={1}>
{errorMessage} {errorMessage}
</Text> </Text>
)} )}
@@ -50,4 +51,4 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
} }
); );
Input.displayName = 'Input'; Input.displayName = 'Input';

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

View File

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

View File

@@ -5,5 +5,5 @@ interface MainContentProps {
} }
export function MainContent({ children }: 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) { }: SectionProps) {
const variantClasses = { const variantClasses = {
default: '', default: '',
card: 'bg-iron-gray rounded-lg p-6 border border-charcoal-outline', card: 'bg-panel-gray rounded-none p-6 border border-border-gray',
highlight: 'bg-gradient-to-r from-blue-900/20 to-blue-700/10 rounded-lg p-6 border border-blue-500/30', highlight: 'bg-gradient-to-r from-primary-accent/10 to-transparent rounded-none p-6 border border-primary-accent/30',
dark: 'bg-iron-gray', dark: 'bg-graphite-black',
light: 'bg-charcoal-outline' light: 'bg-panel-gray'
}; };
const classes = [ const classes = [

View File

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

View File

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

View File

@@ -47,53 +47,55 @@ export function TeamCard({
onClick, onClick,
}: TeamCardProps) { }: TeamCardProps) {
return ( return (
<Box onClick={onClick} h="full" cursor={onClick ? 'pointer' : 'default'}> <Box onClick={onClick} h="full" cursor={onClick ? 'pointer' : 'default'} className="group">
<Card h="full" p={0} display="flex" flexDirection="col" overflow="hidden"> <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 */} {/* Header with Logo */}
<Box p={4} pb={0}> <Box p={5} pb={0}>
<Stack direction="row" align="start" gap={4}> <Stack direction="row" align="start" gap={4}>
{/* Logo */} {/* Logo */}
<Box <Box
w="14" w="16"
h="14" h="16"
rounded="lg" rounded="none"
bg="bg-deep-graphite" bg="graphite-black"
display="flex" display="flex"
center center
overflow="hidden" overflow="hidden"
border border
borderColor="border-charcoal-outline" borderColor="border-gray/50"
className="relative"
> >
{logo ? ( {logo ? (
<Image <Image
src={logo} src={logo}
alt={name} alt={name}
width={56} width={64}
height={56} height={64}
fullWidth fullWidth
fullHeight fullHeight
objectFit="cover" 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> </Box>
{/* Title & Badges */} {/* Title & Badges */}
<Box flexGrow={1} minWidth="0"> <Box flexGrow={1} minWidth="0">
<Stack direction="row" align="start" justify="between" gap={2}> <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} {name}
</Heading> </Heading>
{isRecruiting && ( {isRecruiting && (
<Badge variant="success" icon={UserPlus}> <Badge variant="success" size="xs">
Recruiting RECRUITING
</Badge> </Badge>
)} )}
</Stack> </Stack>
{/* Performance Level & Category */} {/* 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} {performanceBadge}
{specializationContent} {specializationContent}
{categoryBadge} {categoryBadge}
@@ -103,48 +105,43 @@ export function TeamCard({
</Box> </Box>
{/* Content */} {/* Content */}
<Box p={4} display="flex" flexDirection="col" flexGrow={1}> <Box p={5} display="flex" flexDirection="col" flexGrow={1}>
{/* Description */} {/* Description */}
<Text <Text
size="xs" size="xs"
color="text-gray-500" color="text-gray-500"
mb={3} mb={4}
lineClamp={2} lineClamp={2}
block block
leading="relaxed"
style={{ height: '2.5rem' }}
> >
{description || 'No description available'} {description || 'No description available'}
</Text> </Text>
{/* Region & Languages */} {/* Region & Languages */}
{(region || languagesContent) && ( {(region || languagesContent) && (
<Stack direction="row" align="center" gap={2} wrap mb={3}> <Stack direction="row" align="center" gap={2} wrap mb={4}>
{region && ( {region && (
<Box <Box
display="flex" display="flex"
alignItems="center" alignItems="center"
gap={1.5} gap={2}
px={2} px={2}
py={1} py={1}
rounded="md" rounded="none"
bg="bg-iron-gray/50" bg="panel-gray/20"
border border
style={{ borderColor: 'rgba(38, 38, 38, 0.3)' }} borderColor="border-gray/30"
> >
<Icon icon={Globe} size={3} color="var(--neon-aqua)" /> <Icon icon={Globe} size={3} color="text-primary-accent" />
<Text size="xs" color="text-gray-400">{region}</Text> <Text size="xs" color="text-gray-400" weight="bold" className="uppercase tracking-widest">{region}</Text>
</Box> </Box>
)} )}
{languagesContent} {languagesContent}
</Stack> </Stack>
)} )}
{/* Stats Grid */}
{statsContent && (
<Box display="grid" gridCols={3} gap={2} mb={4}>
{statsContent}
</Box>
)}
{/* Spacer */} {/* Spacer */}
<Box flexGrow={1} /> <Box flexGrow={1} />
@@ -153,21 +150,21 @@ export function TeamCard({
display="flex" display="flex"
alignItems="center" alignItems="center"
justifyContent="between" justifyContent="between"
pt={3} pt={4}
borderTop borderTop
style={{ borderColor: 'rgba(38, 38, 38, 0.5)' }} borderColor="border-gray/30"
mt="auto" mt="auto"
> >
<Stack direction="row" align="center" gap={2}> <Stack direction="row" align="center" gap={2}>
<Icon icon={Users} size={3} color="var(--text-gray-600)" /> <Icon icon={Users} size={3} color="text-gray-500" />
<Text size="xs" color="text-gray-500"> <Text size="xs" color="text-gray-500" font="mono">
{memberCount} {memberCount === 1 ? 'member' : 'members'} {memberCount} {memberCount === 1 ? 'MEMBER' : 'MEMBERS'}
</Text> </Text>
</Stack> </Stack>
<Stack direction="row" align="center" gap={1}> <Stack direction="row" align="center" gap={1} className="group-hover:text-primary-accent transition-colors">
<Text size="xs" color="text-gray-500">View</Text> <Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">VIEW</Text>
<Icon icon={ChevronRight} size={3} color="var(--text-gray-600)" /> <Icon icon={ChevronRight} size={3} color="text-gray-500" className="transition-transform group-hover:translate-x-0.5" />
</Stack> </Stack>
</Box> </Box>
</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 ( return (
<Surface <Surface
variant="muted" variant="muted"
padding={3} padding={4}
rounded="lg" 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}>
{track} <Box w="1" h="8" bg="primary-accent" opacity={0.3} className="group-hover:opacity-100 transition-opacity" />
</Text> <Box flexGrow={1}>
<Text size="sm" color="text-gray-400" block> <Text color="text-white" weight="bold" block className="tracking-tight">
{car} {track}
</Text> </Text>
<Stack direction="row" align="center" gap={2} mt={1}> <Text size="xs" color="text-gray-500" block weight="medium" className="uppercase tracking-widest mt-0.5">
<Text size="xs" color="text-gray-500"> {car}
{formattedDate} </Text>
</Text> </Box>
<Text size="xs" color="text-gray-500"> <Stack align="end" gap={1}>
<Text size="xs" color="text-gray-400" font="mono" weight="bold">
</Text> {formattedDate}
<Text size="xs" color="text-gray-500"> </Text>
{formattedTime} <Text size="xs" color="text-gray-600" font="mono">
</Text> {formattedTime}
</Text>
</Stack>
</Stack> </Stack>
{isMyLeague && ( {isMyLeague && (
<Box mt={1}> <Box mt={3} display="flex" justifyContent="end">
<Badge variant="success"> <Badge variant="success" size="xs">
Your League YOUR LEAGUE
</Badge> </Badge>
</Box> </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: The theme should communicate:
- **race control software**, not a game UI • Precision (like telemetry screens)
- **infrastructure**, not a startup product • Calm intensity (the feeling before a qualifying lap)
- **engineered**, not styled • Authority without ego (used by serious racers)
- **stable and authoritative**, yet pleasant to use • Modern minimalism (dev-friendly, clean structure)
- **built for years**, not trends • Soft gaming hints (subtle neon touches, not RGB vomit)
The goal is not excitement. It appeals equally to:
The goal is **confidence**. • 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: Color System
- dark, neutral background • Base: Near-black graphite (#0C0D0F)
- minimal gradients (almost invisible) • Surface: Deep charcoal (#141619)
- restrained highlights only for meaning • Outline: Soft steel grey (#23272B)
- strict hierarchy • Primary Accent: Electric blue (#198CFF)
- dense, readable layouts • Telemetry Accent: Aqua (#4ED4E0)
- smooth transitions only where state changes • Warning: Motorsport amber (#FFBE4D)
Everything should look: Everything should feel instrument-grade, not decorative.
**intentional, measured, and calm.**
---
### Color Palette (refined) 3. Component Philosophy
- **Graphite Black:** `#0E0F11` Cards
- **Panel Gray:** `#171A1E` • Functional over flashy
- **Border Gray:** `#22262A` • Slight bevel or inset shadow (hint of cockpit panels)
- **Primary Accent:** `#198CFF` (used sparingly) • Dense info, clean hierarchy
- **Success Green:** `#6FE37A`
- **Warning Amber:** `#FFC556`
- **Critical Red:** `#E35C5C`
No neon. Tables
No playful colors. • High-density
Color = meaning, not decoration. • 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: Racing-inspired, but minimal:
- short durations (120200ms) • Short accelerations (fast ease-out)
- low amplitude • No bounces — this isnt a game store
- no exaggerated easing • Hover = slight lift + color pulse
- no elastic bounces • Loading = progress line (like pit limiter light)
- no decorative movement • 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 Think “Telemetry Workspace”:
- panel open / close • Sidebar = dashboard rail
- table row expansion • Header = control bar
- state changes (pending → approved → completed) • Content = track map / data table zone
- subtle loading indicators • 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 Examples:
- animated backgrounds • “Add race”
- glowing UI chrome • “Review protest”
- playful hover gimmicks • “Session updated”
- “app store” aesthetics • “Export standings”
- anything that reduces trust • “Sponsor payout queued”
GridPilot must feel: No hype. No marketing tone inside the app.
**reliable before it feels beautiful.**
---
## 6. Components 7. Emotional Target
### Tables Users should feel:
- primary UI element • In control
- dense, readable • Supported
- fixed column logic • Efficient
- no playful effects • Connected to motorsport culture
• Trusting (nothing looks cheap or gimmicky)
### Cards Its basically:
- functional grouping
- no visual dominance
- secondary to tables
### Modals “A premium cockpit dashboard… built by people who race and code.”
- 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.**