website refactor
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
|
||||
|
||||
import { useParallax } from "@/hooks/useScrollProgress";
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { useRef } from 'react';
|
||||
|
||||
interface AlternatingSectionProps {
|
||||
@@ -25,8 +24,7 @@ export function AlternatingSection({
|
||||
backgroundVideo
|
||||
}: AlternatingSectionProps) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
const bgParallax = useParallax(sectionRef, 0.2);
|
||||
const bgParallax = useParallax(sectionRef, 0.1);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -34,93 +32,97 @@ export function AlternatingSection({
|
||||
ref={sectionRef}
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
bg="bg-deep-graphite"
|
||||
px={{ base: 'calc(1rem+var(--sal))', lg: 8 }}
|
||||
py={{ base: 20, sm: 24, md: 32 }}
|
||||
bg="graphite-black"
|
||||
py={{ base: 20, md: 32 }}
|
||||
className="border-b border-border-gray"
|
||||
>
|
||||
{backgroundVideo && (
|
||||
<>
|
||||
<Box
|
||||
position="absolute"
|
||||
inset="0"
|
||||
fullWidth
|
||||
fullHeight
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
as="video"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
position="absolute"
|
||||
inset="0"
|
||||
fullWidth
|
||||
fullHeight
|
||||
objectFit="cover"
|
||||
opacity={0.2}
|
||||
maskImage="radial-gradient(ellipse at center, black 0%, rgba(0,0,0,0.8) 40%, transparent 70%)"
|
||||
webkitMaskImage="radial-gradient(ellipse at center, black 0%, rgba(0,0,0,0.8) 40%, transparent 70%)"
|
||||
opacity={0.1}
|
||||
>
|
||||
<Box as="source" src={backgroundVideo} type="video/mp4" />
|
||||
</Box>
|
||||
{/* Racing red accent for sections with background videos */}
|
||||
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, rgba(239, 68, 68, 0.3), transparent)" />
|
||||
</>
|
||||
{/* Dark overlay to ensure readability */}
|
||||
<Box position="absolute" inset="0" bg="linear-gradient(to bottom, #0C0D0F, transparent, #0C0D0F)" />
|
||||
</Box>
|
||||
)}
|
||||
{backgroundImage && !backgroundVideo && (
|
||||
<>
|
||||
<Box
|
||||
position="absolute"
|
||||
inset="0"
|
||||
fullWidth
|
||||
fullHeight
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
inset="0"
|
||||
bg={`url(${backgroundImage})`}
|
||||
backgroundSize="cover"
|
||||
backgroundPosition="center"
|
||||
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
|
||||
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
|
||||
transform={`translateY(${bgParallax * 0.3}px)`}
|
||||
opacity={0.1}
|
||||
style={{ transform: `translateY(${bgParallax * 0.3}px)` }}
|
||||
/>
|
||||
{/* Racing red accent for sections with background images */}
|
||||
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, rgba(239, 68, 68, 0.3), transparent)" />
|
||||
</>
|
||||
{/* Dark overlay to ensure readability */}
|
||||
<Box position="absolute" inset="0" bg="linear-gradient(to bottom, #0C0D0F, transparent, #0C0D0F)" />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Carbon fiber texture on sections without images or videos */}
|
||||
{!backgroundImage && !backgroundVideo && (
|
||||
<Box position="absolute" inset="0" opacity={0.3} bg="carbon-fiber" />
|
||||
)}
|
||||
|
||||
{/* Checkered pattern accent */}
|
||||
<Box position="absolute" inset="0" opacity={0.1} bg="checkered-pattern" />
|
||||
|
||||
<Container size="lg" position="relative" zIndex={10}>
|
||||
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={{ base: 8, md: 12, lg: 16 }} alignItems="center">
|
||||
{/* Text Content - Always first on mobile, respects layout on desktop */}
|
||||
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={{ base: 12, lg: 24 }} alignItems="center">
|
||||
{/* Text Content */}
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
gap={{ base: 4, md: 6, lg: 8 }}
|
||||
gap={8}
|
||||
order={{ lg: layout === 'text-right' ? 2 : 1 }}
|
||||
>
|
||||
<Heading level={2} fontSize={{ base: 'xl', md: '2xl', lg: '3xl', xl: '4xl' }} weight="medium" style={{ background: 'linear-gradient(to right, #dc2626, #ffffff, #2563eb)', backgroundClip: 'text', WebkitBackgroundClip: 'text', color: 'transparent', filter: 'drop-shadow(0 0 15px rgba(220,0,0,0.4))', WebkitTextStroke: '0.5px rgba(220,0,0,0.2)' }}>
|
||||
{heading}
|
||||
</Heading>
|
||||
<Box display="flex" flexDirection="column" gap={{ base: 3, md: 5 }}>
|
||||
<Text size={{ base: 'sm', md: 'base', lg: 'lg' }} color="text-slate-400" weight="light" leading="relaxed">
|
||||
{description}
|
||||
</Text>
|
||||
<Stack gap={4}>
|
||||
<Box w="12" h="1" bg="primary-accent" />
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" className="tracking-tight">
|
||||
{heading}
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Box className="text-gray-400">
|
||||
{typeof description === 'string' ? (
|
||||
<Text size="lg" leading="relaxed" weight="normal">{description}</Text>
|
||||
) : (
|
||||
description
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Mockup - Always second on mobile, respects layout on desktop */}
|
||||
{/* Mockup */}
|
||||
<Box
|
||||
position="relative"
|
||||
order={{ lg: layout === 'text-right' ? 1 : 2 }}
|
||||
group
|
||||
className="bg-panel-gray/30 border border-border-gray/50 rounded-none p-2 shadow-2xl group"
|
||||
>
|
||||
<Box
|
||||
fullWidth
|
||||
minHeight={{ base: '240px', md: '380px', lg: '440px' }}
|
||||
transition
|
||||
hoverScale
|
||||
maskImage={`linear-gradient(to ${layout === 'text-left' ? 'right' : 'left'}, white 50%, rgba(255,255,255,0.8) 70%, rgba(255,255,255,0.4) 85%, transparent 100%)`}
|
||||
webkitMaskImage={`linear-gradient(to ${layout === 'text-left' ? 'right' : 'left'}, white 50%, rgba(255,255,255,0.8) 70%, rgba(255,255,255,0.4) 85%, transparent 100%)`}
|
||||
minHeight={{ base: '240px', md: '380px' }}
|
||||
className="overflow-hidden rounded-none border border-border-gray/30 bg-graphite-black"
|
||||
>
|
||||
{mockup}
|
||||
</Box>
|
||||
{/* Decorative corner accents */}
|
||||
<Box position="absolute" top="-1px" left="-1px" w="4" h="4" borderTop borderLeft borderColor="primary-accent/50" />
|
||||
<Box position="absolute" bottom="-1px" right="-1px" w="4" h="4" borderBottom borderRight borderColor="primary-accent/50" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
123
apps/website/components/landing/DiscoverySection.tsx
Normal file
123
apps/website/components/landing/DiscoverySection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
@@ -47,27 +48,31 @@ function FAQItem({ faq, index }: { faq: typeof faqs[0]; index: number }) {
|
||||
transition={{ delay: index * 0.1 }}
|
||||
group
|
||||
>
|
||||
<Box rounded="lg" bg="bg-iron-gray" border borderColor="border-charcoal-outline" transition hoverBorderColor="border-primary-blue/50" transform={isOpen ? '' : 'translateY(0)'} hoverScale={!isOpen}>
|
||||
<Box rounded="none" bg="panel-gray/40" border borderColor="border-gray/50" transition hoverBorderColor="primary-accent/30">
|
||||
<Box
|
||||
as="button"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
fullWidth
|
||||
p={{ base: 2, md: 3, lg: 4 }}
|
||||
p={{ base: 4, md: 6 }}
|
||||
textAlign="left"
|
||||
rounded="lg"
|
||||
rounded="none"
|
||||
minHeight="44px"
|
||||
className="relative overflow-hidden"
|
||||
>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" gap={{ base: 1.5, md: 2 }}>
|
||||
<Heading level={3} fontSize={{ base: 'xs', md: 'sm' }} weight="semibold" color="text-white" groupHoverColor="primary-blue" transition>
|
||||
{faq.question}
|
||||
</Heading>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" gap={{ base: 2, md: 4 }}>
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box w="1" h="4" bg={isOpen ? "primary-accent" : "border-gray"} transition className="group-hover:bg-primary-accent" />
|
||||
<Heading level={3} fontSize={{ base: 'sm', md: 'base' }} weight="bold" color="text-white" groupHoverColor="primary-accent" transition className="tracking-wide">
|
||||
{faq.question}
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Box
|
||||
as={motion.div}
|
||||
animate={{ rotate: isOpen ? 180 : 0 }}
|
||||
transition={{ duration: 0.15, ease: 'easeInOut' }}
|
||||
w={{ base: "3.5", md: "4" }}
|
||||
h={{ base: "3.5", md: "4" }}
|
||||
color="text-neon-aqua"
|
||||
w={{ base: "4", md: "5" }}
|
||||
h={{ base: "4", md: "5" }}
|
||||
color={isOpen ? "text-primary-accent" : "text-gray-500"}
|
||||
flexShrink={0}
|
||||
>
|
||||
<Icon icon={ChevronDown} size="full" />
|
||||
@@ -82,13 +87,13 @@ function FAQItem({ faq, index }: { faq: typeof faqs[0]; index: number }) {
|
||||
opacity: isOpen ? 1 : 0
|
||||
}}
|
||||
transition={{
|
||||
height: { duration: 0.3, ease: [0.34, 1.56, 0.64, 1] },
|
||||
opacity: { duration: 0.2, ease: 'easeInOut' }
|
||||
height: { duration: 0.2, ease: 'easeInOut' },
|
||||
opacity: { duration: 0.15, ease: 'easeInOut' }
|
||||
}}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box px={{ base: 2, md: 3 }} pb={{ base: 2, md: 3 }} pt={1}>
|
||||
<Text size={{ base: 'xs', md: 'xs' }} color="text-gray-300" weight="light" leading="relaxed">
|
||||
<Box px={{ base: 4, md: 6 }} pb={{ base: 4, md: 6 }} pt={0} ml={5}>
|
||||
<Text size="sm" color="text-gray-400" weight="normal" leading="relaxed" className="max-w-2xl">
|
||||
{faq.answer}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -100,7 +105,7 @@ function FAQItem({ faq, index }: { faq: typeof faqs[0]; index: number }) {
|
||||
|
||||
export function FAQ() {
|
||||
return (
|
||||
<Box as="section" position="relative" py={{ base: 3, md: 12, lg: 16 }} bg="bg-deep-graphite" overflow="hidden">
|
||||
<Box as="section" position="relative" py={{ base: 20, md: 32 }} bg="graphite-black" overflow="hidden" borderBottom borderColor="border-gray/50">
|
||||
{/* Background image with mask */}
|
||||
<Box
|
||||
position="absolute"
|
||||
@@ -108,20 +113,21 @@ export function FAQ() {
|
||||
bg="url(/images/porsche.jpeg)"
|
||||
backgroundSize="cover"
|
||||
backgroundPosition="center"
|
||||
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
|
||||
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.1) 40%, transparent 70%)"
|
||||
opacity={0.03}
|
||||
/>
|
||||
{/* Racing red accent */}
|
||||
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, rgba(239, 68, 68, 0.3), transparent)" />
|
||||
|
||||
<Box maxWidth="3xl" mx="auto" px={{ base: 4, md: 6 }} position="relative" zIndex={10}>
|
||||
<Box textAlign="center" mb={{ base: 4, md: 8 }}>
|
||||
<Heading level={2} fontSize={{ base: 'base', md: 'xl', lg: '2xl' }} weight="semibold" color="text-white" mb={1}>
|
||||
<Box maxWidth="4xl" mx="auto" px={{ base: 4, md: 6 }} position="relative" zIndex={10}>
|
||||
<Box textAlign="center" mb={{ base: 12, md: 16 }}>
|
||||
<Box borderLeft borderRight borderStyle="solid" borderColor="primary-accent" px={4} display="inline-block" mb={4}>
|
||||
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-[0.2em]">
|
||||
Support & Information
|
||||
</Text>
|
||||
</Box>
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" color="text-white" mb={4} className="tracking-tight">
|
||||
Frequently Asked Questions
|
||||
</Heading>
|
||||
<Box mx="auto" rounded="full" w={{ base: "24", md: "32" }} h="1" bg="linear-gradient(to right, var(--primary-blue), var(--neon-aqua))" />
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="column" gap={{ base: 1.5, md: 2 }}>
|
||||
<Box display="flex" flexDirection="column" gap={4}>
|
||||
{faqs.map((faq, index) => (
|
||||
<FAQItem key={faq.question} faq={faq} index={index} />
|
||||
))}
|
||||
|
||||
@@ -53,49 +53,63 @@ function FeatureCard({ feature, index }: { feature: typeof features[0], index: n
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
gap={6}
|
||||
group
|
||||
className="p-8 bg-panel-gray/40 border border-border-gray/50 rounded-none hover:border-primary-accent/30 transition-all duration-300 ease-smooth group relative overflow-hidden"
|
||||
>
|
||||
<Box aspectRatio="video" fullWidth position="relative">
|
||||
<Box position="absolute" inset="-0.5" bg="linear-gradient(to right, rgba(239, 68, 68, 0.2), rgba(59, 130, 246, 0.2), rgba(239, 68, 68, 0.2))" rounded="lg" opacity={0} groupHoverOpacity={1} transition blur="sm" />
|
||||
<Box position="relative">
|
||||
<MockupStack index={index}>
|
||||
<feature.MockupComponent />
|
||||
</MockupStack>
|
||||
</Box>
|
||||
<Box aspectRatio="video" fullWidth position="relative" className="bg-graphite-black rounded-none overflow-hidden border border-border-gray/30">
|
||||
<MockupStack index={index}>
|
||||
<feature.MockupComponent />
|
||||
</MockupStack>
|
||||
</Box>
|
||||
<Stack gap={3}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Heading level={3} weight="medium" style={{ background: 'linear-gradient(to right, #dc2626, #ffffff, #2563eb)', backgroundClip: 'text', WebkitBackgroundClip: 'text', color: 'transparent', filter: 'drop-shadow(0 0 15px rgba(220,0,0,0.4))', WebkitTextStroke: '0.5px rgba(220,0,0,0.2)' }}>
|
||||
<Stack gap={4}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box w="1" h="4" bg="primary-accent" />
|
||||
<Heading level={3} weight="bold" fontSize="xl" className="tracking-tight">
|
||||
{feature.title}
|
||||
</Heading>
|
||||
</Box>
|
||||
<Text size={{ base: 'sm', sm: 'base' }} color="text-gray-400" weight="light" leading="relaxed">
|
||||
<Text size="sm" color="text-gray-400" leading="relaxed" weight="normal">
|
||||
{feature.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
{/* Subtle hover effect */}
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom="0"
|
||||
left="0"
|
||||
w="full"
|
||||
h="1"
|
||||
bg="primary-accent"
|
||||
className="scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function FeatureGrid() {
|
||||
return (
|
||||
<Section variant="default">
|
||||
<Section className="bg-graphite-black border-b border-border-gray/50 py-24">
|
||||
<Container position="relative" zIndex={10}>
|
||||
<Container size="sm" center>
|
||||
<Box>
|
||||
<Heading level={2} weight="semibold" style={{ background: 'linear-gradient(to right, #dc2626, #ffffff, #2563eb)', backgroundClip: 'text', WebkitBackgroundClip: 'text', color: 'transparent', filter: 'drop-shadow(0 0 20px rgba(220,0,0,0.5))', WebkitTextStroke: '1px rgba(220,0,0,0.2)' }}>
|
||||
<Stack gap={16}>
|
||||
<Box maxWidth="2xl">
|
||||
<Box borderLeft borderStyle="solid" borderColor="primary-accent" pl={4} mb={4}>
|
||||
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-[0.2em]">
|
||||
Engineered for Competition
|
||||
</Text>
|
||||
</Box>
|
||||
<Heading level={2} weight="bold" fontSize={{ base: '3xl', md: '4xl' }} className="tracking-tight">
|
||||
Building for League Racing
|
||||
</Heading>
|
||||
<Text size={{ base: 'base', sm: 'lg' }} color="text-gray-400" block mt={{ base: 4, sm: 6 }}>
|
||||
These features are in development. Join the community to help shape what gets built first
|
||||
<Text size="lg" color="text-gray-400" block mt={6} leading="relaxed">
|
||||
Every feature is designed to reduce friction and increase immersion. Join our Discord to help shape the future of the platform.
|
||||
</Text>
|
||||
</Box>
|
||||
</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' }}>
|
||||
{features.map((feature, index) => (
|
||||
<FeatureCard key={feature.title} feature={feature} index={index} />
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2, lg: 3 }} gap={8}>
|
||||
{features.map((feature, index) => (
|
||||
<FeatureCard key={feature.title} feature={feature} index={index} />
|
||||
))}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { useParallax } from '@/hooks/useScrollProgress';
|
||||
import { Box } from '@/ui/Box';
|
||||
@@ -7,17 +6,13 @@ import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
|
||||
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#';
|
||||
|
||||
if (!process.env.NEXT_PUBLIC_DISCORD_URL) {
|
||||
console.warn('NEXT_PUBLIC_DISCORD_URL is not set. Discord button will use "#" as fallback.');
|
||||
}
|
||||
|
||||
export function LandingHero() {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
const bgParallax = useParallax(sectionRef, 0.3);
|
||||
const bgParallax = useParallax(sectionRef, 0.2);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -25,11 +20,10 @@ export function LandingHero() {
|
||||
ref={sectionRef}
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
bg="bg-deep-graphite"
|
||||
px={{ base: 'calc(1.5rem+var(--sal))', lg: 8 }}
|
||||
pt={{ base: 'calc(3rem+var(--sat))', sm: 'calc(4rem+var(--sat))' }}
|
||||
pb={{ base: 16, sm: 24 }}
|
||||
py={{ md: 32 }}
|
||||
bg="graphite-black"
|
||||
pt={{ base: 24, md: 32, lg: 40 }}
|
||||
pb={{ base: 16, md: 24, lg: 32 }}
|
||||
className="border-b border-border-gray"
|
||||
>
|
||||
{/* Background image layer with parallax */}
|
||||
<Box
|
||||
@@ -38,131 +32,93 @@ export function LandingHero() {
|
||||
bg="url(/images/header.jpeg)"
|
||||
backgroundSize="cover"
|
||||
backgroundPosition="center"
|
||||
maskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.35) 40%, transparent 70%)"
|
||||
webkitMaskImage="radial-gradient(ellipse at center, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.35) 40%, transparent 70%)"
|
||||
transform={`translateY(${bgParallax * 0.5}px)`}
|
||||
opacity={0.2}
|
||||
style={{ transform: `translateY(${bgParallax * 0.5}px)` }}
|
||||
/>
|
||||
|
||||
{/* Racing red accent gradient */}
|
||||
<Box position="absolute" top="0" left="0" right="0" h="1" bg="linear-gradient(to right, transparent, rgba(220, 38, 38, 0.4), transparent)" />
|
||||
{/* Robust gradient overlay */}
|
||||
<Box
|
||||
position="absolute"
|
||||
inset="0"
|
||||
bg="linear-gradient(to bottom, #0C0D0F 0%, transparent 50%, #0C0D0F 100%)"
|
||||
/>
|
||||
|
||||
{/* Racing stripes background */}
|
||||
<Box position="absolute" inset="0" opacity={0.3} bg="racing-stripes" />
|
||||
<Box
|
||||
position="absolute"
|
||||
inset="0"
|
||||
bg="radial-gradient(circle at center, transparent 0%, #0C0D0F 100%)"
|
||||
opacity={0.6}
|
||||
/>
|
||||
|
||||
{/* Checkered pattern overlay */}
|
||||
<Box position="absolute" inset="0" opacity={0.2} bg="checkered-pattern" />
|
||||
<Glow color="primary" size="xl" position="center" opacity={0.1} />
|
||||
|
||||
{/* Speed lines - left side */}
|
||||
<Box position="absolute" left="0" top="1/4" w="32" h="px" bg="linear-gradient(to right, transparent, rgba(59, 130, 246, 0.3))" className="animate-speed-lines" style={{ animationDelay: '0s' }} />
|
||||
<Box position="absolute" left="0" top="1/3" w="24" h="px" bg="linear-gradient(to right, transparent, rgba(59, 130, 246, 0.2))" className="animate-speed-lines" style={{ animationDelay: '0.3s' }} />
|
||||
<Box position="absolute" left="0" top="2/5" w="28" h="px" bg="linear-gradient(to right, transparent, rgba(59, 130, 246, 0.25))" className="animate-speed-lines" style={{ animationDelay: '0.6s' }} />
|
||||
|
||||
{/* Carbon fiber accent - bottom */}
|
||||
<Box position="absolute" bottom="0" left="0" right="0" h="32" opacity={0.5} bg="carbon-fiber" />
|
||||
|
||||
{/* Radial gradient overlay with racing red accent */}
|
||||
<Box position="absolute" inset="0" bg="radial-gradient(circle at center, rgba(220, 38, 38, 0.05), rgba(59, 130, 246, 0.05), transparent)" opacity={0.6} pointerEvents="none" />
|
||||
|
||||
<Container size="sm" position="relative" zIndex={10}>
|
||||
<Stack gap={{ base: 6, sm: 8, md: 12 }}>
|
||||
<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>
|
||||
))}
|
||||
<Container size="lg" position="relative" zIndex={10}>
|
||||
<Stack gap={{ base: 8, md: 12 }}>
|
||||
<Stack gap={6} maxWidth="3xl">
|
||||
<Box borderLeft borderStyle="solid" borderColor="primary-accent" pl={4} mb={2}>
|
||||
<Text size="xs" weight="bold" color="text-primary-accent" className="uppercase tracking-[0.2em]">
|
||||
Precision Racing Infrastructure
|
||||
</Text>
|
||||
</Box>
|
||||
<Text size={{ base: 'sm', md: 'lg' }} align={{ base: 'left', md: 'center' }} color="text-slate-200" weight="light">
|
||||
The ecosystem isn't built for this.
|
||||
<Heading
|
||||
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 size={{ base: 'sm', md: 'lg' }} align={{ base: 'left', md: 'center' }} color="text-white" weight="semibold">
|
||||
GridPilot gives your league racing a real home.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" justifyContent="center">
|
||||
</Stack>
|
||||
|
||||
<Box display="flex" flexDirection={{ base: 'column', sm: 'row' }} gap={4}>
|
||||
<Button
|
||||
as="a"
|
||||
href={discordUrl}
|
||||
variant="primary"
|
||||
px={8}
|
||||
py={4}
|
||||
size="lg"
|
||||
bg="bg-[#5865F2]"
|
||||
hoverBg="bg-[#4752C4]"
|
||||
shadow="0 0 20px rgba(88,101,242,0.3)"
|
||||
hoverScale
|
||||
transition
|
||||
aria-label="Join us on Discord"
|
||||
className="px-10 rounded-none uppercase tracking-widest text-xs font-bold"
|
||||
>
|
||||
{/* Discord Logo SVG */}
|
||||
<Box
|
||||
as="svg"
|
||||
w="7"
|
||||
h="7"
|
||||
viewBox="0 0 71 55"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
transition
|
||||
groupHoverScale
|
||||
>
|
||||
<g clipPath="url(#clip0)">
|
||||
<path
|
||||
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="71" height="55" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</Box>
|
||||
<Text>Join us on Discord</Text>
|
||||
Join the Grid
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="px-10 rounded-none border-border-gray hover:border-primary-accent/50 uppercase tracking-widest text-xs font-bold"
|
||||
>
|
||||
Explore Leagues
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Problem list - more professional */}
|
||||
<Box
|
||||
display="grid"
|
||||
gridCols={{ base: 1, sm: 2, lg: 4 }}
|
||||
gap={8}
|
||||
mt={12}
|
||||
className="border-t border-border-gray/30 pt-12"
|
||||
>
|
||||
{[
|
||||
{ label: 'IDENTITY', text: 'Your racing career in one place', color: 'primary' },
|
||||
{ label: 'AUTOMATION', text: 'No more manual session setup', color: 'aqua' },
|
||||
{ label: 'PRECISION', text: 'Real-time results and standings', color: 'amber' },
|
||||
{ label: 'COMMUNITY', text: 'Built for teams and leagues', color: 'green' }
|
||||
].map((item) => (
|
||||
<Stack key={item.label} gap={3} className="group">
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box w="2" h="2" rounded="full" className={`bg-${item.color === 'primary' ? 'primary-accent' : item.color === 'aqua' ? 'telemetry-aqua' : item.color === 'amber' ? 'warning-amber' : 'success-green'}`} />
|
||||
<Text size="xs" weight="bold" color="text-gray-500" className="uppercase tracking-[0.2em] group-hover:text-white transition-colors">
|
||||
{item.label}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text size="sm" color="text-gray-400" className="group-hover:text-gray-200 transition-colors">
|
||||
{item.text}
|
||||
</Text>
|
||||
</Stack>
|
||||
))}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -5,21 +5,34 @@ import { Text } from '@/ui/Text';
|
||||
|
||||
export function HeaderContent() {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Link href="/" className="inline-flex items-center">
|
||||
<Image
|
||||
src="/images/logos/wordmark-rectangle-dark.svg"
|
||||
alt="GridPilot"
|
||||
width={160}
|
||||
height={30}
|
||||
className="h-6 w-auto md:h-8"
|
||||
priority
|
||||
/>
|
||||
<div className="flex items-center justify-between h-16 md:h-20">
|
||||
<div className="flex items-center space-x-6">
|
||||
<Link href="/" className="inline-flex items-center group">
|
||||
<div className="relative">
|
||||
<Image
|
||||
src="/images/logos/wordmark-rectangle-dark.svg"
|
||||
alt="GridPilot"
|
||||
width={160}
|
||||
height={30}
|
||||
className="h-6 w-auto md:h-7 transition-opacity group-hover:opacity-80"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute -bottom-1 left-0 w-0 h-0.5 bg-primary-accent transition-all group-hover:w-full" />
|
||||
</div>
|
||||
</Link>
|
||||
<Text size="sm" color="text-gray-400" className="hidden sm:block font-light">
|
||||
Making league racing less chaotic
|
||||
</Text>
|
||||
<div className="hidden sm:flex items-center space-x-2 border-l border-border-gray/50 pl-6">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-primary-accent animate-pulse" />
|
||||
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-[0.2em] font-mono">
|
||||
Motorsport Infrastructure
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="hidden md:flex items-center space-x-1 px-3 py-1 border border-border-gray/30 bg-panel-gray/20">
|
||||
<Text size="xs" color="text-gray-600" weight="bold" font="mono" className="uppercase">Status:</Text>
|
||||
<Text size="xs" color="text-success-green" weight="bold" font="mono" className="uppercase">Operational</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ export function CareerProgressionMockup() {
|
||||
// Simple mobile version - just the essence
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial="hidden"
|
||||
@@ -43,8 +43,8 @@ export function CareerProgressionMockup() {
|
||||
{ value: '48', label: 'Podiums' },
|
||||
{ value: '156', label: 'Races' }
|
||||
].map((stat, i) => (
|
||||
<Box key={i} bg="bg-iron-gray/60" rounded="lg" p={3} border borderColor="border-primary-blue/20" textAlign="center">
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" font="mono" block>{stat.value}</Text>
|
||||
<Box key={i} bg="panel-gray/60" rounded="none" p={3} border borderColor="primary-accent/20" textAlign="center">
|
||||
<Text size="2xl" weight="bold" color="text-primary-accent" font="mono" block>{stat.value}</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '11px' }}
|
||||
@@ -52,6 +52,8 @@ export function CareerProgressionMockup() {
|
||||
opacity={0.5}
|
||||
mt={1}
|
||||
block
|
||||
font="mono"
|
||||
uppercase
|
||||
>
|
||||
{stat.label}
|
||||
</Text>
|
||||
@@ -61,11 +63,11 @@ export function CareerProgressionMockup() {
|
||||
|
||||
{/* Single elegant season card */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Box bg="bg-iron-gray/40" rounded="lg" p={3} border borderColor="border-charcoal-outline">
|
||||
<Box bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Text size="sm" color="text-white" opacity={0.7}>GT3 Championship</Text>
|
||||
<Box as="span" px={2.5} py={1} bg="bg-performance-green/20" rounded="sm">
|
||||
<Text size="xs" color="text-performance-green" weight="semibold">P2</Text>
|
||||
<Text size="sm" color="text-white" opacity={0.7} weight="bold">GT3 Championship</Text>
|
||||
<Box as="span" px={2.5} py={1} bg="success-green/10" border borderColor="success-green/30">
|
||||
<Text size="xs" color="text-success-green" weight="bold" font="mono">P2</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -78,7 +80,7 @@ export function CareerProgressionMockup() {
|
||||
|
||||
// Desktop version - more detailed
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial="hidden"
|
||||
@@ -89,26 +91,29 @@ export function CareerProgressionMockup() {
|
||||
>
|
||||
<Stack gap={{ base: 1.5, sm: 3, md: 4, lg: 6 }}>
|
||||
{/* Driver Header */}
|
||||
<Box as={motion.div} variants={itemVariants} display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 3, md: 4, lg: 6 }} borderBottom borderColor="border-charcoal-outline">
|
||||
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} bg="bg-charcoal-outline" rounded="full" border borderWidth="2px" borderColor="border-primary-blue/30" display="flex" alignItems="center" justifyContent="center" overflow="hidden">
|
||||
<Box as={motion.div} variants={itemVariants} display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 3, md: 4, lg: 6 }} borderBottom borderColor="border-gray/50">
|
||||
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} bg="panel-gray" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" display="flex" alignItems="center" justifyContent="center" overflow="hidden" className="relative">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏎️</Text>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.9} mb={{ base: 1, sm: 1.5, md: 2 }} block>Your Racing Identity</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-white" opacity={0.9} mb={{ base: 1, sm: 1.5, md: 2 }} block className="uppercase tracking-widest">Your Racing Identity</Text>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 2, md: 3 }}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-primary-blue"
|
||||
opacity={0.7}
|
||||
color="text-primary-accent"
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Multi-league profile
|
||||
</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-performance-green"
|
||||
opacity={0.7}
|
||||
color="text-success-green"
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Career tracking
|
||||
</Text>
|
||||
@@ -118,7 +123,7 @@ export function CareerProgressionMockup() {
|
||||
|
||||
{/* Career Stats */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.6} mb={{ base: 1, sm: 2, md: 3 }} block>Career Overview</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-gray-500" mb={{ base: 1, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Career Overview</Text>
|
||||
<Box display="grid" gridCols={3} gap={{ base: 1, sm: 2, md: 3 }}>
|
||||
{[
|
||||
{ label: 'Wins', value: '24' },
|
||||
@@ -128,19 +133,19 @@ export function CareerProgressionMockup() {
|
||||
<Box
|
||||
key={i}
|
||||
as={motion.div}
|
||||
bg="bg-iron-gray"
|
||||
rounded="lg"
|
||||
bg="panel-gray/40"
|
||||
rounded="none"
|
||||
p={{ base: 1, sm: 2, md: 3 }}
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/50"
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
y: -2,
|
||||
boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
|
||||
borderColor: '#198CFF80',
|
||||
transition: { duration: 0.15 }
|
||||
}}
|
||||
>
|
||||
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }} weight="bold" color="text-primary-blue" font="mono" mb={0.5} block>{stat.value}</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.4} block>{stat.label}</Text>
|
||||
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }} weight="bold" color="text-primary-accent" font="mono" mb={0.5} block>{stat.value}</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-gray-500" weight="bold" className="uppercase tracking-widest" block>{stat.label}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
@@ -148,7 +153,7 @@ export function CareerProgressionMockup() {
|
||||
|
||||
{/* Season Timeline */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.6} mb={{ base: 1, sm: 2, md: 3 }} block>Season History</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-gray-500" mb={{ base: 1, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Season History</Text>
|
||||
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
{[
|
||||
{ league: 'GT3 Championship', season: 'S3', position: 'P2', points: '248' },
|
||||
@@ -161,30 +166,30 @@ export function CareerProgressionMockup() {
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={{ base: 1.5, sm: 2, md: 3 }}
|
||||
bg="bg-iron-gray"
|
||||
rounded="lg"
|
||||
bg="panel-gray/20"
|
||||
rounded="none"
|
||||
p={{ base: 1.5, sm: 2, md: 3 }}
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/30"
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
x: 4,
|
||||
boxShadow: '0 2px 12px rgba(25,140,255,0.2)',
|
||||
borderColor: '#198CFF50',
|
||||
transition: { duration: 0.15 }
|
||||
}}
|
||||
>
|
||||
<Box h={{ base: 5, sm: 6, md: 8 }} w={{ base: 5, sm: 6, md: 8 }} bg="bg-charcoal-outline" rounded border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box h={{ base: 5, sm: 6, md: 8 }} w={{ base: 5, sm: 6, md: 8 }} bg="panel-gray" rounded="none" border borderColor="primary-accent/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }}>🏁</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.8} mb={{ base: 0.5, sm: 1 }} block>{season.league}</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.4} block>Season complete</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" weight="bold" mb={{ base: 0.5, sm: 1 }} block>{season.league}</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-gray-500" font="mono" uppercase block>Season complete</Text>
|
||||
</Box>
|
||||
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-performance-green/20" rounded="sm">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-performance-green" weight="semibold">{season.position}</Text>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="success-green/10" border borderColor="success-green/30">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-success-green" weight="bold" font="mono">{season.position}</Text>
|
||||
</Box>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-primary-blue/20" rounded="sm">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-primary-blue" font="mono">{season.points}</Text>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/30">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-primary-accent" font="mono" weight="bold">{season.points}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -194,18 +199,18 @@ export function CareerProgressionMockup() {
|
||||
|
||||
{/* Multi-League Badge */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }} bg="bg-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3 }} border borderColor="border-primary-blue/30">
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }} bg="panel-gray/40" rounded="none" p={{ base: 1.5, sm: 2, md: 3 }} border borderColor="primary-accent/20">
|
||||
<Box display="flex"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="-space-x-2"
|
||||
>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Box key={i} h={{ base: 4, sm: 5, md: 6 }} w={{ base: 4, sm: 5, md: 6 }} bg="bg-iron-gray" rounded="full" border borderWidth="2px" borderColor="border-charcoal-outline" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box key={i} h={{ base: 4, sm: 5, md: 6 }} w={{ base: 4, sm: 5, md: 6 }} bg="panel-gray" rounded="full" border borderWidth="2px" borderColor="border-gray" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }}>🏆</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-white" opacity={0.6}>Active in 3 leagues across seasons</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-gray-400" weight="medium">Active in 3 leagues across seasons</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Check } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
|
||||
export function CompanionAutomationMockup() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
@@ -23,7 +21,7 @@ export function CompanionAutomationMockup() {
|
||||
// Simple mobile version - just the essence of automation
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0 }}
|
||||
@@ -32,39 +30,41 @@ export function CompanionAutomationMockup() {
|
||||
>
|
||||
<Stack gap={4}>
|
||||
{/* Simple progress indicator */}
|
||||
<Box bg="bg-iron-gray/60" rounded="xl" p={4} border borderColor="border-primary-blue/40">
|
||||
<Box bg="panel-gray/60" rounded="none" p={4} border borderColor="primary-accent/40">
|
||||
<Box display="flex" alignItems="center" gap={3} mb={3}>
|
||||
<Box
|
||||
as={motion.div}
|
||||
h="8"
|
||||
w="8"
|
||||
bg="bg-performance-green/40"
|
||||
rounded="full"
|
||||
bg="success-green/20"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
border
|
||||
borderWidth="2px"
|
||||
borderColor="border-performance-green/60"
|
||||
borderWidth="1px"
|
||||
borderColor="success-green/40"
|
||||
flexShrink={0}
|
||||
animate={shouldReduceMotion ? {} : {
|
||||
scale: [1, 1.1, 1],
|
||||
scale: [1, 1.05, 1],
|
||||
opacity: [0.6, 1, 0.6]
|
||||
}}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
className="relative"
|
||||
>
|
||||
<Box h="3" w="3" bg="bg-performance-green" rounded="full" />
|
||||
<Box h="2" w="2" bg="success-green" />
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="success-green" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="sm" color="text-white" weight="medium" block>Creating Session</Text>
|
||||
<Text size="xs" color="text-white" opacity={0.5} block>Automated</Text>
|
||||
<Text size="sm" color="text-white" weight="bold" block className="uppercase tracking-widest">Creating Session</Text>
|
||||
<Text size="xs" color="text-gray-500" block font="mono">AUTOMATED</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box h="2.5" fullWidth bg="bg-white/5" rounded="full" overflow="hidden">
|
||||
<Box h="1" fullWidth bg="white/5" rounded="none" overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
h="full"
|
||||
bg="bg-primary-blue/60"
|
||||
bg="primary-accent"
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: '75%' }}
|
||||
transition={{ duration: 2, ease: 'easeInOut' }}
|
||||
@@ -74,8 +74,8 @@ export function CompanionAutomationMockup() {
|
||||
|
||||
{/* Simple CTA */}
|
||||
<Box display="flex" justifyContent="center">
|
||||
<Box bg="bg-primary-blue/20" color="text-primary-blue" px={6} py={2.5} rounded="lg" border borderColor="border-primary-blue/40">
|
||||
<Text size="sm" weight="semibold">One Click</Text>
|
||||
<Box bg="primary-accent/10" color="text-primary-accent" px={6} py={2.5} rounded="none" border borderColor="primary-accent/30">
|
||||
<Text size="xs" weight="bold" className="uppercase tracking-[0.2em]">One Click</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -86,7 +86,7 @@ export function CompanionAutomationMockup() {
|
||||
|
||||
// Desktop version - richer with more automation steps
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 3, md: 4, lg: 6 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 3, md: 4, lg: 6 }} overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial="hidden"
|
||||
@@ -103,12 +103,14 @@ export function CompanionAutomationMockup() {
|
||||
{/* Companion App Header - Enhanced */}
|
||||
<Box as={motion.div} variants={{ hidden: { opacity: 0, y: -10 }, visible: { opacity: 1, y: 0 } }}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 2.5, md: 3, lg: 4 }} mb={{ base: 3, md: 4, lg: 5 }}>
|
||||
<Box h={{ base: 10, md: 12, lg: 14 }} w={{ base: 10, md: 12, lg: 14 }} bg="bg-primary-blue/20" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/40" display="flex" alignItems="center" justifyContent="center" shadow="lg">
|
||||
<Box h={{ base: 6, md: 7, lg: 8 }} w={{ base: 6, md: 7, lg: 8 }} bg="bg-primary-blue/60" rounded="sm" />
|
||||
<Box h={{ base: 10, md: 12, lg: 14 }} w={{ base: 10, md: 12, lg: 14 }} bg="primary-accent/10" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" display="flex" alignItems="center" justifyContent="center" className="relative">
|
||||
<Box h={{ base: 6, md: 7, lg: 8 }} w={{ base: 6, md: 7, lg: 8 }} bg="primary-accent/40" />
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
<Box position="absolute" bottom="-1px" right="-1px" w="2" h="2" borderBottom borderRight borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={2} fontSize={{ base: 'base', md: 'lg', lg: 'xl' }} weight="semibold" color="text-white">GridPilot Companion</Heading>
|
||||
<Text size={{ base: 'xs', md: 'sm', lg: 'base' }} color="text-white" opacity={0.5} block>Automated Session Creator</Text>
|
||||
<Heading level={2} fontSize={{ base: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" className="uppercase tracking-widest">GridPilot Companion</Heading>
|
||||
<Text size={{ base: 'xs', md: 'sm', lg: 'base' }} color="text-gray-500" block font="mono">AUTOMATED SESSION CREATOR</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -118,26 +120,25 @@ export function CompanionAutomationMockup() {
|
||||
as={motion.div}
|
||||
variants={{ hidden: { opacity: 0, y: 10 }, visible: { opacity: 1, y: 0 } }}
|
||||
position="relative"
|
||||
bg="bg-charcoal-outline"
|
||||
rounded="lg"
|
||||
bg="panel-gray/40"
|
||||
rounded="none"
|
||||
p={{ base: 3, md: 4, lg: 5 }}
|
||||
border
|
||||
borderWidth="2px"
|
||||
borderColor="border-primary-blue/40"
|
||||
borderWidth="1px"
|
||||
borderColor="border-gray/50"
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* Browser Window Mockup */}
|
||||
<Stack gap={{ base: 3, md: 4 }}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 2, md: 2.5 }} pb={{ base: 3, md: 4 }} borderBottom borderColor="border-white/10">
|
||||
<Box h={{ base: 2.5, md: 3 }} w={{ base: 2.5, md: 3 }} bg="bg-red-500/60" rounded="full" />
|
||||
<Box h={{ base: 2.5, md: 3 }} w={{ base: 2.5, md: 3 }} bg="bg-warning-amber/60" rounded="full" />
|
||||
<Box h={{ base: 2.5, md: 3 }} w={{ base: 2.5, md: 3 }} bg="bg-performance-green/60" rounded="full" />
|
||||
<Box flexGrow={1} h={{ base: 2.5, md: 3 }} bg="bg-white/5" rounded="sm" ml={2} px={2} display="flex" alignItems="center">
|
||||
<Box display="flex" alignItems="center" gap={{ base: 2, md: 2.5 }} pb={{ base: 3, md: 4 }} borderBottom borderColor="border-gray/30">
|
||||
<Box h="2" w="2" bg="critical-red/40" rounded="none" />
|
||||
<Box h="2" w="2" bg="warning-amber/40" rounded="none" />
|
||||
<Box h="2" w="2" bg="success-green/40" rounded="none" />
|
||||
<Box flexGrow={1} h="4" bg="graphite-black" rounded="none" ml={2} px={2} display="flex" alignItems="center" border borderColor="border-gray/30">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '8px' }}
|
||||
color="text-white"
|
||||
opacity={0.3}
|
||||
color="text-gray-500"
|
||||
font="mono"
|
||||
>
|
||||
members.iracing.com
|
||||
@@ -169,54 +170,53 @@ export function CompanionAutomationMockup() {
|
||||
as={motion.div}
|
||||
h={{ base: 7, md: 8, lg: 9 }}
|
||||
w={{ base: 7, md: 8, lg: 9 }}
|
||||
rounded="full"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
flexShrink={0}
|
||||
border
|
||||
borderWidth="2px"
|
||||
borderWidth="1px"
|
||||
bg={
|
||||
step.status === 'Complete'
|
||||
? 'bg-performance-green/40'
|
||||
? 'success-green/10'
|
||||
: step.status === 'Running'
|
||||
? 'bg-primary-blue/40'
|
||||
: 'bg-charcoal-outline'
|
||||
? 'primary-accent/10'
|
||||
: 'transparent'
|
||||
}
|
||||
borderColor={
|
||||
step.status === 'Complete'
|
||||
? 'border-performance-green/60'
|
||||
? 'success-green/40'
|
||||
: step.status === 'Running'
|
||||
? 'border-primary-blue/60'
|
||||
: 'border-white/20'
|
||||
? 'primary-accent/40'
|
||||
: 'border-gray/30'
|
||||
}
|
||||
animate={shouldReduceMotion ? {} : step.status === 'Running' ? {
|
||||
scale: [1, 1.15, 1],
|
||||
opacity: [0.4, 1, 0.4]
|
||||
} : {}}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
>
|
||||
{step.status === 'Complete' && (
|
||||
<Icon icon={Check} size={5} color="text-performance-green" />
|
||||
<Box w="2" h="2" bg="success-green" />
|
||||
)}
|
||||
{step.status === 'Running' && (
|
||||
<Box h={{ base: 3, md: 4 }} w={{ base: 3, md: 4 }} bg="bg-primary-blue" rounded="full" />
|
||||
<Box h="2" w="2" bg="primary-accent" />
|
||||
)}
|
||||
{step.status === 'Pending' && (
|
||||
<Box h={{ base: 2, md: 2.5 }} w={{ base: 2, md: 2.5 }} bg="bg-white/30" rounded="full" />
|
||||
<Box h="1" w="1" bg="gray-700" />
|
||||
)}
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Text size={{ base: 'sm', md: 'base', lg: 'lg' }} color="text-white" weight="medium" block>{step.label}</Text>
|
||||
<Text size={{ base: 'xs', md: 'sm' }} color="text-white" opacity={0.5} block>{step.detail}</Text>
|
||||
<Text size={{ base: 'sm', md: 'base' }} color="text-white" weight="bold" block className="uppercase tracking-widest">{step.label}</Text>
|
||||
<Text size="xs" color="text-gray-500" block font="mono">{step.detail.toUpperCase()}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
{step.status !== 'Pending' && (
|
||||
<Box h={{ base: 2.5, md: 3 }} fullWidth bg="bg-white/5" rounded="full" overflow="hidden" ml={{ base: 9, md: 10, lg: 11 }}>
|
||||
<Box h="1" fullWidth bg="white/5" rounded="none" overflow="hidden" ml={{ base: 9, md: 10, lg: 11 }}>
|
||||
<Box
|
||||
as={motion.div}
|
||||
h="full"
|
||||
bg={step.status === 'Complete' ? 'bg-performance-green/60' : 'bg-primary-blue/60'}
|
||||
bg={step.status === 'Complete' ? 'success-green/60' : 'primary-accent/60'}
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: step.status === 'Complete' ? '100%' : '65%' }}
|
||||
transition={{ duration: 2, ease: 'easeInOut' }}
|
||||
@@ -238,36 +238,33 @@ export function CompanionAutomationMockup() {
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
bg="bg-deep-graphite/90"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="backdrop-blur-sm"
|
||||
bg="graphite-black"
|
||||
px={3}
|
||||
py={2}
|
||||
rounded="full"
|
||||
py={1.5}
|
||||
rounded="none"
|
||||
border
|
||||
borderWidth="2px"
|
||||
borderColor="border-primary-blue/40"
|
||||
borderWidth="1px"
|
||||
borderColor="primary-accent/40"
|
||||
animate={shouldReduceMotion ? {} : {
|
||||
boxShadow: [
|
||||
'0 0 12px rgba(25,140,255,0.3)',
|
||||
'0 0 20px rgba(25,140,255,0.5)',
|
||||
'0 0 12px rgba(25,140,255,0.3)'
|
||||
'0 0 8px rgba(25,140,255,0.2)',
|
||||
'0 0 15px rgba(25,140,255,0.4)',
|
||||
'0 0 8px rgba(25,140,255,0.2)'
|
||||
]
|
||||
}}
|
||||
transition={{ duration: 2, repeat: Infinity }}
|
||||
>
|
||||
<Box
|
||||
as={motion.div}
|
||||
h={{ base: 2.5, md: 3 }}
|
||||
w={{ base: 2.5, md: 3 }}
|
||||
bg="bg-primary-blue"
|
||||
rounded="full"
|
||||
h="1.5"
|
||||
w="1.5"
|
||||
bg="primary-accent"
|
||||
animate={shouldReduceMotion ? {} : {
|
||||
opacity: [1, 0.5, 1]
|
||||
opacity: [1, 0.3, 1]
|
||||
}}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
/>
|
||||
<Text size={{ base: 'xs', md: 'sm' }} color="text-primary-blue" weight="medium">Running</Text>
|
||||
<Text size="xs" color="text-primary-accent" weight="bold" className="uppercase tracking-widest">Running</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -278,32 +275,36 @@ export function CompanionAutomationMockup() {
|
||||
display="flex"
|
||||
flexDirection="col"
|
||||
alignItems="center"
|
||||
gap={{ base: 2, md: 3 }}
|
||||
gap={3}
|
||||
>
|
||||
<Box
|
||||
as={motion.div}
|
||||
bg="bg-primary-blue/20"
|
||||
color="text-primary-blue"
|
||||
bg="primary-accent/10"
|
||||
color="text-primary-accent"
|
||||
px={8}
|
||||
py={4}
|
||||
rounded="lg"
|
||||
py={3}
|
||||
rounded="none"
|
||||
border
|
||||
borderWidth="2px"
|
||||
borderColor="border-primary-blue/40"
|
||||
borderWidth="1px"
|
||||
borderColor="primary-accent/40"
|
||||
cursor="pointer"
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
scale: 1.03,
|
||||
boxShadow: '0 4px 24px rgba(25,140,255,0.3)',
|
||||
scale: 1.02,
|
||||
borderColor: '#198CFF',
|
||||
transition: { duration: 0.15 }
|
||||
}}
|
||||
className="relative"
|
||||
>
|
||||
<Text size={{ base: 'base', md: 'lg' }} weight="semibold">Create Session</Text>
|
||||
<Text size="sm" weight="bold" className="uppercase tracking-[0.2em]">Create Session</Text>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
<Box position="absolute" bottom="-1px" right="-1px" w="2" h="2" borderBottom borderRight borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.4}
|
||||
color="text-gray-500"
|
||||
font="mono"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
One click. All fields automated.
|
||||
</Text>
|
||||
|
||||
@@ -11,7 +11,10 @@ export function DriverProfileMockup() {
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMobile(window.innerWidth < 768);
|
||||
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
return () => window.removeEventListener('resize', checkMobile);
|
||||
}, []);
|
||||
|
||||
const stats = [
|
||||
@@ -26,77 +29,58 @@ export function DriverProfileMockup() {
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
|
||||
<Stack gap={4}>
|
||||
<Box>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box position="relative" h="12" w="12" rounded="full" border borderWidth="2px" borderColor="border-primary-blue/50" overflow="hidden" bg="bg-charcoal-outline">
|
||||
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="2xl">🏎️</Text>
|
||||
</Box>
|
||||
<Box position="relative" h="12" w="12" rounded="none" border borderWidth="1px" borderColor="primary-accent/50" overflow="hidden" bg="panel-gray" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="2xl">🏎️</Text>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text weight="bold" color="text-white" block>Driver Profile</Text>
|
||||
<Text size="xs" color="text-white" opacity={0.5} block>Cross-league</Text>
|
||||
<Text weight="bold" color="text-white" block className="uppercase tracking-widest">Driver Profile</Text>
|
||||
<Text size="xs" color="text-gray-500" block font="mono">CROSS-LEAGUE</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text size="2xl" weight="bold" color="text-charcoal-outline">#33</Text>
|
||||
<Text size="2xl" weight="bold" color="text-gray-800" font="mono">#33</Text>
|
||||
</Box>
|
||||
|
||||
<Box position="relative" h="2" bg="bg-charcoal-outline" rounded="full" overflow="hidden" mb={1}>
|
||||
<Box position="relative" h="1" bg="white/5" rounded="none" overflow="hidden" mb={1}>
|
||||
<Box
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
bg="bg-gradient-to-r from-primary-blue to-neon-aqua"
|
||||
rounded="full"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
bg="primary-accent"
|
||||
style={{ width: '86%' }}
|
||||
/>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="end">
|
||||
<Text size="xs" color="text-gray-400">2150 GP Rating</Text>
|
||||
<Text size="xs" color="text-primary-accent" font="mono" weight="bold">2150 GP RATING</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={2} block>Career Stats</Text>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" mb={2} block className="uppercase tracking-widest">Career Stats</Text>
|
||||
<Box display="grid" gridCols={3} gap={2}>
|
||||
{stats.slice(0, 3).map((stat) => (
|
||||
<Box
|
||||
key={stat.label}
|
||||
bg="bg-iron-gray/50"
|
||||
bg="panel-gray/40"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="lg"
|
||||
borderColor="border-gray/50"
|
||||
rounded="none"
|
||||
p={2}
|
||||
textAlign="center"
|
||||
>
|
||||
<Text weight="bold" color="text-white" font="mono" block>
|
||||
{stat.value}{stat.suffix}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-400" mt={0.5} block>{stat.label}</Text>
|
||||
<Text size="xs" color="text-gray-500" mt={0.5} block className="uppercase tracking-tighter">{stat.label}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={2} block>Recent Form</Text>
|
||||
<Box h="16" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" rounded="lg" p={2} display="flex" alignItems="end" gap={1}>
|
||||
{formData.slice(-6).map((value, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
flexGrow={1}
|
||||
bg="bg-gradient-to-t from-performance-green to-primary-blue"
|
||||
rounded="sm"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ height: `${value}%` }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
@@ -120,7 +104,7 @@ export function DriverProfileMockup() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }}
|
||||
@@ -129,50 +113,46 @@ export function DriverProfileMockup() {
|
||||
>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
|
||||
<Box position="relative" h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="full" border borderWidth="2px" borderColor="border-primary-blue/50" overflow="hidden" bg="bg-charcoal-outline">
|
||||
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏎️</Text>
|
||||
</Box>
|
||||
<Box position="relative" h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="none" border borderWidth="1px" borderColor="primary-accent/50" overflow="hidden" bg="panel-gray" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏎️</Text>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={{ base: 1, sm: 1.5, md: 2 }} block>Driver Profile</Text>
|
||||
<Text size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={{ base: 1, sm: 1.5, md: 2 }} block className="uppercase tracking-widest">Driver Profile</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
color="text-gray-500"
|
||||
block
|
||||
font="mono"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Cross-league racing identity
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold" color="text-charcoal-outline">#33</Text>
|
||||
<Text size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold" color="text-gray-800" font="mono">#33</Text>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" flexWrap="wrap" alignItems="center" gap={{ base: 1, sm: 2, md: 3, lg: 4 }} mb={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Text size="xs" color="text-gray-400">GridPilot Rating:</Text>
|
||||
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">GP RATING:</Text>
|
||||
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={2150} />
|
||||
<Text size="xs" color="text-gray-400">iRating:</Text>
|
||||
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest ml-4">iRATING:</Text>
|
||||
<AnimatedRating shouldReduceMotion={shouldReduceMotion ?? false} value={3200} />
|
||||
</Box>
|
||||
|
||||
<Box position="relative" h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} bg="bg-charcoal-outline" rounded="full" overflow="hidden">
|
||||
<Box position="relative" h="1" bg="white/5" rounded="none" overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
bg="bg-gradient-to-r from-primary-blue to-neon-aqua"
|
||||
rounded="full"
|
||||
bg="primary-accent"
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: '86%' }}
|
||||
transition={{ delay: shouldReduceMotion ? 0 : 0.4, duration: 0.8, ease: 'easeOut' }}
|
||||
/>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="end" mt={1}>
|
||||
<Text size="xs" color="text-gray-400">86%</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@@ -182,8 +162,7 @@ export function DriverProfileMockup() {
|
||||
animate="visible"
|
||||
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }}
|
||||
>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={1} block>Career Statistics</Text>
|
||||
<Text size="xs" color="text-white" opacity={0.5} mb={{ base: 1, sm: 2, md: 3 }} block>Aggregated across all leagues</Text>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-[0.2em]">Career Statistics</Text>
|
||||
|
||||
<Box display="grid" gridCols={{ base: 2, md: 5 }} gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
{stats.map((stat, index) => (
|
||||
@@ -191,10 +170,10 @@ export function DriverProfileMockup() {
|
||||
key={stat.label}
|
||||
as={motion.div}
|
||||
variants={itemVariants}
|
||||
bg="bg-iron-gray/50"
|
||||
bg="panel-gray/40"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="lg"
|
||||
borderColor="border-gray/50"
|
||||
rounded="none"
|
||||
p={{ base: 1.5, sm: 2, md: 3 }}
|
||||
textAlign="center"
|
||||
>
|
||||
@@ -204,7 +183,7 @@ export function DriverProfileMockup() {
|
||||
delay={index * 0.1}
|
||||
suffix={stat.suffix ?? ''}
|
||||
/>
|
||||
<Text size="xs" color="text-gray-400" mt={0.5} block>{stat.label}</Text>
|
||||
<Text size="xs" color="text-gray-500" mt={0.5} block className="uppercase tracking-tighter font-bold">{stat.label}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
@@ -212,22 +191,20 @@ export function DriverProfileMockup() {
|
||||
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: shouldReduceMotion ? 0 : 0.6 }}
|
||||
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }}
|
||||
transition={{ delay: 0.6 }}
|
||||
>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={1} block>Recent Form</Text>
|
||||
<Text size="xs" color="text-white" opacity={0.5} mb={{ base: 1, sm: 2, md: 3 }} block>Performance trend over last 10 races</Text>
|
||||
|
||||
<Box h={{ base: 12, sm: 16, md: 20 }} bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3 }} display="flex" alignItems="end" gap={0.5}>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-[0.2em]">Recent Form</Text>
|
||||
<Box h={{ base: 12, sm: 16, md: 20 }} bg="panel-gray/20" border borderColor="border-gray/50" rounded="none" p={{ base: 1.5, sm: 2, md: 3 }} display="flex" alignItems="end" gap={1}>
|
||||
{formData.map((value, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
as={motion.div}
|
||||
flexGrow={1}
|
||||
bg="bg-gradient-to-t from-performance-green to-primary-blue"
|
||||
rounded="sm"
|
||||
bg="primary-accent"
|
||||
opacity={0.4 + (i * 0.06)}
|
||||
rounded="none"
|
||||
initial={{ height: 0 }}
|
||||
animate={{ height: `${value}%` }}
|
||||
transition={{
|
||||
@@ -238,66 +215,6 @@ export function DriverProfileMockup() {
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="between" mt={0.5}>
|
||||
<Text size="xs" color="text-gray-500">Last 10 races</Text>
|
||||
<Text size="xs" color="text-gray-500">Recent</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: shouldReduceMotion ? 0 : 0.8 }}
|
||||
>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={1} block>Teams</Text>
|
||||
<Text size="xs" color="text-white" opacity={0.5} mb={{ base: 1, sm: 2, md: 3 }} block>Current and past team memberships</Text>
|
||||
|
||||
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
{[
|
||||
{ team: 'Red Bull Racing', status: 'Current', color: 'primary-blue' },
|
||||
{ team: 'Mercedes AMG', status: '2023', color: 'charcoal-outline' }
|
||||
].map((team, i) => (
|
||||
<Box
|
||||
key={team.team}
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, x: shouldReduceMotion ? 0 : -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: shouldReduceMotion ? 0 : 0.9 + i * 0.1 }}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
bg="bg-iron-gray/30"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="lg"
|
||||
p={{ base: 1, sm: 1.5, md: 2 }}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 2, sm: 3 }}>
|
||||
<Box h={{ base: 5, sm: 6, md: 8 }} w={{ base: 5, sm: 6, md: 8 }} rounded="sm" border borderColor="border-primary-blue/30" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }}>🏁</Text>
|
||||
</Box>
|
||||
<Box h={{ base: 1.5, sm: 2, md: 3 }} w={{ base: 16, sm: 20, md: 32 }} bg="bg-white/10" rounded="sm" />
|
||||
</Box>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} rounded="sm" bg={team.status === 'Current' ? 'bg-primary-blue/20' : 'bg-charcoal-outline'}>
|
||||
<Text size="xs" color={team.status === 'Current' ? 'text-primary-blue' : 'text-gray-400'}>
|
||||
{team.status}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: shouldReduceMotion ? 0 : 1.2 }}
|
||||
mt={{ base: 2, sm: 3, md: 4 }}
|
||||
textAlign="center"
|
||||
>
|
||||
<Text size="xs" color="text-gray-400">Active in 3 leagues</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@@ -318,7 +235,7 @@ function AnimatedRating({ shouldReduceMotion, value }: { shouldReduceMotion: boo
|
||||
}, [shouldReduceMotion, count, value]);
|
||||
|
||||
return (
|
||||
<Box as={motion.span} weight="bold" color="text-primary-blue" font="mono" size={{ base: 'sm', sm: 'base', md: 'lg' }}>
|
||||
<Box as={motion.span} weight="bold" color="text-primary-accent" font="mono" size={{ base: 'sm', sm: 'base' }}>
|
||||
{shouldReduceMotion ? value : <Box as={motion.span}>{rounded}</Box>}
|
||||
</Box>
|
||||
);
|
||||
@@ -348,7 +265,7 @@ function AnimatedCounter({
|
||||
}, [shouldReduceMotion, count, value, delay]);
|
||||
|
||||
return (
|
||||
<Box weight="bold" color="text-white" font="mono" size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }}>
|
||||
<Box weight="bold" color="text-white" font="mono" size={{ base: 'sm', sm: 'base', md: 'lg' }}>
|
||||
{shouldReduceMotion ? value : <Box as={motion.span}>{rounded}</Box>}{suffix}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -43,86 +43,58 @@ export function LeagueDiscoveryMockup() {
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
|
||||
<Box mb={3}>
|
||||
<Box h="4" w="40" bg="bg-white/10" rounded="sm" mb={3} />
|
||||
<Box h="1.5" w="40" bg="white/10" rounded="none" mb={3} />
|
||||
<Box display="flex" gap={2} flexWrap="wrap">
|
||||
{['Game', 'Region'].map((filter) => (
|
||||
<Box
|
||||
key={filter}
|
||||
h="6"
|
||||
h="5"
|
||||
px={3}
|
||||
bg="bg-charcoal-outline"
|
||||
bg="panel-gray"
|
||||
border
|
||||
borderColor="border-primary-blue/30"
|
||||
rounded="full"
|
||||
borderColor="primary-accent/30"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box h="1.5" w="10" bg="bg-white/10" rounded="sm" />
|
||||
<Box h="1" w="8" bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Stack gap={3}>
|
||||
<Stack gap={2}>
|
||||
{leagues.map((league) => (
|
||||
<Box
|
||||
key={league.name}
|
||||
bg="bg-iron-gray/80"
|
||||
bg="panel-gray/40"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="lg"
|
||||
borderColor="border-gray/50"
|
||||
rounded="none"
|
||||
p={3}
|
||||
>
|
||||
<Box display="flex" alignItems="start" justifyContent="between" mb={2}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box h="10" w="10" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/30" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box h="10" w="10" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" bg="graphite-black" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xl">{league.icon}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box h="3" w="32" bg="bg-white/10" rounded="sm" mb={1} />
|
||||
<Box h="1.5" w="32" bg="white/10" rounded="none" mb={1.5} />
|
||||
<Box display="flex" gap={1}>
|
||||
<Box as="span" px={1.5} py={0.5} bg="bg-primary-blue/20" rounded="sm">
|
||||
<Text size="xs" color="text-primary-blue">{league.carClass}</Text>
|
||||
</Box>
|
||||
<Box as="span" px={1.5} py={0.5} bg="bg-neon-aqua/20" rounded="sm">
|
||||
<Text size="xs" color="text-neon-aqua">{league.region}</Text>
|
||||
<Box as="span" px={1.5} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/20">
|
||||
<Text size="xs" color="text-primary-accent" weight="bold" font="mono">{league.carClass}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Text size="xs" color="text-gray-400">{league.drivers} drivers</Text>
|
||||
<Text size="xs" color="text-gray-400">•</Text>
|
||||
<Text size="xs" color="text-gray-400">{league.schedule}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<Box
|
||||
as="svg"
|
||||
key={i}
|
||||
w="3"
|
||||
h="3"
|
||||
color={i < Math.floor(league.rating) ? 'text-warning-amber' : 'text-charcoal-outline'}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="fill-current"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Box as="button" px={2} py={1} bg="bg-primary-blue" rounded="sm">
|
||||
<Text size="xs" color="text-white">Join</Text>
|
||||
<Text size="xs" color="text-gray-500" font="mono">{league.drivers} DRIVERS • {league.schedule.toUpperCase()}</Text>
|
||||
<Box px={2} py={0.5} bg="primary-accent" rounded="none">
|
||||
<Text size="xs" color="text-white" weight="bold">JOIN</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -141,25 +113,25 @@ export function LeagueDiscoveryMockup() {
|
||||
};
|
||||
|
||||
const cardVariants = {
|
||||
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 20 },
|
||||
hidden: { opacity: 0, x: shouldReduceMotion ? 0 : -20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { type: 'spring' as const, stiffness: 200, damping: 20 }
|
||||
x: 0,
|
||||
transition: { duration: 0.4 }
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
mb={{ base: 1.5, sm: 3, md: 4, lg: 6 }}
|
||||
>
|
||||
<Box h={{ base: 3, sm: 4, md: 5, lg: 6 }} w={{ base: 32, sm: 40, md: 52 }} bg="bg-white/10" rounded="sm" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} />
|
||||
<Box h="2" w="48" bg="white/10" rounded="none" mb={4} />
|
||||
|
||||
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }} flexWrap="wrap">
|
||||
<Box display="flex" gap={2} flexWrap="wrap">
|
||||
{['Game', 'Region', 'Skill'].map((filter, i) => (
|
||||
<Box
|
||||
key={filter}
|
||||
@@ -167,16 +139,16 @@ export function LeagueDiscoveryMockup() {
|
||||
initial={{ opacity: 0, scale: shouldReduceMotion ? 1 : 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: shouldReduceMotion ? 0 : i * 0.1 }}
|
||||
h={{ base: 5, sm: 6, md: 7, lg: 8 }}
|
||||
px={{ base: 2, sm: 3, md: 4 }}
|
||||
bg="bg-charcoal-outline"
|
||||
h="6"
|
||||
px={4}
|
||||
bg="panel-gray"
|
||||
border
|
||||
borderColor="border-primary-blue/30"
|
||||
rounded="full"
|
||||
borderColor="primary-accent/30"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box h={{ base: 1, sm: 1.5, md: 2 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-white/10" rounded="sm" />
|
||||
<Box h="1" w="10" bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
@@ -188,7 +160,7 @@ export function LeagueDiscoveryMockup() {
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
<Stack gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
|
||||
<Stack gap={2}>
|
||||
{leagues.map((league, index) => (
|
||||
<Box
|
||||
key={league.name}
|
||||
@@ -197,157 +169,90 @@ export function LeagueDiscoveryMockup() {
|
||||
onHoverStart={() => !shouldReduceMotion && setHoveredIndex(index)}
|
||||
onHoverEnd={() => setHoveredIndex(null)}
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
scale: 1.02,
|
||||
y: -4,
|
||||
x: 4,
|
||||
borderColor: '#198CFF30',
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
bg="bg-iron-gray/80"
|
||||
bg="panel-gray/20"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="lg"
|
||||
borderColor="border-gray/50"
|
||||
rounded="none"
|
||||
p={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="backdrop-blur-sm"
|
||||
>
|
||||
<Box display="flex" alignItems="start" justifyContent="between" mb={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
<Box h={{ base: 7, sm: 8, md: 10, lg: 12 }} w={{ base: 7, sm: 8, md: 10, lg: 12 }} rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/30" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" shadow="0_0_12px_rgba(25,140,255,0.2)">
|
||||
<Box display="flex" alignItems="start" justifyContent="between" mb={3}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box h={{ base: 7, sm: 8, md: 10, lg: 12 }} w={{ base: 7, sm: 8, md: 10, lg: 12 }} rounded="none" border borderWidth="1px" borderColor="primary-accent/30" bg="graphite-black" display="flex" alignItems="center" justifyContent="center" className="relative">
|
||||
<Text size={{ base: 'base', sm: 'lg', md: 'xl', lg: '2xl' }}>{league.icon}</Text>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Box h={{ base: 2.5, sm: 3, md: 4 }} w={{ base: 28, sm: 32, md: 40 }} bg="bg-white/10" rounded="sm" mb={{ base: 1, sm: 1.5, md: 2 }} />
|
||||
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-primary-blue/20" rounded="sm">
|
||||
<Box h="2" w="40" bg="white/10" rounded="none" mb={2} />
|
||||
<Box display="flex" gap={2}>
|
||||
<Box as="span" px={2} py={0.5} bg="primary-accent/10" border borderColor="primary-accent/20">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-primary-blue"
|
||||
color="text-primary-accent"
|
||||
weight="bold"
|
||||
font="mono"
|
||||
>
|
||||
{league.carClass}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-neon-aqua/20" rounded="sm">
|
||||
<Box as="span" px={2} py={0.5} bg="telemetry-aqua/10" border borderColor="telemetry-aqua/20">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-neon-aqua"
|
||||
color="text-telemetry-aqua"
|
||||
weight="bold"
|
||||
font="mono"
|
||||
>
|
||||
{league.region}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box as="span" px={{ base: 1, sm: 1.5, md: 2 }} py={0.5} bg="bg-charcoal-outline" rounded="sm">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-400"
|
||||
>
|
||||
{league.skill}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Box as="svg" w={{ base: 2.5, sm: 3, md: 4 }} h={{ base: 2.5, sm: 3, md: 4 }} color="text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-400"
|
||||
>
|
||||
{league.drivers} drivers
|
||||
</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Box as="svg" w={{ base: 2.5, sm: 3, md: 4 }} h={{ base: 2.5, sm: 3, md: 4 }} color="text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-400"
|
||||
>
|
||||
{league.schedule}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<Box
|
||||
as={motion.svg}
|
||||
key={i}
|
||||
w={{ base: 2.5, sm: 3, md: 4 }}
|
||||
h={{ base: 2.5, sm: 3, md: 4 }}
|
||||
color={i < Math.floor(league.rating) ? 'text-warning-amber' : 'text-charcoal-outline'}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="fill-current"
|
||||
viewBox="0 0 20 20"
|
||||
animate={hoveredIndex === index && !shouldReduceMotion ? {
|
||||
scale: [1, 1.2, 1],
|
||||
transition: { delay: i * 0.05, duration: 0.3 }
|
||||
} : {}}
|
||||
>
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</Box>
|
||||
))}
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-400"
|
||||
ml={1}
|
||||
color="text-gray-500"
|
||||
font="mono"
|
||||
weight="bold"
|
||||
>
|
||||
{league.rating}
|
||||
{league.drivers} DRIVERS
|
||||
</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-500"
|
||||
font="mono"
|
||||
weight="bold"
|
||||
>
|
||||
{league.schedule.toUpperCase()}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Box display="flex" gap={2}>
|
||||
<Box
|
||||
as={motion.button}
|
||||
whileHover={shouldReduceMotion ? {} : { scale: 1.05 }}
|
||||
whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}
|
||||
px={{ base: 1.5, sm: 2, md: 3 }}
|
||||
py={0.5}
|
||||
bg="bg-primary-blue"
|
||||
px={3}
|
||||
py={1}
|
||||
bg="primary-accent"
|
||||
color="text-white"
|
||||
rounded="sm"
|
||||
transition
|
||||
hoverBg="bg-primary-blue/80"
|
||||
rounded="none"
|
||||
>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
weight="bold"
|
||||
>
|
||||
Join
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
as={motion.button}
|
||||
whileHover={shouldReduceMotion ? {} : { scale: 1.05 }}
|
||||
whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}
|
||||
px={{ base: 1.5, sm: 2, md: 3 }}
|
||||
py={0.5}
|
||||
bg="bg-charcoal-outline"
|
||||
color="text-gray-300"
|
||||
rounded="sm"
|
||||
transition
|
||||
hoverBg="bg-charcoal-outline/80"
|
||||
>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
View
|
||||
JOIN
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -17,47 +17,45 @@ export function LeagueHomeMockup() {
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
|
||||
<Stack gap={4}>
|
||||
<Box display="flex" alignItems="center" gap={3} mb={2}>
|
||||
<Box h="12" w="12" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/50" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" shadow="0_0_20px_rgba(25,140,255,0.3)">
|
||||
<Box h="12" w="12" rounded="none" border borderWidth="1px" borderColor="primary-accent/50" bg="panel-gray" display="flex" alignItems="center" justifyContent="center" className="relative">
|
||||
<Text size="2xl">🏆</Text>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="base" weight="bold" color="text-white" block>Super GT</Text>
|
||||
<Text size="xs" color="text-gray-400" block>Round 8/12</Text>
|
||||
<Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">Super GT</Text>
|
||||
<Text size="xs" color="text-gray-500" block font="mono">ROUND 8/12</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Next Race</Text>
|
||||
<Box position="relative" display="flex" alignItems="center" gap={3} bg="bg-iron-gray" rounded="lg" p={3} border borderColor="border-charcoal-outline">
|
||||
<Box h="8" w="8" bg="bg-charcoal-outline" rounded border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Next Race</Text>
|
||||
<Box position="relative" display="flex" alignItems="center" gap={3} bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
|
||||
<Box h="8" w="8" bg="graphite-black" rounded="none" border borderColor="primary-accent/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="base">🏁</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Box h="2.5" w="28" bg="bg-white/10" rounded="sm" mb={2} />
|
||||
<Box h="2" w="20" bg="bg-white/5" rounded="sm" />
|
||||
<Box h="2" w="28" bg="white/10" rounded="none" mb={2} />
|
||||
<Box h="1.5" w="20" bg="white/5" rounded="none" />
|
||||
</Box>
|
||||
<Box w="2" h="2" bg="bg-primary-blue" rounded="full"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="shadow-glow"
|
||||
/>
|
||||
<Box w="1.5" h="1.5" bg="primary-accent" rounded="full" className="animate-pulse" />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Latest Result</Text>
|
||||
<Box bg="bg-iron-gray" rounded="lg" p={3} border borderColor="border-charcoal-outline">
|
||||
<Box display="flex" alignItems="center" gap={3} py={2} borderBottom borderColor="border-charcoal-outline">
|
||||
<Box h="2.5" w="6" bg="bg-white/10" rounded="sm" />
|
||||
<Box h="2.5" flexGrow={1} bg="bg-white/10" rounded="sm" />
|
||||
<Box h="2.5" w="10" bg="bg-white/10" rounded="sm" />
|
||||
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Latest Result</Text>
|
||||
<Box bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
|
||||
<Box display="flex" alignItems="center" gap={3} py={2} borderBottom borderColor="border-gray/30">
|
||||
<Box h="1.5" w="6" bg="white/10" rounded="none" />
|
||||
<Box h="1.5" flexGrow={1} bg="white/10" rounded="none" />
|
||||
<Box h="1.5" w="10" bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={3} py={2}>
|
||||
<Box h="2" w="6" bg="bg-white/5" rounded="sm" />
|
||||
<Box h="2" flexGrow={1} bg="bg-white/5" rounded="sm" />
|
||||
<Box h="2" w="10" bg="bg-performance-green/20" rounded="sm" />
|
||||
<Box h="1.5" w="6" bg="white/5" rounded="none" />
|
||||
<Box h="1.5" flexGrow={1} bg="white/5" rounded="none" />
|
||||
<Box h="1.5" w="10" bg="success-green/20" rounded="none" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -80,7 +78,7 @@ export function LeagueHomeMockup() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
variants={containerVariants}
|
||||
@@ -90,44 +88,27 @@ export function LeagueHomeMockup() {
|
||||
<Stack gap={{ base: 1.5, sm: 3, md: 4, lg: 6 }}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} mb={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/50" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" shadow="0_0_20px_rgba(25,140,255,0.3)">
|
||||
<Box h={{ base: 8, sm: 10, md: 12, lg: 16 }} w={{ base: 8, sm: 10, md: 12, lg: 16 }} rounded="none" border borderWidth="1px" borderColor="primary-accent/50" bg="panel-gray" display="flex" alignItems="center" justifyContent="center" className="relative">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl', lg: '3xl' }}>🏆</Text>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={2} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={0.5}>Super GT Championship</Heading>
|
||||
<Heading level={2} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" mb={0.5} className="uppercase tracking-widest">Super GT Championship</Heading>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '9px' }}
|
||||
color="text-gray-400"
|
||||
color="text-gray-500"
|
||||
font="mono"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Season 3 • Round 8/12
|
||||
SEASON 3 • ROUND 8/12
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '8px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
mt={{ base: 1, sm: 1.5, md: 2 }}
|
||||
block
|
||||
>
|
||||
Your league's dedicated home page
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} block>Upcoming Races</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '8px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
mb={{ base: 1.5, sm: 2, md: 3 }}
|
||||
block
|
||||
>
|
||||
Calendar automatically synced from iRacing
|
||||
</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-500" mb={{ base: 1.5, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Upcoming Races</Text>
|
||||
<Stack gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Box
|
||||
@@ -137,25 +118,23 @@ export function LeagueHomeMockup() {
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
|
||||
bg="bg-iron-gray"
|
||||
rounded="lg"
|
||||
bg="panel-gray/40"
|
||||
rounded="none"
|
||||
p={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
shadow="inset_0_1px_2px_rgba(0,0,0,0.2)"
|
||||
borderColor="border-gray/50"
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
y: -2,
|
||||
boxShadow: '0 4px 24px rgba(0,0,0,0.4), 0 0 20px rgba(25,140,255,0.3)',
|
||||
x: 4,
|
||||
borderColor: '#198CFF50',
|
||||
transition: { duration: 0.15 }
|
||||
}}
|
||||
transition={{ type: 'spring', stiffness: 200, damping: 20 }}
|
||||
>
|
||||
<Box h={{ base: 6, sm: 7, md: 8, lg: 10 }} w={{ base: 6, sm: 7, md: 8, lg: 10 }} bg="bg-charcoal-outline" rounded border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box h={{ base: 6, sm: 7, md: 8, lg: 10 }} w={{ base: 6, sm: 7, md: 8, lg: 10 }} bg="graphite-black" rounded="none" border borderColor="primary-accent/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }}>🏁</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} w={{ base: 20, sm: 24, md: 28, lg: 32 }} bg="bg-white/10" rounded="sm" mb={{ base: 1, sm: 1.5, md: 2 }} />
|
||||
<Box h={{ base: 1, sm: 1.5, md: 2, lg: 2.5 }} w={{ base: 12, sm: 16, md: 20, lg: 24 }} bg="bg-white/5" rounded="sm" font="mono" />
|
||||
<Box h="1.5" w={i === 1 ? "32" : "24"} bg="white/10" rounded="none" mb={{ base: 1, sm: 1.5, md: 2 }} />
|
||||
<Box h="1" w="16" bg="white/5" rounded="none" />
|
||||
</Box>
|
||||
{i === 1 && (
|
||||
<Box
|
||||
@@ -163,19 +142,11 @@ export function LeagueHomeMockup() {
|
||||
position="absolute"
|
||||
right="4"
|
||||
animate={shouldReduceMotion ? {} : {
|
||||
scale: [1, 1.2, 1],
|
||||
boxShadow: [
|
||||
'0 0 20px rgba(25,140,255,0.3)',
|
||||
'0 0 32px rgba(67,201,230,0.4)',
|
||||
'0 0 20px rgba(25,140,255,0.3)'
|
||||
]
|
||||
opacity: [1, 0.4, 1]
|
||||
}}
|
||||
transition={{ duration: 2, repeat: Infinity }}
|
||||
>
|
||||
<Box w={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} bg="bg-primary-blue" rounded="full"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="shadow-glow"
|
||||
/>
|
||||
<Box w="1.5" h="1.5" bg="primary-accent" rounded="full" />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
@@ -184,28 +155,18 @@ export function LeagueHomeMockup() {
|
||||
</Box>
|
||||
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} block>Recent Results</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '8px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
mb={{ base: 1.5, sm: 2, md: 3 }}
|
||||
block
|
||||
>
|
||||
Results appear instantly after each race
|
||||
</Text>
|
||||
<Box bg="bg-iron-gray" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} border borderColor="border-charcoal-outline" shadow="0_4px_24px_rgba(0,0,0,0.4)">
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }} mb={{ base: 1.5, sm: 2, md: 3 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-charcoal-outline">
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 5, sm: 6, md: 8 }} bg="bg-white/10" rounded="sm" font="mono" />
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} flexGrow={1} bg="bg-white/10" rounded="sm" />
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-white/10" rounded="sm" font="mono" />
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-500" mb={{ base: 1.5, sm: 2, md: 3 }} block className="uppercase tracking-[0.2em]">Recent Results</Text>
|
||||
<Box bg="panel-gray/20" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} border borderColor="border-gray/50">
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }} mb={{ base: 1.5, sm: 2, md: 3 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-gray/30">
|
||||
<Box h="1" w="6" bg="white/10" rounded="none" />
|
||||
<Box h="1" flexGrow={1} bg="white/10" rounded="none" />
|
||||
<Box h="1" w="10" bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
{[1, 2].map((i) => (
|
||||
<Box key={i} display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }} py={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 5, sm: 6, md: 8 }} bg="bg-white/5" rounded="sm" font="mono" />
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} flexGrow={1} bg="bg-white/5" rounded="sm" />
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-performance-green/20" rounded="sm" textAlign="center" font="mono" color="text-performance-green" />
|
||||
<Box h="1" w="6" bg="white/5" rounded="none" />
|
||||
<Box h="1" flexGrow={1} bg="white/5" rounded="none" />
|
||||
<Box h="1" w="10" bg={i === 1 ? "success-green/20" : "white/5"} rounded="none" />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
|
||||
return (
|
||||
<div className="relative w-full h-full scale-60 sm:scale-70 md:scale-85 lg:scale-95 max-w-[85vw] mx-auto my-4 sm:my-0" style={{ perspective: '1200px' }}>
|
||||
<div
|
||||
className="absolute rounded-lg bg-iron-gray/80 border border-charcoal-outline"
|
||||
className="absolute rounded-none bg-panel-gray/80 border border-border-gray/50"
|
||||
style={{
|
||||
rotate: `${rotation1}deg`,
|
||||
zIndex: 1,
|
||||
@@ -44,7 +44,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute rounded-lg bg-iron-gray/90 border border-charcoal-outline"
|
||||
className="absolute rounded-none bg-panel-gray/90 border border-border-gray/50"
|
||||
style={{
|
||||
rotate: `${rotation2}deg`,
|
||||
zIndex: 2,
|
||||
@@ -58,7 +58,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
|
||||
/>
|
||||
|
||||
<div
|
||||
className="relative z-10 w-full h-full rounded-lg overflow-hidden"
|
||||
className="relative z-10 w-full h-full rounded-none overflow-hidden border border-border-gray/30"
|
||||
style={{
|
||||
boxShadow: '0 20px 60px rgba(0,0,0,0.45)',
|
||||
}}
|
||||
@@ -73,7 +73,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
|
||||
return (
|
||||
<div className="relative w-full h-full scale-60 sm:scale-70 md:scale-85 lg:scale-95 max-w-[85vw] mx-auto my-4 sm:my-0" style={{ perspective: '1200px' }}>
|
||||
<motion.div
|
||||
className="absolute rounded-lg bg-iron-gray/80 border border-charcoal-outline"
|
||||
className="absolute rounded-none bg-panel-gray/80 border border-border-gray/50"
|
||||
style={{
|
||||
rotate: `${rotation1}deg`,
|
||||
zIndex: 1,
|
||||
@@ -89,7 +89,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
className="absolute rounded-lg bg-iron-gray/90 border border-charcoal-outline"
|
||||
className="absolute rounded-none bg-panel-gray/90 border border-border-gray/50"
|
||||
style={{
|
||||
rotate: `${rotation2}deg`,
|
||||
zIndex: 2,
|
||||
@@ -105,7 +105,7 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
className="relative z-10 w-full h-full rounded-lg overflow-hidden"
|
||||
className="relative z-10 w-full h-full rounded-none overflow-hidden border border-border-gray/30"
|
||||
style={{
|
||||
boxShadow: '0 20px 60px rgba(0,0,0,0.45)',
|
||||
}}
|
||||
@@ -129,12 +129,12 @@ export function MockupStack({ children, index = 0 }: MockupStackProps) {
|
||||
transition={{ duration: 0.4, delay: 0.2 }}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute inset-0 pointer-events-none rounded-lg"
|
||||
className="absolute inset-0 pointer-events-none rounded-none"
|
||||
whileHover={
|
||||
shouldReduceMotion
|
||||
? {}
|
||||
: {
|
||||
boxShadow: '0 0 40px rgba(25, 140, 255, 0.4)',
|
||||
boxShadow: '0 0 40px rgba(25, 140, 255, 0.2)',
|
||||
transition: { duration: 0.2 },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,35 +21,32 @@ export function ProtestWorkflowMockup() {
|
||||
{
|
||||
name: 'Submit',
|
||||
status: 'pending',
|
||||
color: 'charcoal-outline',
|
||||
icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z'
|
||||
},
|
||||
{
|
||||
name: 'Review',
|
||||
status: 'active',
|
||||
color: 'warning-amber',
|
||||
icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4'
|
||||
},
|
||||
{
|
||||
name: 'Resolve',
|
||||
status: 'resolved',
|
||||
color: 'performance-green',
|
||||
icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'
|
||||
},
|
||||
];
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const getStatusStyles = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pending': return 'bg-charcoal-outline border-charcoal-outline text-gray-500';
|
||||
case 'active': return 'bg-warning-amber/20 border-warning-amber text-warning-amber';
|
||||
case 'resolved': return 'bg-performance-green/20 border-performance-green text-performance-green';
|
||||
default: return 'bg-charcoal-outline border-charcoal-outline text-gray-500';
|
||||
case 'pending': return 'bg-panel-gray border-gray-700 text-gray-600';
|
||||
case 'active': return 'bg-warning-amber/10 border-warning-amber text-warning-amber';
|
||||
case 'resolved': return 'bg-success-green/10 border-success-green text-success-green';
|
||||
default: return 'bg-panel-gray border-gray-700 text-gray-600';
|
||||
}
|
||||
};
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" overflow="hidden" p={3} display="flex" flexDirection="col" justifyContent="center" gap={4}>
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" overflow="hidden" p={3} display="flex" flexDirection="col" justifyContent="center" gap={4}>
|
||||
<Box display="flex" alignItems="center" justifyContent="center" gap={3}>
|
||||
{steps.map((step, i) => (
|
||||
<Box key={step.name} display="flex" alignItems="center">
|
||||
@@ -57,41 +54,36 @@ export function ProtestWorkflowMockup() {
|
||||
<Box
|
||||
w="10"
|
||||
h="10"
|
||||
rounded="lg"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
mb={1}
|
||||
border
|
||||
borderWidth="2px"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={getStatusColor(step.status)}
|
||||
borderWidth="1px"
|
||||
className={getStatusStyles(step.status)}
|
||||
>
|
||||
<Box as="svg" w="5" h="5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={step.icon} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Text size="xs" color="text-white" opacity={0.7} textAlign="center">{step.name}</Text>
|
||||
<Text size="xs" color="text-white" weight="bold" textAlign="center" className="uppercase tracking-widest">{step.name}</Text>
|
||||
</Box>
|
||||
{i < steps.length - 1 && (
|
||||
<Box as="svg" w="4" h="4" mx={1} viewBox="0 0 24 24" fill="none">
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#43C9E6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#198CFF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Box position="relative" h="1" bg="bg-charcoal-outline" rounded="full" overflow="hidden">
|
||||
<Box position="relative" h="1" bg="white/5" rounded="none" overflow="hidden">
|
||||
<Box
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
bg="bg-gradient-to-r from-neon-aqua to-primary-blue"
|
||||
rounded="full"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
bg="primary-accent"
|
||||
style={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
|
||||
/>
|
||||
</Box>
|
||||
@@ -100,21 +92,19 @@ export function ProtestWorkflowMockup() {
|
||||
}
|
||||
|
||||
const stepVariants = {
|
||||
hidden: { opacity: 0, scale: shouldReduceMotion ? 1 : 0.8 },
|
||||
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
|
||||
visible: (i: number) => ({
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: shouldReduceMotion ? 0 : i * 0.2,
|
||||
type: 'spring' as const,
|
||||
stiffness: 200,
|
||||
damping: 20
|
||||
duration: 0.4
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" overflow="hidden" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} display="flex" flexDirection="col" justifyContent="center" gap={{ base: 2, sm: 4, md: 6, lg: 8 }}>
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" overflow="hidden" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} display="flex" flexDirection="col" justifyContent="center" gap={{ base: 2, sm: 4, md: 6, lg: 8 }}>
|
||||
<Box display="flex" flexDirection={{ base: 'col', md: 'row' }} alignItems="center" justifyContent="center" gap={{ base: 2, sm: 3, md: 4 }}>
|
||||
{steps.map((step, i) => (
|
||||
<Box key={step.name} display="flex" alignItems="center" flexShrink={0}>
|
||||
@@ -134,84 +124,48 @@ export function ProtestWorkflowMockup() {
|
||||
position="relative"
|
||||
w={{ base: 8, sm: 10, md: 12, lg: 14 }}
|
||||
h={{ base: 8, sm: 10, md: 12, lg: 14 }}
|
||||
rounded="lg"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
mb={{ base: 1, sm: 1.5, md: 2 }}
|
||||
border
|
||||
borderWidth="2px"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={getStatusColor(step.status)}
|
||||
borderWidth="1px"
|
||||
className={getStatusStyles(step.status)}
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
scale: 1.1,
|
||||
boxShadow: step.status === 'active'
|
||||
? '0 0 32px rgba(255,197,86,0.4)'
|
||||
: step.status === 'resolved'
|
||||
? '0 0 32px rgba(111,227,122,0.4)'
|
||||
: '0 0 20px rgba(34,38,42,0.4)',
|
||||
scale: 1.05,
|
||||
borderColor: '#198CFF',
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 15 }}
|
||||
>
|
||||
<Box as="svg" w={{ base: 4, sm: 5, md: 6, lg: 7 }} h={{ base: 4, sm: 5, md: 6, lg: 7 }} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={step.icon} />
|
||||
</Box>
|
||||
{step.status === 'active' && (
|
||||
<Box
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
inset="0"
|
||||
rounded="lg"
|
||||
animate={shouldReduceMotion ? {} : {
|
||||
boxShadow: [
|
||||
'0 0 20px rgba(25,140,255,0.3)',
|
||||
'0 0 32px rgba(255,197,86,0.5)',
|
||||
'0 0 20px rgba(25,140,255,0.3)'
|
||||
]
|
||||
}}
|
||||
transition={{ duration: 2, repeat: Infinity }}
|
||||
/>
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="warning-amber" />
|
||||
)}
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '8px' }}
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.7}
|
||||
weight="bold"
|
||||
textAlign="center"
|
||||
mb={0.5}
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
{step.name}
|
||||
</Text>
|
||||
<Box
|
||||
as={motion.div}
|
||||
h={{ base: 0.5, sm: 1, md: 1.5 }}
|
||||
w={{ base: 6, sm: 8, md: 10, lg: 12 }}
|
||||
rounded="sm"
|
||||
bg={
|
||||
step.status === 'pending' ? 'bg-charcoal-outline' :
|
||||
step.status === 'active' ? 'bg-warning-amber/30' :
|
||||
'bg-performance-green/30'
|
||||
}
|
||||
animate={shouldReduceMotion ? {} : step.status === 'active' ? {
|
||||
opacity: [0.5, 1, 0.5]
|
||||
} : {}}
|
||||
transition={{ duration: 2, repeat: Infinity }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{i < steps.length - 1 && (
|
||||
<Box
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="hidden md:block"
|
||||
position="relative"
|
||||
ml={1}
|
||||
ml={2}
|
||||
mr={2}
|
||||
>
|
||||
<Box as="svg" w={{ base: 3, sm: 4, md: 5 }} h={{ base: 3, sm: 4, md: 5 }} viewBox="0 0 24 24" fill="none">
|
||||
{/* eslint-disable-next-line gridpilot-rules/component-classification */}
|
||||
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#43C9E6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M5 12h14m-7-7l7 7-7 7" stroke="#198CFF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" opacity={0.5} />
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
@@ -225,9 +179,9 @@ export function ProtestWorkflowMockup() {
|
||||
animate={{ opacity: 1, scaleX: 1 }}
|
||||
transition={{ delay: shouldReduceMotion ? 0 : 0.8, duration: 0.6 }}
|
||||
position="relative"
|
||||
h={{ base: 0.5, md: 1 }}
|
||||
bg="bg-charcoal-outline"
|
||||
rounded="full"
|
||||
h="1"
|
||||
bg="white/5"
|
||||
rounded="none"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
@@ -235,8 +189,7 @@ export function ProtestWorkflowMockup() {
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
bg="bg-gradient-to-r from-neon-aqua to-primary-blue"
|
||||
rounded="full"
|
||||
bg="primary-accent"
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
|
||||
transition={{ duration: 0.5, ease: 'easeOut' }}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function RaceHistoryMockup() {
|
||||
// Simple, elegant mobile version - just the core story
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial="hidden"
|
||||
@@ -40,14 +40,14 @@ export function RaceHistoryMockup() {
|
||||
<Stack gap={4}>
|
||||
{/* Race result - clean and simple */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Box bg="bg-iron-gray/60" rounded="xl" p={4} border borderWidth="2px" borderColor="border-primary-blue/40">
|
||||
<Box bg="panel-gray/60" rounded="none" p={4} border borderWidth="1px" borderColor="primary-accent/40">
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box h="14" w="14" bg="bg-primary-blue/20" rounded="lg" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue">P3</Text>
|
||||
<Box h="14" w="14" bg="primary-accent/10" rounded="none" display="flex" alignItems="center" justifyContent="center" flexShrink={0} border borderColor="primary-accent/30">
|
||||
<Text size="2xl" weight="bold" color="text-primary-accent" font="mono">P3</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="base" weight="semibold" color="text-white" block>Watkins Glen</Text>
|
||||
<Text size="xs" color="text-white" opacity={0.6} block>GT3 Sprint</Text>
|
||||
<Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">Watkins Glen</Text>
|
||||
<Text size="xs" color="text-gray-500" block font="mono">GT3 SPRINT</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -55,30 +55,32 @@ export function RaceHistoryMockup() {
|
||||
|
||||
{/* Simple arrow */}
|
||||
<Box as={motion.div} variants={itemVariants} display="flex" justifyContent="center">
|
||||
<Text color="text-primary-blue" size="2xl">↓</Text>
|
||||
<Text color="text-primary-accent" size="2xl">↓</Text>
|
||||
</Box>
|
||||
|
||||
{/* Updates - minimal */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Box bg="bg-iron-gray/40" rounded="xl" p={3} border borderColor="border-charcoal-outline">
|
||||
<Box bg="panel-gray/40" rounded="none" p={3} border borderColor="border-gray/50">
|
||||
<Stack gap={2}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.7}
|
||||
color="text-gray-500"
|
||||
textAlign="center"
|
||||
mb={2}
|
||||
block
|
||||
font="mono"
|
||||
uppercase
|
||||
className="tracking-widest"
|
||||
>
|
||||
Profile Updated
|
||||
</Text>
|
||||
<Box display="flex" gap={2}>
|
||||
<Box flexGrow={1} bg="bg-deep-graphite/50" rounded="sm" py={2} textAlign="center" border borderColor="border-primary-blue/30">
|
||||
<Text size="xs" color="text-primary-blue" weight="semibold" block>Stats ↑</Text>
|
||||
<Box flexGrow={1} bg="graphite-black" rounded="none" py={2} textAlign="center" border borderColor="primary-accent/30">
|
||||
<Text size="xs" color="text-primary-accent" weight="bold" block font="mono">STATS ↑</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} bg="bg-deep-graphite/50" rounded="sm" py={2} textAlign="center" border borderColor="border-performance-green/30">
|
||||
<Text size="xs" color="text-performance-green" weight="semibold" block>+12</Text>
|
||||
<Box flexGrow={1} bg="graphite-black" rounded="none" py={2} textAlign="center" border borderColor="success-green/30">
|
||||
<Text size="xs" color="text-success-green" weight="bold" block font="mono">+12</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -92,7 +94,7 @@ export function RaceHistoryMockup() {
|
||||
|
||||
// Desktop version - richer with more updates
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 5, lg: 8 }} overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial="hidden"
|
||||
@@ -104,60 +106,43 @@ export function RaceHistoryMockup() {
|
||||
<Stack gap={{ base: 2, sm: 3, md: 4, lg: 5 }}>
|
||||
{/* Race Result Card - Enhanced */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Box bg="bg-iron-gray" rounded="lg" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderWidth="2px" borderColor="border-primary-blue/40">
|
||||
<Box bg="panel-gray/40" rounded="none" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderWidth="1px" borderColor="primary-accent/40">
|
||||
<Box display="flex" alignItems="center" gap={{ base: 2, sm: 3, md: 4 }}>
|
||||
<Box position="relative" h={{ base: 12, sm: 14, md: 16, lg: 20 }} w={{ base: 12, sm: 14, md: 16, lg: 20 }} bg="bg-charcoal-outline" rounded="lg" border borderWidth="2px" borderColor="border-primary-blue/30" overflow="hidden" flexShrink={0}>
|
||||
<Box position="absolute" inset="0" bg="bg-gradient-to-br from-primary-blue/20 to-performance-green/20" />
|
||||
<Box position="relative" h={{ base: 12, sm: 14, md: 16, lg: 20 }} w={{ base: 12, sm: 14, md: 16, lg: 20 }} bg="graphite-black" rounded="none" border borderWidth="1px" borderColor="primary-accent/30" overflow="hidden" flexShrink={0}>
|
||||
<Box position="absolute" inset="0" bg="gradient-to-br from-primary-accent/10 to-success-green/10" />
|
||||
<Box position="absolute" inset="0" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text color="text-white" size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold">P3</Text>
|
||||
<Text color="text-white" size={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} weight="bold" font="mono">P3</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flexGrow={1} minWidth="0">
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }} mb={1}>
|
||||
<Text size={{ base: 'sm', sm: 'base', md: 'lg' }}>🏁</Text>
|
||||
<Heading level={3} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="semibold" color="text-white" truncate>Watkins Glen</Heading>
|
||||
<Box w="1" h="4" bg="primary-accent" />
|
||||
<Heading level={3} fontSize={{ base: 'sm', sm: 'base', md: 'lg', lg: 'xl' }} weight="bold" color="text-white" truncate className="uppercase tracking-widest">Watkins Glen</Heading>
|
||||
</Box>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} color="text-white" opacity={0.6} mb={1} block>GT3 Sprint Race</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} color="text-gray-500" mb={1} block font="mono">GT3 SPRINT RACE</Text>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 2, sm: 3, md: 4 }}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
color="text-gray-600"
|
||||
font="mono"
|
||||
>
|
||||
24 drivers
|
||||
24 DRIVERS
|
||||
</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
color="text-gray-600"
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
color="text-gray-600"
|
||||
font="mono"
|
||||
>
|
||||
45 min
|
||||
</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
>
|
||||
Just finished
|
||||
45 MIN
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -178,12 +163,13 @@ export function RaceHistoryMockup() {
|
||||
}}
|
||||
transition={{ duration: 2, repeat: Infinity }}
|
||||
>
|
||||
<Text color="text-primary-blue" size={{ base: '2xl', sm: '3xl', md: '4xl' }}>↓</Text>
|
||||
<Text color="text-primary-accent" size={{ base: '2xl', sm: '3xl', md: '4xl' }}>↓</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-primary-blue"
|
||||
opacity={0.7}
|
||||
color="text-primary-accent"
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Auto-sync
|
||||
</Text>
|
||||
@@ -192,27 +178,28 @@ export function RaceHistoryMockup() {
|
||||
|
||||
{/* Profile Updates Grid - More detailed */}
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Box bg="bg-iron-gray/80" rounded="lg" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderColor="border-charcoal-outline">
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" opacity={0.8} textAlign="center" mb={{ base: 2, sm: 3, md: 4 }} block>
|
||||
<Box bg="panel-gray/20" rounded="none" p={{ base: 2, sm: 3, md: 4, lg: 5 }} border borderColor="border-gray/50">
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-gray-500" textAlign="center" mb={{ base: 2, sm: 3, md: 4 }} block className="uppercase tracking-[0.2em]">
|
||||
Profile Updates
|
||||
</Text>
|
||||
|
||||
<Box display="grid" gridCols={2} gap={{ base: 2, sm: 3, md: 4 }}>
|
||||
{/* Career Stats Update */}
|
||||
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-primary-blue/30">
|
||||
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="primary-accent/30">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.7}
|
||||
color="text-gray-500"
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Career Stats
|
||||
</Text>
|
||||
<Box
|
||||
as={motion.span}
|
||||
color="text-performance-green"
|
||||
weight="semibold"
|
||||
color="text-success-green"
|
||||
weight="bold"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
animate={shouldReduceMotion ? {} : { scale: [1, 1.2, 1] }}
|
||||
@@ -227,26 +214,28 @@ export function RaceHistoryMockup() {
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Wins: 24 → 25
|
||||
WINS: 24 → 25
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Rating Update */}
|
||||
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-performance-green/30">
|
||||
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="success-green/30">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.7}
|
||||
color="text-gray-500"
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Rating
|
||||
</Text>
|
||||
<Box
|
||||
as={motion.span}
|
||||
color="text-performance-green"
|
||||
weight="semibold"
|
||||
color="text-success-green"
|
||||
weight="bold"
|
||||
font="mono"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
@@ -262,26 +251,28 @@ export function RaceHistoryMockup() {
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
1342 → 1354
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Season Points Update */}
|
||||
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-warning-amber/30">
|
||||
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="warning-amber/30">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.7}
|
||||
color="text-gray-500"
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Season
|
||||
</Text>
|
||||
<Box
|
||||
as={motion.span}
|
||||
color="text-warning-amber"
|
||||
weight="semibold"
|
||||
weight="bold"
|
||||
font="mono"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
@@ -297,26 +288,28 @@ export function RaceHistoryMockup() {
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
248 → 266 pts
|
||||
248 → 266 PTS
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Team Points Update */}
|
||||
<Box bg="bg-deep-graphite/50" rounded="lg" p={{ base: 2, sm: 3, md: 4 }} border borderColor="border-neon-aqua/30">
|
||||
<Box bg="graphite-black" rounded="none" p={{ base: 2, sm: 3, md: 4 }} border borderColor="telemetry-aqua/30">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.7}
|
||||
color="text-gray-500"
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Team
|
||||
</Text>
|
||||
<Box
|
||||
as={motion.span}
|
||||
color="text-neon-aqua"
|
||||
weight="semibold"
|
||||
color="text-telemetry-aqua"
|
||||
weight="bold"
|
||||
font="mono"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
@@ -332,8 +325,9 @@ export function RaceHistoryMockup() {
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Contributing
|
||||
CONTRIBUTING
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -18,30 +18,30 @@ export function SimPlatformMockup() {
|
||||
// Simple mobile version - just the essence of cross-platform
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite to-iron-gray" rounded="lg" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={4} overflow="hidden" display="flex" alignItems="center" justifyContent="center">
|
||||
<Stack gap={3} w="full">
|
||||
{/* Active Platform - Clean */}
|
||||
<Box bg="bg-iron-gray/60" border borderWidth="2px" borderColor="border-primary-blue" rounded="xl" p={4} position="relative">
|
||||
<Box bg="panel-gray/60" border borderWidth="1px" borderColor="primary-accent" rounded="none" p={4} position="relative">
|
||||
<Box position="absolute" top="3" right="3">
|
||||
<Box w="2" h="2" rounded="full" bg="bg-performance-green"
|
||||
<Box w="1.5" h="1.5" rounded="full" bg="success-green"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="animate-pulse"
|
||||
/>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box w="12" h="12" rounded="lg" bg="bg-primary-blue/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue">iR</Text>
|
||||
<Box w="12" h="12" rounded="none" bg="primary-accent/10" display="flex" alignItems="center" justifyContent="center" flexShrink={0} border borderColor="primary-accent/30">
|
||||
<Text size="2xl" weight="bold" color="text-primary-accent" font="mono">iR</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="base" weight="semibold" color="text-white" block>iRacing</Text>
|
||||
<Text size="xs" color="text-performance-green" block>Active</Text>
|
||||
<Text size="base" weight="bold" color="text-white" block className="uppercase tracking-widest">iRacing</Text>
|
||||
<Text size="xs" color="text-success-green" block font="mono">ACTIVE</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Simple "more coming" indicator */}
|
||||
<Box bg="bg-iron-gray/30" rounded="xl" p={3} border borderColor="border-charcoal-outline/50">
|
||||
<Text size="xs" textAlign="center" color="text-slate-500" block>More platforms coming</Text>
|
||||
<Box bg="panel-gray/20" rounded="none" p={3} border borderStyle="dashed" borderColor="border-gray/30">
|
||||
<Text size="xs" textAlign="center" color="text-gray-600" block font="mono" uppercase tracking-widest>More platforms coming</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
@@ -51,150 +51,161 @@ export function SimPlatformMockup() {
|
||||
// Desktop version
|
||||
return (
|
||||
<Box position="relative" fullWidth maxWidth="3xl" mx="auto">
|
||||
<Box bg="bg-iron-gray" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} shadow="2xl">
|
||||
<Box bg="panel-gray/40" border borderColor="border-gray/50" rounded="none" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} shadow="2xl">
|
||||
<Stack gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" pb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderBottom borderColor="border-charcoal-outline">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-300">Platform Support</Text>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" pb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderBottom borderColor="border-gray/30">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-400" className="uppercase tracking-widest">Platform Support</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-500"
|
||||
color="text-gray-600"
|
||||
font="mono"
|
||||
>
|
||||
Active: 1 | Planned: 3
|
||||
ACTIVE: 1 | PLANNED: 3
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
{/* iRacing - Active */}
|
||||
<Box bg="bg-deep-graphite" border borderWidth="2px" borderColor="border-primary-blue" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} position="relative" overflow="hidden">
|
||||
<Box bg="graphite-black" border borderWidth="1px" borderColor="primary-accent/40" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} position="relative" overflow="hidden">
|
||||
<Box position="absolute" top={{ base: 1, sm: 1.5, md: 2 }} right={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Box w={{ base: 1, sm: 1.5, md: 2 }} h={{ base: 1, sm: 1.5, md: 2 }} rounded="full" bg="bg-performance-green"
|
||||
<Box w="1.5" h="1.5" rounded="full" bg="success-green"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="animate-pulse"
|
||||
/>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-primary-blue/10" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-primary-blue">iR</Text>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="primary-accent/10" display="flex" alignItems="center" justifyContent="center" border borderColor="primary-accent/20">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-primary-accent" font="mono">iR</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="semibold" color="text-white" block>iRacing</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm', md: 'base' }} weight="bold" color="text-white" block className="uppercase tracking-widest">iRacing</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-performance-green"
|
||||
color="text-success-green"
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Active
|
||||
ACTIVE
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-400"
|
||||
color="text-gray-500"
|
||||
mt={{ base: 1.5, sm: 2, md: 3 }}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Full integration
|
||||
FULL INTEGRATION
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* ACC - Future */}
|
||||
<Box bg="bg-deep-graphite" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.4}>
|
||||
<Box bg="panel-gray/20" border borderColor="border-gray/30" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.6}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-slate-700/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-slate-600">AC</Text>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="gray-800/20" display="flex" alignItems="center" justifyContent="center" border borderColor="gray-800/30">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">AC</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-500" block>ACC</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-600" block className="uppercase tracking-widest">ACC</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-600"
|
||||
color="text-gray-700"
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Planned
|
||||
PLANNED
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-600"
|
||||
color="text-gray-700"
|
||||
mt={{ base: 1.5, sm: 2, md: 3 }}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Coming later
|
||||
COMING LATER
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* rFactor 2 - Future */}
|
||||
<Box bg="bg-deep-graphite" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.4}>
|
||||
<Box bg="panel-gray/20" border borderColor="border-gray/30" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.6}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-slate-700/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-slate-600">rF</Text>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="gray-800/20" display="flex" alignItems="center" justifyContent="center" border borderColor="gray-800/30">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">rF</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-500" block>rFactor 2</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-600" block className="uppercase tracking-widest">rFactor 2</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-600"
|
||||
color="text-gray-700"
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Planned
|
||||
PLANNED
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-600"
|
||||
color="text-gray-700"
|
||||
mt={{ base: 1.5, sm: 2, md: 3 }}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Coming later
|
||||
COMING LATER
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* LMU - Future */}
|
||||
<Box bg="bg-deep-graphite" border borderColor="border-charcoal-outline" rounded="lg" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.4}>
|
||||
<Box bg="panel-gray/20" border borderColor="border-gray/30" rounded="none" p={{ base: 1.5, sm: 2, md: 3, lg: 4 }} opacity={0.6}>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3 }}>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="sm" bg="bg-slate-700/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-slate-600">LM</Text>
|
||||
<Box w={{ base: 8, sm: 10, md: 12 }} h={{ base: 8, sm: 10, md: 12 }} rounded="none" bg="gray-800/20" display="flex" alignItems="center" justifyContent="center" border borderColor="gray-800/30">
|
||||
<Text size={{ base: 'base', sm: 'xl', md: '2xl' }} weight="bold" color="text-gray-700" font="mono">LM</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="semibold" color="text-slate-500" block>Le Mans Ult.</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} weight="bold" color="text-gray-600" block className="uppercase tracking-widest">Le Mans Ult.</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-600"
|
||||
color="text-gray-700"
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Planned
|
||||
PLANNED
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-600"
|
||||
color="text-gray-700"
|
||||
mt={{ base: 1.5, sm: 2, md: 3 }}
|
||||
block
|
||||
font="mono"
|
||||
>
|
||||
Coming later
|
||||
COMING LATER
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box pt={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderTop borderColor="border-charcoal-outline">
|
||||
<Box pt={{ base: 1.5, sm: 2, md: 3, lg: 4 }} borderTop borderStyle="dashed" borderColor="border-gray/30">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-slate-500"
|
||||
color="text-gray-600"
|
||||
textAlign="center"
|
||||
block
|
||||
font="mono"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Your identity stays with you across platforms
|
||||
</Text>
|
||||
|
||||
@@ -17,30 +17,30 @@ export function StandingsTableMockup() {
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
|
||||
<Box mb={3}>
|
||||
<Box display="flex" alignItems="center" gap={2} pb={2} borderBottom borderColor="border-charcoal-outline">
|
||||
<Box display="flex" alignItems="center" gap={2} pb={2} borderBottom borderColor="border-gray/30">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '12px' }}
|
||||
style={{ fontSize: '10px' }}
|
||||
font="mono"
|
||||
color="text-gray-400"
|
||||
color="text-gray-600"
|
||||
>
|
||||
#
|
||||
POS
|
||||
</Text>
|
||||
<Text size="xs" flexGrow={1} weight="semibold" color="text-white">Driver</Text>
|
||||
<Text size="xs" flexGrow={1} weight="bold" color="text-gray-500" className="uppercase tracking-widest">Driver</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '12px' }}
|
||||
style={{ fontSize: '10px' }}
|
||||
font="mono"
|
||||
color="text-gray-400"
|
||||
color="text-gray-600"
|
||||
>
|
||||
Pts
|
||||
PTS
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Stack gap={2}>
|
||||
<Stack gap={1}>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -49,49 +49,41 @@ export function StandingsTableMockup() {
|
||||
gap={2}
|
||||
py={2}
|
||||
px={2}
|
||||
rounded="lg"
|
||||
rounded="none"
|
||||
border
|
||||
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/10 to-iron-gray' : 'bg-iron-gray'}
|
||||
borderColor={i <= 3 ? 'border-performance-green/20' : 'border-charcoal-outline'}
|
||||
bg={i <= 3 ? 'panel-gray/40' : 'transparent'}
|
||||
borderColor={i <= 3 ? 'primary-accent/20' : 'border-gray/20'}
|
||||
>
|
||||
<Box
|
||||
h="7"
|
||||
w="7"
|
||||
rounded="full"
|
||||
h="6"
|
||||
w="6"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
weight="semibold"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '12px' }}
|
||||
bg={i <= 3 ? 'bg-primary-blue' : 'bg-charcoal-outline'}
|
||||
color={i <= 3 ? 'text-white' : 'text-gray-400'}
|
||||
shadow={i <= 3 ? '0_0_12px_rgba(25,140,255,0.3)' : undefined}
|
||||
style={{ fontSize: '10px' }}
|
||||
bg={i <= 3 ? 'primary-accent' : 'panel-gray'}
|
||||
color={i <= 3 ? 'text-white' : 'text-gray-500'}
|
||||
font="mono"
|
||||
weight="bold"
|
||||
>
|
||||
{i}
|
||||
</Box>
|
||||
<Box flexGrow={1} display="flex" alignItems="center" gap={2}>
|
||||
<Box h="5" w="5" rounded="full" bg="bg-charcoal-outline" border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '12px' }}
|
||||
>
|
||||
🏎️
|
||||
</Text>
|
||||
</Box>
|
||||
<Box h="2.5" fullWidth maxWidth="100px" bg="bg-white/10" rounded="sm" />
|
||||
<Box h="1.5" fullWidth maxWidth="80px" bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
<Box position="relative" w="16" h="5" bg="bg-charcoal-outline" rounded="sm" border borderColor="border-primary-blue/20" overflow="hidden">
|
||||
<Box position="relative" w="12" h="4" bg="graphite-black" rounded="none" border borderColor="border-gray/30" overflow="hidden">
|
||||
<Box
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20' : 'bg-gradient-to-r from-iron-gray to-charcoal-outline'}
|
||||
bg={i <= 3 ? 'primary-accent/40' : 'gray-700/40'}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ width: `${100 - (i - 1) * 15}%` }}
|
||||
/>
|
||||
<Box position="relative" h="full" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xs" font="mono" weight="semibold" color="text-white">
|
||||
<Text size="xs" font="mono" weight="bold" color="text-white">
|
||||
{300 - i * 20}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -104,47 +96,48 @@ export function StandingsTableMockup() {
|
||||
}
|
||||
|
||||
const getRowAnimation = (i: number) => ({
|
||||
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
|
||||
hidden: { opacity: 0, x: shouldReduceMotion ? 0 : -10 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
x: 0,
|
||||
transition: {
|
||||
delay: shouldReduceMotion ? 0 : i * 0.05,
|
||||
type: 'spring' as const,
|
||||
stiffness: 300,
|
||||
damping: 24
|
||||
duration: 0.3
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
|
||||
<Box mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
opacity={0.5}
|
||||
color="text-gray-600"
|
||||
mb={{ base: 1.5, sm: 2, md: 3 }}
|
||||
block
|
||||
font="mono"
|
||||
uppercase
|
||||
className="tracking-widest"
|
||||
>
|
||||
Real-time standings updated after every race
|
||||
</Text>
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-charcoal-outline">
|
||||
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-gray/30">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
font="mono"
|
||||
color="text-gray-400"
|
||||
color="text-gray-500"
|
||||
>
|
||||
#
|
||||
POS
|
||||
</Text>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
flexGrow={1}
|
||||
weight="semibold"
|
||||
color="text-white"
|
||||
weight="bold"
|
||||
color="text-gray-400"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Driver
|
||||
</Text>
|
||||
@@ -152,9 +145,8 @@ export function StandingsTableMockup() {
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
font="mono"
|
||||
color="text-gray-400"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="hidden md:block"
|
||||
color="text-gray-500"
|
||||
className="hidden md:block uppercase tracking-widest"
|
||||
>
|
||||
Wins
|
||||
</Text>
|
||||
@@ -163,21 +155,16 @@ export function StandingsTableMockup() {
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
font="mono"
|
||||
color="text-gray-400"
|
||||
color="text-gray-500"
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
Points
|
||||
</Text>
|
||||
<Text color="text-performance-green"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '8px' }}
|
||||
>
|
||||
●
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Stack gap={0.5}>
|
||||
<Stack gap={1}>
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -189,18 +176,18 @@ export function StandingsTableMockup() {
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
|
||||
py={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }}
|
||||
py={{ base: 1.5, sm: 2, md: 2.5 }}
|
||||
px={{ base: 1.5, sm: 2, md: 3 }}
|
||||
rounded="lg"
|
||||
rounded="none"
|
||||
border
|
||||
transition
|
||||
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/10 to-iron-gray' : 'bg-iron-gray'}
|
||||
borderColor={i <= 3 ? 'border-performance-green/20' : 'border-charcoal-outline'}
|
||||
bg={i <= 3 ? 'panel-gray/40' : 'transparent'}
|
||||
borderColor={i <= 3 ? 'primary-accent/20' : 'border-gray/20'}
|
||||
onHoverStart={() => !shouldReduceMotion && setHoveredRow(i)}
|
||||
onHoverEnd={() => setHoveredRow(null)}
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
scale: 1.01,
|
||||
boxShadow: '0 0 20px rgba(25,140,255,0.3)',
|
||||
x: 4,
|
||||
borderColor: '#198CFF30',
|
||||
transition: { duration: 0.15 }
|
||||
}}
|
||||
>
|
||||
@@ -208,30 +195,23 @@ export function StandingsTableMockup() {
|
||||
as={motion.div}
|
||||
h={{ base: 6, sm: 7 }}
|
||||
w={{ base: 6, sm: 7 }}
|
||||
rounded="full"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
weight="semibold"
|
||||
weight="bold"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '12px' }}
|
||||
bg={i <= 3 ? 'bg-primary-blue' : 'bg-charcoal-outline'}
|
||||
color={i <= 3 ? 'text-white' : 'text-gray-400'}
|
||||
animate={
|
||||
shouldReduceMotion ? {} : i <= 3 && hoveredRow === i
|
||||
? { scale: 1.15, boxShadow: '0 0 28px rgba(25,140,255,0.5)' }
|
||||
: {}
|
||||
}
|
||||
style={{ fontSize: '11px' }}
|
||||
bg={i <= 3 ? 'primary-accent' : 'panel-gray'}
|
||||
color={i <= 3 ? 'text-white' : 'text-gray-500'}
|
||||
font="mono"
|
||||
>
|
||||
{i}
|
||||
</Box>
|
||||
<Box flexGrow={1} display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Box h={{ base: 4, sm: 5, md: 6 }} w={{ base: 4, sm: 5, md: 6 }} rounded="full" bg="bg-charcoal-outline" border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size={{ base: 'xs', sm: 'sm' }}>🏎️</Text>
|
||||
</Box>
|
||||
<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 h="1.5" fullWidth maxWidth={{ base: '80px', sm: '100px', md: '140px' }} bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} w={{ base: 10, sm: 12, md: 16 }} bg="bg-white/5" rounded="sm" font="mono"
|
||||
<Box h="1" w={{ base: 10, sm: 12, md: 16 }} bg="white/5" rounded="none"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="hidden md:block"
|
||||
/>
|
||||
@@ -272,13 +252,13 @@ function AnimatedPoints({
|
||||
const percentage = (points / 300) * 100;
|
||||
|
||||
return (
|
||||
<Box position="relative" w={{ base: 12, sm: 16, md: 20, lg: 24 }} h={{ base: 4, sm: 5, md: 6, lg: 7 }} bg="bg-charcoal-outline" rounded="sm" border borderColor="border-primary-blue/20" overflow="hidden">
|
||||
<Box position="relative" w={{ base: 12, sm: 16, md: 20, lg: 24 }} h={{ base: 4, sm: 5, md: 6 }} bg="graphite-black" rounded="none" border borderColor="border-gray/30" overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
bg={position <= 3 ? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20' : 'bg-gradient-to-r from-iron-gray to-charcoal-outline'}
|
||||
bg={position <= 3 ? 'primary-accent/40' : 'gray-700/40'}
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: `${percentage}%` }}
|
||||
transition={{ duration: shouldReduceMotion ? 0 : 0.8, ease: 'easeOut', delay: 0.1 + position * 0.05 }}
|
||||
@@ -288,7 +268,7 @@ function AnimatedPoints({
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
font="mono"
|
||||
weight="semibold"
|
||||
weight="bold"
|
||||
color="text-white"
|
||||
>
|
||||
{shouldReduceMotion ? points : <Box as={motion.span}>{spring}</Box>}
|
||||
|
||||
@@ -16,15 +16,15 @@ export function TeamCompetitionMockup() {
|
||||
setIsMobile(window.innerWidth < 768);
|
||||
}, []);
|
||||
|
||||
const teamColors = ['#198CFF', '#6FE37A', '#FFC556', '#43C9E6', '#9333EA'];
|
||||
const teamColors = ['#198CFF', '#6FE37A', '#FFBE4D', '#4ED4E0', '#9333EA'];
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={3} overflow="hidden">
|
||||
<Stack gap={4}>
|
||||
<Box>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Drivers</Text>
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Drivers</Text>
|
||||
<Stack gap={1}>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -32,11 +32,11 @@ export function TeamCompetitionMockup() {
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
bg="bg-iron-gray"
|
||||
rounded="lg"
|
||||
bg="panel-gray/40"
|
||||
rounded="none"
|
||||
p={2}
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/20"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
@@ -44,54 +44,40 @@ export function TeamCompetitionMockup() {
|
||||
left="0"
|
||||
top="0"
|
||||
bottom="0"
|
||||
w="1"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
w="0.5"
|
||||
style={{ backgroundColor: teamColors[i-1] }}
|
||||
/>
|
||||
<Box
|
||||
h="5"
|
||||
w="5"
|
||||
rounded="full"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
weight="semibold"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
weight="bold"
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
borderColor: teamColors[i-1],
|
||||
backgroundColor: `${teamColors[i-1]}20`,
|
||||
borderWidth: '2px'
|
||||
backgroundColor: `${teamColors[i-1]}10`,
|
||||
borderWidth: '1px'
|
||||
}}
|
||||
>
|
||||
<Text color="text-white">{i}</Text>
|
||||
</Box>
|
||||
<Box h="6" w="6" rounded="full" display="flex" alignItems="center" justifyContent="center"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ backgroundColor: `${teamColors[i-1]}20`, borderWidth: '1px', borderColor: teamColors[i-1], fontSize: '14px' }}
|
||||
>
|
||||
<Text>🏎️</Text>
|
||||
<Text color="text-white" font="mono">{i}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Box h="2.5" w="full" bg="bg-white/10" rounded="sm" />
|
||||
<Box h="1.5" w="full" bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
<Box h="3" w="10" bg="bg-charcoal-outline" rounded="sm" display="flex" alignItems="center" justifyContent="center" color="text-white" opacity={0.7}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
/>
|
||||
<Box h="3" w="10" bg="graphite-black" rounded="none" border borderColor="border-gray/30" />
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Box h="px"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="bg-gradient-to-r from-transparent via-charcoal-outline to-transparent"
|
||||
/>
|
||||
<Box h="px" bg="border-gray/30" />
|
||||
|
||||
<Box>
|
||||
<Text size="sm" weight="semibold" color="text-white" mb={3} block>Constructors</Text>
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" mb={3} block className="uppercase tracking-widest">Constructors</Text>
|
||||
<Stack gap={1}>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -99,11 +85,11 @@ export function TeamCompetitionMockup() {
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
bg="bg-iron-gray"
|
||||
rounded="lg"
|
||||
bg="panel-gray/40"
|
||||
rounded="none"
|
||||
p={2}
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/20"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
@@ -111,37 +97,16 @@ export function TeamCompetitionMockup() {
|
||||
left="0"
|
||||
top="0"
|
||||
bottom="0"
|
||||
w="1"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
w="0.5"
|
||||
style={{ backgroundColor: teamColors[i-1] }}
|
||||
/>
|
||||
<Box
|
||||
h="6"
|
||||
w="6"
|
||||
rounded="sm"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
border
|
||||
borderWidth="2px"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{
|
||||
borderColor: teamColors[i-1],
|
||||
backgroundColor: `${teamColors[i-1]}20`,
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
<Text>🏁</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Box h="2.5" w="full" bg="bg-white/10" rounded="sm" mb={1} />
|
||||
<Box position="relative" h="1.5" bg="bg-charcoal-outline" rounded="full" overflow="hidden">
|
||||
<Box h="1.5" w="full" bg="white/10" rounded="none" mb={1.5} />
|
||||
<Box position="relative" h="1" bg="graphite-black" rounded="none" overflow="hidden">
|
||||
<Box
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
rounded="full"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ backgroundColor: teamColors[i-1], width: `${100 - (i-1) * 15}%` }}
|
||||
/>
|
||||
</Box>
|
||||
@@ -160,11 +125,7 @@ export function TeamCompetitionMockup() {
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
type: 'spring' as const,
|
||||
stiffness: 100,
|
||||
damping: 20
|
||||
}
|
||||
transition: { duration: 0.4 }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -173,30 +134,24 @@ export function TeamCompetitionMockup() {
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
type: 'spring' as const,
|
||||
stiffness: 100,
|
||||
damping: 20
|
||||
}
|
||||
transition: { duration: 0.4 }
|
||||
}
|
||||
};
|
||||
|
||||
const rowVariants = {
|
||||
hidden: { opacity: 0, scale: shouldReduceMotion ? 1 : 0.95 },
|
||||
hidden: { opacity: 0, x: shouldReduceMotion ? 0 : -10 },
|
||||
visible: (i: number) => ({
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
delay: shouldReduceMotion ? 0 : 0.3 + i * 0.05,
|
||||
type: 'spring' as const,
|
||||
stiffness: 300,
|
||||
damping: 25
|
||||
duration: 0.3
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
|
||||
<Box position="relative" fullWidth fullHeight bg="graphite-black" rounded="none" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
|
||||
<Box display="grid" gridCols={2} gap={{ base: 2, sm: 3, md: 4, lg: 6 }} fullHeight>
|
||||
<Box
|
||||
as={motion.div}
|
||||
@@ -205,17 +160,10 @@ export function TeamCompetitionMockup() {
|
||||
animate="visible"
|
||||
position="relative"
|
||||
>
|
||||
<Box h={{ base: 3, sm: 4, md: 5 }} w={{ base: 16, sm: 20, md: 24 }} bg="bg-white/10" rounded="sm" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} display="flex" alignItems="center" justifyContent="center">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
weight="semibold"
|
||||
>
|
||||
Drivers
|
||||
</Text>
|
||||
<Box h="6" w="24" bg="panel-gray" rounded="none" mb={4} display="flex" alignItems="center" justifyContent="center" border borderColor="border-gray/30">
|
||||
<Text size="xs" color="text-gray-400" weight="bold" className="uppercase tracking-widest">DRIVERS</Text>
|
||||
</Box>
|
||||
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Stack gap={1}>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -227,18 +175,18 @@ export function TeamCompetitionMockup() {
|
||||
position="relative"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }}
|
||||
bg="bg-iron-gray"
|
||||
rounded="lg"
|
||||
p={{ base: 1, sm: 1.5, md: 2, lg: 2.5 }}
|
||||
gap={3}
|
||||
bg="panel-gray/20"
|
||||
rounded="none"
|
||||
p={2}
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/20"
|
||||
overflow="hidden"
|
||||
onHoverStart={() => !shouldReduceMotion && setHoveredDriver(i)}
|
||||
onHoverEnd={() => setHoveredDriver(null)}
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
scale: 1.02,
|
||||
boxShadow: `0 0 20px ${teamColors[i-1]}40`,
|
||||
x: 4,
|
||||
borderColor: `${teamColors[i-1]}40`,
|
||||
transition: { duration: 0.15 }
|
||||
}}
|
||||
>
|
||||
@@ -248,65 +196,35 @@ export function TeamCompetitionMockup() {
|
||||
top="0"
|
||||
bottom="0"
|
||||
w="0.5"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ backgroundColor: teamColors[i-1] }}
|
||||
/>
|
||||
<Box
|
||||
h={{ base: 3.5, sm: 4, md: 5 }}
|
||||
w={{ base: 3.5, sm: 4, md: 5 }}
|
||||
rounded="full"
|
||||
h="5"
|
||||
w="5"
|
||||
rounded="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
weight="semibold"
|
||||
weight="bold"
|
||||
border
|
||||
borderWidth="2px"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
borderWidth="1px"
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
borderColor: teamColors[i-1],
|
||||
backgroundColor: `${teamColors[i-1]}20`
|
||||
backgroundColor: `${teamColors[i-1]}10`
|
||||
}}
|
||||
>
|
||||
<Text color="text-white">{i}</Text>
|
||||
</Box>
|
||||
<Box h={{ base: 5, sm: 6, md: 7 }} w={{ base: 5, sm: 6, md: 7 }} rounded="full" display="flex" alignItems="center" justifyContent="center"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ backgroundColor: `${teamColors[i-1]}20`, borderWidth: '1px', borderColor: teamColors[i-1], fontSize: '12px' }}
|
||||
>
|
||||
<Text>🏎️</Text>
|
||||
<Text color="text-white" font="mono">{i}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} minWidth="0">
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w="full" bg="bg-white/10" rounded="sm" />
|
||||
<Box h="1.5" w="full" bg="white/10" rounded="none" />
|
||||
</Box>
|
||||
<Box h={{ base: 2, sm: 2.5, md: 3 }} w={{ base: 8, sm: 10, md: 12 }} bg="bg-charcoal-outline" rounded="sm" font="mono" display="flex" alignItems="center" justifyContent="center" color="text-white" opacity={0.7}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
/>
|
||||
{hoveredDriver === i && (
|
||||
<Box
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
inset="0"
|
||||
pointerEvents="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{
|
||||
background: `linear-gradient(90deg, ${teamColors[i-1]}10 0%, transparent 100%)`
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box h="3" w="12" bg="graphite-black" rounded="none" border borderColor="border-gray/30" />
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Box position="absolute" left="1/2" top="8" bottom="8" w="px"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="bg-gradient-to-b from-transparent via-charcoal-outline to-transparent backdrop-blur-sm"
|
||||
/>
|
||||
|
||||
<Box
|
||||
as={motion.div}
|
||||
variants={rightColumnVariants}
|
||||
@@ -314,17 +232,10 @@ export function TeamCompetitionMockup() {
|
||||
animate="visible"
|
||||
position="relative"
|
||||
>
|
||||
<Box h={{ base: 3, sm: 4, md: 5 }} w={{ base: 20, sm: 24, md: 32 }} bg="bg-white/10" rounded="sm" mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }} display="flex" alignItems="center" justifyContent="center">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-white"
|
||||
weight="semibold"
|
||||
>
|
||||
Constructors
|
||||
</Text>
|
||||
<Box h="6" w="32" bg="panel-gray" rounded="none" mb={4} display="flex" alignItems="center" justifyContent="center" border borderColor="border-gray/30">
|
||||
<Text size="xs" color="text-gray-400" weight="bold" className="uppercase tracking-widest">CONSTRUCTORS</Text>
|
||||
</Box>
|
||||
<Stack gap={{ base: 1, sm: 1.5, md: 2 }}>
|
||||
<Stack gap={1}>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -336,58 +247,37 @@ export function TeamCompetitionMockup() {
|
||||
position="relative"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }}
|
||||
bg="bg-iron-gray"
|
||||
rounded="lg"
|
||||
p={{ base: 1, sm: 1.5, md: 2, lg: 2.5 }}
|
||||
gap={3}
|
||||
bg="panel-gray/20"
|
||||
rounded="none"
|
||||
p={2}
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/20"
|
||||
overflow="hidden"
|
||||
onHoverStart={() => !shouldReduceMotion && setHoveredTeam(i)}
|
||||
onHoverEnd={() => setHoveredTeam(null)}
|
||||
whileHover={shouldReduceMotion ? {} : {
|
||||
scale: 1.02,
|
||||
boxShadow: `0 0 20px ${teamColors[i-1]}40`,
|
||||
x: -4,
|
||||
borderColor: `${teamColors[i-1]}40`,
|
||||
transition: { duration: 0.15 }
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
left="0"
|
||||
right="0"
|
||||
top="0"
|
||||
bottom="0"
|
||||
w="0.5"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ backgroundColor: teamColors[i-1] }}
|
||||
/>
|
||||
<Box
|
||||
h={{ base: 5, sm: 6, md: 7 }}
|
||||
w={{ base: 5, sm: 6, md: 7 }}
|
||||
rounded="sm"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
border
|
||||
borderWidth="2px"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
borderColor: teamColors[i-1],
|
||||
backgroundColor: `${teamColors[i-1]}20`
|
||||
}}
|
||||
>
|
||||
<Text>🏁</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} minWidth="0">
|
||||
<Box h={{ base: 1.5, sm: 2, md: 2.5 }} w="full" bg="bg-white/10" rounded="sm" mb={{ base: 0.5, sm: 1, md: 1.5 }} />
|
||||
<Box position="relative" h={{ base: 0.5, sm: 1, md: 1.5 }} bg="bg-charcoal-outline" rounded="full" overflow="hidden">
|
||||
<Box h="1.5" w="full" bg="white/10" rounded="none" mb={1.5} />
|
||||
<Box position="relative" h="1" bg="graphite-black" rounded="none" overflow="hidden">
|
||||
<Box
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
insetY="0"
|
||||
left="0"
|
||||
rounded="full"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ backgroundColor: teamColors[i-1] }}
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: `${100 - (i-1) * 15}%` }}
|
||||
@@ -395,30 +285,6 @@ export function TeamCompetitionMockup() {
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{i === 3 && (
|
||||
<Box h={{ base: 3, sm: 3.5, md: 4 }} px={{ base: 0.5, sm: 1, md: 1.5 }} bg="bg-warning-amber/20" rounded="sm" display="flex" alignItems="center" justifyContent="center" color="text-warning-amber" weight="semibold" border borderColor="border-warning-amber/30">
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
=
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{hoveredTeam === i && (
|
||||
<Box
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
inset="0"
|
||||
pointerEvents="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{
|
||||
background: `linear-gradient(90deg, ${teamColors[i-1]}10 0%, transparent 100%)`
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
|
||||
import { CheckCircle2, LucideIcon } from 'lucide-react';
|
||||
@@ -43,16 +43,16 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
|
||||
if (!isMounted) {
|
||||
return (
|
||||
<Box position="relative" fullWidth>
|
||||
<Surface variant="muted" rounded="2xl" border={true} padding={6}>
|
||||
<Surface variant="muted" rounded="none" border={true} padding={6} bg="panel-gray/40">
|
||||
<Stack direction="row" justify="between" gap={2}>
|
||||
{steps.map((step) => (
|
||||
<Stack key={step.id} align="center" center direction="col">
|
||||
<Box width="10" height="10" rounded="lg" bg="bg-iron-gray" border={true} borderColor="border-charcoal-outline" display="flex" center mb={2}>
|
||||
<Box width="10" height="10" rounded="none" bg="graphite-black" border={true} borderColor="border-gray/50" display="flex" center mb={2}>
|
||||
<Text color={step.color}>
|
||||
<Icon icon={step.icon} size={4} />
|
||||
</Text>
|
||||
</Box>
|
||||
<Text size="xs" weight="medium" color="text-white">{step.title}</Text>
|
||||
<Text size="xs" weight="bold" color="text-white" className="uppercase tracking-widest">{step.title}</Text>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
@@ -63,15 +63,15 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
|
||||
|
||||
return (
|
||||
<Box position="relative" fullWidth>
|
||||
<Surface variant="muted" rounded="2xl" border={true} padding={6} overflow="hidden">
|
||||
<Surface variant="muted" rounded="none" border={true} padding={6} overflow="hidden" bg="panel-gray/40">
|
||||
{/* Connection Lines */}
|
||||
<Box position="absolute" top="3.5rem" left="8%" right="8%" display={{ base: 'none', sm: 'block' }}>
|
||||
<Box height="0.5" bg="bg-charcoal-outline" position="relative">
|
||||
<Box height="0.5" bg="white/5" position="relative">
|
||||
<Box
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
fullHeight
|
||||
bg="bg-gradient-to-r from-primary-blue to-performance-green"
|
||||
bg="primary-accent"
|
||||
initial={{ width: '0%' }}
|
||||
animate={{ width: `${(activeStep / (steps.length - 1)) * 100}%` }}
|
||||
transition={{ duration: 0.5, ease: 'easeInOut' }}
|
||||
@@ -104,35 +104,39 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
|
||||
as={motion.div}
|
||||
w={{ base: '10', sm: '12' }}
|
||||
h={{ base: '10', sm: '12' }}
|
||||
rounded="lg"
|
||||
rounded="none"
|
||||
border={true}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
mb={2}
|
||||
transition
|
||||
bg={isActive ? 'bg-primary-blue/20' : isCompleted ? 'bg-performance-green/20' : 'bg-iron-gray'}
|
||||
borderColor={isActive ? 'border-primary-blue' : isCompleted ? 'border-performance-green/50' : 'border-charcoal-outline'}
|
||||
shadow={isActive ? 'shadow-[0_0_15px_rgba(25,140,255,0.3)]' : 'none'}
|
||||
bg={isActive ? 'primary-accent/10' : isCompleted ? 'success-green/10' : 'graphite-black'}
|
||||
borderColor={isActive ? 'primary-accent' : isCompleted ? 'success-green/50' : 'border-gray/50'}
|
||||
animate={isActive && !shouldReduceMotion ? {
|
||||
scale: [1, 1.08, 1],
|
||||
transition: { duration: 1, repeat: Infinity }
|
||||
opacity: [0.7, 1, 0.7],
|
||||
transition: { duration: 1.5, repeat: Infinity }
|
||||
} : {}}
|
||||
className="relative"
|
||||
>
|
||||
{isActive && (
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
)}
|
||||
{isCompleted ? (
|
||||
<Icon icon={CheckCircle2} size={5} color="text-performance-green" />
|
||||
<Icon icon={CheckCircle2} size={5} color="text-success-green" />
|
||||
) : (
|
||||
<Text color={isActive ? step.color : 'text-gray-500'}>
|
||||
<Text color={isActive ? 'text-primary-accent' : 'text-gray-600'}>
|
||||
<Icon icon={StepIcon} size={5} />
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Text
|
||||
size="xs"
|
||||
weight="medium"
|
||||
color={isActive ? 'text-white' : 'text-gray-400'}
|
||||
weight="bold"
|
||||
color={isActive ? 'text-white' : 'text-gray-500'}
|
||||
display={{ base: 'none', sm: 'block' }}
|
||||
transition
|
||||
className="uppercase tracking-widest"
|
||||
>
|
||||
{step.title}
|
||||
</Text>
|
||||
@@ -153,15 +157,15 @@ export function WorkflowMockup({ steps }: WorkflowMockupProps) {
|
||||
mt={4}
|
||||
pt={4}
|
||||
borderTop={true}
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/30"
|
||||
display={{ base: 'block', sm: 'none' }}
|
||||
>
|
||||
<Box textAlign="center">
|
||||
<Text size="xs" color="text-gray-400" block mb={1}>
|
||||
Step {activeStep + 1}: {steps[activeStep]?.title || ''}
|
||||
<Text size="xs" color="text-gray-400" block mb={1} font="mono" weight="bold" className="uppercase tracking-widest">
|
||||
STEP {activeStep + 1}: {steps[activeStep]?.title || ''}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block>
|
||||
{steps[activeStep]?.description || ''}
|
||||
<Text size="xs" color="text-gray-600" block font="mono">
|
||||
{steps[activeStep]?.description.toUpperCase() || ''}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -44,40 +44,40 @@ const notificationIcons: Record<string, typeof Bell> = {
|
||||
|
||||
const notificationColors: Record<string, { bg: string; border: string; text: string; glow: string }> = {
|
||||
protest_filed: {
|
||||
bg: 'bg-red-500/10',
|
||||
border: 'border-red-500/50',
|
||||
text: 'text-red-400',
|
||||
glow: 'shadow-[0_0_60px_rgba(239,68,68,0.3)]',
|
||||
bg: 'bg-critical-red/10',
|
||||
border: 'border-critical-red/50',
|
||||
text: 'text-critical-red',
|
||||
glow: 'shadow-[0_0_60px_rgba(227,92,92,0.3)]',
|
||||
},
|
||||
protest_defense_requested: {
|
||||
bg: 'bg-warning-amber/10',
|
||||
border: 'border-warning-amber/50',
|
||||
text: 'text-warning-amber',
|
||||
glow: 'shadow-[0_0_60px_rgba(245,158,11,0.3)]',
|
||||
glow: 'shadow-[0_0_60px_rgba(255,197,86,0.3)]',
|
||||
},
|
||||
protest_vote_required: {
|
||||
bg: 'bg-primary-blue/10',
|
||||
border: 'border-primary-blue/50',
|
||||
text: 'text-primary-blue',
|
||||
bg: 'bg-primary-accent/10',
|
||||
border: 'border-primary-accent/50',
|
||||
text: 'text-primary-accent',
|
||||
glow: 'shadow-[0_0_60px_rgba(25,140,255,0.3)]',
|
||||
},
|
||||
penalty_issued: {
|
||||
bg: 'bg-red-500/10',
|
||||
border: 'border-red-500/50',
|
||||
text: 'text-red-400',
|
||||
glow: 'shadow-[0_0_60px_rgba(239,68,68,0.3)]',
|
||||
bg: 'bg-critical-red/10',
|
||||
border: 'border-critical-red/50',
|
||||
text: 'text-critical-red',
|
||||
glow: 'shadow-[0_0_60px_rgba(227,92,92,0.3)]',
|
||||
},
|
||||
race_performance_summary: {
|
||||
bg: 'bg-gradient-to-br from-yellow-400/20 via-orange-500/20 to-red-500/20',
|
||||
border: 'border-yellow-400/60',
|
||||
text: 'text-yellow-400',
|
||||
glow: 'shadow-[0_0_80px_rgba(251,191,36,0.4)]',
|
||||
bg: 'bg-panel-gray',
|
||||
border: 'border-warning-amber/60',
|
||||
text: 'text-warning-amber',
|
||||
glow: 'shadow-[0_0_80px_rgba(255,197,86,0.2)]',
|
||||
},
|
||||
race_final_results: {
|
||||
bg: 'bg-gradient-to-br from-purple-500/20 via-pink-500/20 to-indigo-500/20',
|
||||
border: 'border-purple-400/60',
|
||||
text: 'text-purple-400',
|
||||
glow: 'shadow-[0_0_80px_rgba(168,85,247,0.4)]',
|
||||
bg: 'bg-panel-gray',
|
||||
border: 'border-primary-accent/60',
|
||||
text: 'text-primary-accent',
|
||||
glow: 'shadow-[0_0_80px_rgba(25,140,255,0.2)]',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -123,10 +123,10 @@ export function ModalNotification({
|
||||
|
||||
const NotificationIcon = notificationIcons[notification.type] || AlertCircle;
|
||||
const colors = notificationColors[notification.type] || {
|
||||
bg: 'bg-warning-amber/10',
|
||||
border: 'border-warning-amber/50',
|
||||
text: 'text-warning-amber',
|
||||
glow: 'shadow-[0_0_60px_rgba(245,158,11,0.3)]',
|
||||
bg: 'bg-panel-gray',
|
||||
border: 'border-border-gray',
|
||||
text: 'text-gray-400',
|
||||
glow: 'shadow-card',
|
||||
};
|
||||
|
||||
const data: Record<string, unknown> = notification.data ?? {};
|
||||
@@ -160,7 +160,6 @@ export function ModalNotification({
|
||||
|
||||
// Special celebratory styling for race notifications
|
||||
const isRaceNotification = notification.type.startsWith('race_');
|
||||
const isPerformanceSummary = notification.type === 'race_performance_summary';
|
||||
|
||||
const provisionalRatingChange = getNumber(data.provisionalRatingChange) ?? 0;
|
||||
const finalRatingChange = getNumber(data.finalRatingChange) ?? 0;
|
||||
@@ -177,10 +176,8 @@ export function ModalNotification({
|
||||
justifyContent="center"
|
||||
p={4}
|
||||
transition
|
||||
bg={isVisible ? 'bg-black/70' : 'bg-transparent'}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
bg={isVisible ? 'bg-black/80' : 'bg-transparent'}
|
||||
className={isVisible ? 'backdrop-blur-sm' : ''}
|
||||
hoverBg={isRaceNotification ? 'bg-gradient-to-br from-black/80 via-indigo-900/10 to-black/80' : undefined}
|
||||
>
|
||||
<Box
|
||||
w="full"
|
||||
@@ -188,55 +185,45 @@ export function ModalNotification({
|
||||
transform
|
||||
transition
|
||||
opacity={isVisible ? 1 : 0}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={isVisible ? 'scale-100' : 'scale-95'}
|
||||
>
|
||||
<Box
|
||||
rounded="2xl"
|
||||
rounded="sm"
|
||||
border
|
||||
borderWidth="2px"
|
||||
borderColor={colors.border}
|
||||
bg={colors.bg}
|
||||
bg="panel-gray"
|
||||
shadow={colors.glow}
|
||||
overflow="hidden"
|
||||
position={isRaceNotification ? 'relative' : undefined}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="backdrop-blur-md"
|
||||
>
|
||||
{/* Header with pulse animation */}
|
||||
{/* Header */}
|
||||
<Box
|
||||
px={6}
|
||||
py={4}
|
||||
bg={colors.bg}
|
||||
bg="graphite-black"
|
||||
borderBottom
|
||||
borderColor={colors.border}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={isRaceNotification ? 'bg-gradient-to-r from-transparent via-yellow-500/10 to-transparent' : ''}
|
||||
>
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Box
|
||||
p={3}
|
||||
rounded="xl"
|
||||
bg={colors.bg}
|
||||
p={2}
|
||||
rounded="sm"
|
||||
bg="panel-gray"
|
||||
border
|
||||
borderColor={colors.border}
|
||||
shadow={isRaceNotification ? 'lg' : undefined}
|
||||
>
|
||||
<Icon icon={NotificationIcon} size={6} color={colors.text} />
|
||||
<Icon icon={NotificationIcon} size={5} color={colors.text} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text
|
||||
size="xs"
|
||||
weight="semibold"
|
||||
transform="uppercase"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="tracking-wide"
|
||||
color={isRaceNotification ? 'text-yellow-400' : 'text-gray-400'}
|
||||
weight="bold"
|
||||
className="uppercase tracking-widest"
|
||||
color="text-gray-500"
|
||||
>
|
||||
{isRaceNotification ? (isPerformanceSummary ? '🏁 Race Complete!' : '🏆 Championship Update') : 'Action Required'}
|
||||
{isRaceNotification ? 'Race Update' : 'Action Required'}
|
||||
</Text>
|
||||
<Heading level={2} fontSize="xl" weight="bold" color="text-white">
|
||||
<Heading level={3} weight="bold" color="text-white">
|
||||
{notification.title}
|
||||
</Heading>
|
||||
</Box>
|
||||
@@ -249,7 +236,7 @@ export function ModalNotification({
|
||||
onClick={() => onDismiss(notification)}
|
||||
variant="ghost"
|
||||
size="md"
|
||||
color="text-gray-400"
|
||||
color="text-gray-500"
|
||||
title="Dismiss notification"
|
||||
/>
|
||||
)}
|
||||
@@ -257,17 +244,11 @@ export function ModalNotification({
|
||||
</Box>
|
||||
|
||||
{/* Body */}
|
||||
<Box
|
||||
px={6}
|
||||
py={5}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={isRaceNotification ? 'bg-gradient-to-b from-transparent to-yellow-500/5' : ''}
|
||||
>
|
||||
<Box px={6} py={6}>
|
||||
<Text
|
||||
leading="relaxed"
|
||||
size={isRaceNotification ? 'lg' : 'base'}
|
||||
weight={isRaceNotification ? 'medium' : 'normal'}
|
||||
color={isRaceNotification ? 'text-white' : 'text-gray-300'}
|
||||
size="base"
|
||||
color="text-gray-300"
|
||||
block
|
||||
>
|
||||
{notification.message}
|
||||
@@ -275,16 +256,16 @@ export function ModalNotification({
|
||||
|
||||
{/* Race performance stats */}
|
||||
{isRaceNotification && (
|
||||
<Box display="grid" gridCols={2} gap={3} mt={4}>
|
||||
<Box bg="bg-black/20" rounded="lg" p={3} border borderColor="border-yellow-400/20">
|
||||
<Text size="xs" color="text-yellow-300" weight="medium" block mb={1}>POSITION</Text>
|
||||
<Box display="grid" gridCols={2} gap={4} mt={6}>
|
||||
<Box bg="graphite-black" rounded="sm" p={4} border borderColor="border-border-gray">
|
||||
<Text size="xs" color="text-gray-500" weight="bold" block mb={1} className="uppercase tracking-widest">POSITION</Text>
|
||||
<Text size="2xl" weight="bold" color="text-white" block>
|
||||
{notification.data?.position === 'DNF' ? 'DNF' : `P${notification.data?.position || '?'}`}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box bg="bg-black/20" rounded="lg" p={3} border borderColor="border-yellow-400/20">
|
||||
<Text size="xs" color="text-yellow-300" weight="medium" block mb={1}>RATING CHANGE</Text>
|
||||
<Text size="2xl" weight="bold" color={ratingChange >= 0 ? 'text-green-400' : 'text-red-400'} block>
|
||||
<Box bg="graphite-black" rounded="sm" p={4} border borderColor="border-border-gray">
|
||||
<Text size="xs" color="text-gray-500" weight="bold" block mb={1} className="uppercase tracking-widest">RATING</Text>
|
||||
<Text size="2xl" weight="bold" color={ratingChange >= 0 ? 'text-success-green' : 'text-critical-red'} block>
|
||||
{ratingChange >= 0 ? '+' : ''}
|
||||
{ratingChange}
|
||||
</Text>
|
||||
@@ -294,12 +275,12 @@ export function ModalNotification({
|
||||
|
||||
{/* Deadline warning */}
|
||||
{hasDeadline && !isRaceNotification && (
|
||||
<Box mt={4} display="flex" alignItems="center" gap={2} px={4} py={3} rounded="lg" bg="bg-warning-amber/10" border borderColor="border-warning-amber/30">
|
||||
<Box mt={6} display="flex" alignItems="center" gap={3} px={4} py={3} rounded="sm" bg="warning-amber/5" border borderColor="border-warning-amber/20">
|
||||
<Icon icon={Clock} size={5} color="text-warning-amber" />
|
||||
<Box>
|
||||
<Text size="sm" weight="medium" color="text-warning-amber" block>Response Required</Text>
|
||||
<Text size="xs" color="text-gray-400" block>
|
||||
Please respond by {deadline ? deadline.toLocaleDateString() : ''} at {deadline ? deadline.toLocaleTimeString() : ''}
|
||||
<Text size="sm" weight="bold" color="text-warning-amber" block className="uppercase tracking-wider">Response Required</Text>
|
||||
<Text size="xs" color="text-gray-500" block mt={0.5}>
|
||||
By {deadline ? deadline.toLocaleDateString() : ''} {deadline ? deadline.toLocaleTimeString() : ''}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -307,9 +288,9 @@ export function ModalNotification({
|
||||
|
||||
{/* Additional context from data */}
|
||||
{protestId && (
|
||||
<Box mt={4} p={3} rounded="lg" bg="bg-iron-gray/50" border borderColor="border-charcoal-outline">
|
||||
<Text size="xs" color="text-gray-500" block mb={1}>Related Protest</Text>
|
||||
<Text size="sm" color="text-gray-300" font="mono" block>
|
||||
<Box mt={6} p={3} rounded="sm" bg="graphite-black" border borderColor="border-border-gray">
|
||||
<Text size="xs" color="text-gray-500" weight="bold" block mb={1} className="uppercase tracking-widest text-[10px]">PROTEST ID</Text>
|
||||
<Text size="xs" color="text-gray-400" font="mono" block>
|
||||
{protestId}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -321,10 +302,8 @@ export function ModalNotification({
|
||||
px={6}
|
||||
py={4}
|
||||
borderTop
|
||||
borderColor={isRaceNotification ? (isPerformanceSummary ? 'border-yellow-400/60' : 'border-purple-400/60') : 'border-charcoal-outline'}
|
||||
bg={isRaceNotification ? undefined : 'bg-iron-gray/30'}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={isRaceNotification ? (isPerformanceSummary ? 'bg-gradient-to-r from-yellow-500/10 to-orange-500/10' : 'bg-gradient-to-r from-purple-500/10 to-pink-500/10') : ''}
|
||||
borderColor="border-border-gray"
|
||||
bg="graphite-black"
|
||||
>
|
||||
<Box display="flex" flexWrap="wrap" gap={3} justifyContent="end">
|
||||
{notification.actions && notification.actions.length > 0 ? (
|
||||
@@ -333,9 +312,7 @@ export function ModalNotification({
|
||||
key={index}
|
||||
variant={action.type === 'primary' ? 'primary' : 'secondary'}
|
||||
onClick={() => handleAction(action)}
|
||||
bg={action.type === 'danger' ? 'bg-red-500' : undefined}
|
||||
color={action.type === 'danger' ? 'text-white' : undefined}
|
||||
shadow={isRaceNotification ? 'lg' : undefined}
|
||||
className={action.type === 'danger' ? 'bg-critical-red hover:bg-critical-red/90' : ''}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
@@ -346,22 +323,14 @@ export function ModalNotification({
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => (onDismiss ? onDismiss(notification) : onAction(notification, 'dismiss'))}
|
||||
shadow="lg"
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleAction({ id: 'share', label: 'Share Achievement', type: 'secondary' })}
|
||||
shadow="lg"
|
||||
>
|
||||
🎉 Share
|
||||
</Button>
|
||||
<Button
|
||||
variant={isPerformanceSummary ? 'race-performance' : 'race-final'}
|
||||
variant="primary"
|
||||
onClick={handlePrimaryAction}
|
||||
>
|
||||
{isPerformanceSummary ? '🏁 View Race Results' : '🏆 View Standings'}
|
||||
{notification.type === 'race_performance_summary' ? 'View Results' : 'View Standings'}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
@@ -375,9 +344,9 @@ export function ModalNotification({
|
||||
|
||||
{/* Cannot dismiss warning */}
|
||||
{notification.requiresResponse && !isRaceNotification && (
|
||||
<Box px={6} py={2} bg="bg-red-500/10" borderTop borderColor="border-red-500/20">
|
||||
<Text size="xs" color="text-red-400" textAlign="center" block>
|
||||
⚠️ This notification requires your action and cannot be dismissed
|
||||
<Box px={6} py={2} bg="critical-red/5" borderTop borderColor="border-critical-red/10">
|
||||
<Text size="xs" color="text-critical-red" textAlign="center" block weight="medium">
|
||||
This action is required to continue
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -10,39 +10,37 @@ module.exports = {
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
'radial-gradient': 'radial-gradient(var(--tw-gradient-stops))',
|
||||
},
|
||||
colors: {
|
||||
'deep-graphite': '#0E0F11',
|
||||
'iron-gray': '#181B1F',
|
||||
'charcoal-outline': '#22262A',
|
||||
'graphite-black': '#0C0D0F',
|
||||
'panel-gray': '#141619',
|
||||
'border-gray': '#23272B',
|
||||
'primary-accent': '#198CFF',
|
||||
'telemetry-aqua': '#4ED4E0',
|
||||
'warning-amber': '#FFBE4D',
|
||||
'success-green': '#6FE37A',
|
||||
'critical-red': '#E35C5C',
|
||||
// Legacy mappings for compatibility during transition
|
||||
'deep-graphite': '#0C0D0F',
|
||||
'iron-gray': '#141619',
|
||||
'charcoal-outline': '#23272B',
|
||||
'primary-blue': '#198CFF',
|
||||
'performance-green': '#6FE37A',
|
||||
'warning-amber': '#FFC556',
|
||||
'neon-aqua': '#43C9E6',
|
||||
'racing-red': '#E31E24',
|
||||
'carbon-black': '#0A0A0A',
|
||||
'metallic-silver': '#C0C0C8',
|
||||
'racing-red': '#E35C5C',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
boxShadow: {
|
||||
'glow': '0 0 20px rgba(25, 140, 255, 0.3)',
|
||||
'glow-strong': '0 0 28px rgba(25, 140, 255, 0.5)',
|
||||
'card': '0 8px 24px rgba(0, 0, 0, 0.12)',
|
||||
'racing': '0 4px 16px rgba(227, 30, 36, 0.15)',
|
||||
'card': '0 4px 12px rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
transitionDuration: {
|
||||
'smooth': '150ms',
|
||||
},
|
||||
transitionTimingFunction: {
|
||||
'spring': 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||
'speed': 'cubic-bezier(0.22, 1, 0.36, 1)',
|
||||
},
|
||||
animation: {
|
||||
'speed-pulse': 'speed-pulse 2s ease-in-out infinite',
|
||||
},
|
||||
keyframes: {
|
||||
'speed-pulse': {
|
||||
'0%, 100%': { opacity: '0.5' },
|
||||
'50%': { opacity: '1' },
|
||||
},
|
||||
'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,27 +4,20 @@ import { AlternatingSection } from '@/components/landing/AlternatingSection';
|
||||
import { FAQ } from '@/components/landing/FAQ';
|
||||
import { FeatureGrid } from '@/components/landing/FeatureGrid';
|
||||
import { LandingHero } from '@/components/landing/LandingHero';
|
||||
import { FeatureItem, ResultItem, StepItem } from '@/components/landing/LandingItems';
|
||||
import { DiscoverySection } from '@/components/landing/DiscoverySection';
|
||||
import { FeatureItem, ResultItem, StepItem } from '@/ui/LandingItems';
|
||||
import { CareerProgressionMockup } from '@/components/mockups/CareerProgressionMockup';
|
||||
import { CompanionAutomationMockup } from '@/components/mockups/CompanionAutomationMockup';
|
||||
import { RaceHistoryMockup } from '@/components/mockups/RaceHistoryMockup';
|
||||
import { SimPlatformMockup } from '@/components/mockups/SimPlatformMockup';
|
||||
import { ModeGuard } from '@/components/shared/ModeGuard';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { getMediaUrl } from '@/lib/utilities/media';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { DiscordCTA } from '@/ui/DiscordCTA';
|
||||
import { Footer } from '@/ui/Footer';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { TelemetryLine } from '@/ui/TelemetryLine';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
|
||||
export interface HomeViewData {
|
||||
isAlpha: boolean;
|
||||
@@ -53,220 +46,132 @@ interface HomeTemplateProps {
|
||||
|
||||
export function HomeTemplate({ viewData }: HomeTemplateProps) {
|
||||
return (
|
||||
<Box as="main">
|
||||
<Box as="main" bg="graphite-black" position="relative" overflow="hidden">
|
||||
<Glow color="primary" size="xl" position="top-right" opacity={0.05} />
|
||||
|
||||
<LandingHero />
|
||||
|
||||
<TelemetryLine color="primary" height="1px" opacity={0.3} />
|
||||
|
||||
{/* Section 1: A Persistent Identity */}
|
||||
<AlternatingSection
|
||||
heading="A Persistent Identity"
|
||||
backgroundVideo="/gameplay.mp4"
|
||||
description={
|
||||
<Stack gap={4}>
|
||||
<Text>
|
||||
Your races, your seasons, your progress — finally in one place.
|
||||
</Text>
|
||||
<Stack gap={3}>
|
||||
<FeatureItem text="Lifetime stats and season history across all your leagues" />
|
||||
<FeatureItem text="Track your performance, consistency, and team contributions" />
|
||||
<FeatureItem text="Your own rating that reflects real league competition" />
|
||||
<Box position="relative" bg="graphite-black">
|
||||
<Glow color="aqua" size="lg" position="bottom-left" opacity={0.03} />
|
||||
<AlternatingSection
|
||||
heading="A Persistent Identity"
|
||||
backgroundVideo="/gameplay.mp4"
|
||||
description={
|
||||
<Stack gap={8}>
|
||||
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
|
||||
Your races, your seasons, your progress — finally in one place.
|
||||
</Text>
|
||||
<Box display="grid" gridCols={1} gap={3}>
|
||||
<FeatureItem text="Lifetime stats and season history across all your leagues" />
|
||||
<FeatureItem text="Track your performance, consistency, and team contributions" />
|
||||
<FeatureItem text="Your own rating that reflects real league competition" />
|
||||
</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>
|
||||
<Text>
|
||||
iRacing gives you physics. GridPilot gives you a career.
|
||||
</Text>
|
||||
</Stack>
|
||||
}
|
||||
mockup={<CareerProgressionMockup />}
|
||||
layout="text-left"
|
||||
/>
|
||||
}
|
||||
mockup={<CareerProgressionMockup />}
|
||||
layout="text-left"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<FeatureGrid />
|
||||
|
||||
{/* Section 2: Results That Actually Stay */}
|
||||
<AlternatingSection
|
||||
heading="Results That Actually Stay"
|
||||
backgroundImage="/images/ff1600.jpeg"
|
||||
description={
|
||||
<Stack gap={4}>
|
||||
<Text size="sm">
|
||||
Every race you run stays with you.
|
||||
</Text>
|
||||
<Stack gap={3}>
|
||||
<ResultItem text="Your stats, your team, your story — all connected" color="#ef4444" />
|
||||
<ResultItem text="One race result updates your profile, team points, rating, and season history" color="#ef4444" />
|
||||
<ResultItem text="No more fragmented data across spreadsheets and forums" color="#ef4444" />
|
||||
<Box position="relative" bg="graphite-black">
|
||||
<Glow color="primary" size="lg" position="top-right" opacity={0.03} />
|
||||
<AlternatingSection
|
||||
heading="Results That Actually Stay"
|
||||
backgroundImage="/images/ff1600.jpeg"
|
||||
description={
|
||||
<Stack gap={8}>
|
||||
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
|
||||
Every race you run stays with you.
|
||||
</Text>
|
||||
<Box display="grid" gridCols={1} gap={3}>
|
||||
<ResultItem text="Your stats, your team, your story — 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>
|
||||
<Text size="sm">
|
||||
Your racing career, finally in one place.
|
||||
</Text>
|
||||
</Stack>
|
||||
}
|
||||
mockup={<RaceHistoryMockup />}
|
||||
layout="text-right"
|
||||
/>
|
||||
}
|
||||
mockup={<RaceHistoryMockup />}
|
||||
layout="text-right"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TelemetryLine color="aqua" height="1px" opacity={0.2} />
|
||||
|
||||
{/* Section 3: Automatic Session Creation */}
|
||||
<AlternatingSection
|
||||
heading="Automatic Session Creation"
|
||||
description={
|
||||
<Stack gap={4}>
|
||||
<Text size="sm">
|
||||
Setting up league races used to mean clicking through iRacing's wizard 20 times.
|
||||
</Text>
|
||||
<Stack gap={3}>
|
||||
<StepItem step={1} text="Our companion app syncs with your league schedule" />
|
||||
<StepItem step={2} text="When it's race time, it creates the iRacing session automatically" />
|
||||
<StepItem step={3} text="No clicking through wizards. No manual setup" />
|
||||
<Box position="relative" bg="graphite-black">
|
||||
<Glow color="amber" size="lg" position="bottom-right" opacity={0.02} />
|
||||
<AlternatingSection
|
||||
heading="Automatic Session Creation"
|
||||
description={
|
||||
<Stack gap={8}>
|
||||
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
|
||||
Setting up league races used to mean clicking through iRacing's wizard 20 times.
|
||||
</Text>
|
||||
<Box display="grid" gridCols={1} gap={3}>
|
||||
<StepItem step={1} text="Our companion app syncs with your league schedule" />
|
||||
<StepItem step={2} text="When it's race time, it creates the iRacing session automatically" />
|
||||
<StepItem step={3} text="No clicking through wizards. No manual setup" />
|
||||
</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>
|
||||
<Text size="sm">
|
||||
Automation instead of repetition.
|
||||
</Text>
|
||||
</Stack>
|
||||
}
|
||||
mockup={<CompanionAutomationMockup />}
|
||||
layout="text-left"
|
||||
/>
|
||||
}
|
||||
mockup={<CompanionAutomationMockup />}
|
||||
layout="text-left"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Section 4: Game-Agnostic Platform */}
|
||||
<AlternatingSection
|
||||
heading="Built for iRacing. Ready for the future."
|
||||
backgroundImage="/images/lmp3.jpeg"
|
||||
description={
|
||||
<Stack gap={4}>
|
||||
<Text size="sm">
|
||||
Right now, we're focused on making iRacing league racing better.
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
But sims come and go. Your leagues, your teams, your rating — those stay.
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
GridPilot is built to outlast any single platform.
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
When the next sim arrives, your competitive identity moves with you.
|
||||
</Text>
|
||||
</Stack>
|
||||
}
|
||||
mockup={<SimPlatformMockup />}
|
||||
layout="text-right"
|
||||
/>
|
||||
<Box position="relative" bg="graphite-black">
|
||||
<Glow color="primary" size="xl" position="center" opacity={0.03} />
|
||||
<AlternatingSection
|
||||
heading="Built for iRacing. Ready for the future."
|
||||
backgroundImage="/images/lmp3.jpeg"
|
||||
description={
|
||||
<Stack gap={8}>
|
||||
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
|
||||
Right now, we're focused on making iRacing league racing better.
|
||||
</Text>
|
||||
<Text color="text-gray-400" leading="relaxed">
|
||||
But sims come and go. Your leagues, your teams, your rating — those stay.
|
||||
</Text>
|
||||
<Box borderLeft borderStyle="solid" borderColor="border-gray" pl={4} py={1} bg="white/5">
|
||||
<Text color="text-gray-500" font="mono" size="xs" uppercase letterSpacing="widest" leading="relaxed">
|
||||
GridPilot is built to outlast any single platform. When the next sim arrives, your competitive identity moves with you.
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
}
|
||||
mockup={<SimPlatformMockup />}
|
||||
layout="text-right"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Alpha-only discovery section */}
|
||||
<ModeGuard feature="alpha_discovery">
|
||||
<Container size="lg" py={12}>
|
||||
<Stack gap={8}>
|
||||
<Box>
|
||||
<Heading level={2}>Discover the grid</Heading>
|
||||
<Text size="sm" color="text-gray-400" block mt={2}>
|
||||
Explore leagues, teams, and races that make up the GridPilot ecosystem.
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<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>
|
||||
<Box bg="panel-gray/20" py={20} borderTop borderBottom borderColor="border-gray/50" position="relative">
|
||||
<Glow color="aqua" size="xl" position="center" opacity={0.02} />
|
||||
<DiscoverySection viewData={viewData} />
|
||||
</Box>
|
||||
</ModeGuard>
|
||||
|
||||
<DiscordCTA />
|
||||
|
||||
@@ -16,21 +16,21 @@ interface BadgeProps {
|
||||
}
|
||||
|
||||
export function Badge({ children, className = '', variant = 'default', size = 'sm', icon, style, bg, color, borderColor }: BadgeProps) {
|
||||
const baseClasses = 'flex items-center gap-1.5 rounded-full border font-medium';
|
||||
const baseClasses = 'flex items-center gap-1.5 rounded-none border font-bold uppercase tracking-widest';
|
||||
|
||||
const sizeClasses = {
|
||||
xs: 'px-1.5 py-0.5 text-[10px]',
|
||||
sm: 'px-2.5 py-1 text-xs',
|
||||
md: 'px-3 py-1.5 text-sm'
|
||||
xs: 'px-1.5 py-0.5 text-[9px]',
|
||||
sm: 'px-2 py-0.5 text-[10px]',
|
||||
md: 'px-3 py-1 text-xs'
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
default: 'bg-gray-500/10 border-gray-500/30 text-gray-400',
|
||||
primary: 'bg-primary-blue/10 border-primary-blue/30 text-primary-blue',
|
||||
success: 'bg-performance-green/10 border-performance-green/30 text-performance-green',
|
||||
primary: 'bg-primary-accent/10 border-primary-accent/30 text-primary-accent',
|
||||
success: 'bg-success-green/10 border-success-green/30 text-success-green',
|
||||
warning: 'bg-warning-amber/10 border-warning-amber/30 text-warning-amber',
|
||||
danger: 'bg-red-600/10 border-red-600/30 text-red-500',
|
||||
info: 'bg-neon-aqua/10 border-neon-aqua/30 text-neon-aqua'
|
||||
danger: 'bg-critical-red/10 border-critical-red/30 text-critical-red',
|
||||
info: 'bg-telemetry-aqua/10 border-telemetry-aqua/30 text-telemetry-aqua'
|
||||
};
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -6,7 +6,7 @@ interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'as'
|
||||
children: ReactNode;
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
className?: string;
|
||||
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'race-performance' | 'race-final' | 'discord';
|
||||
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'race-final' | 'discord';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
disabled?: boolean;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
@@ -34,25 +34,24 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
|
||||
rel,
|
||||
...props
|
||||
}, ref) => {
|
||||
const baseClasses = 'inline-flex items-center rounded-lg transition-all duration-75 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 hover:scale-[1.02] active:scale-95';
|
||||
const baseClasses = 'inline-flex items-center justify-center rounded-none transition-all duration-150 ease-smooth focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 active:opacity-80 uppercase tracking-widest font-bold';
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'bg-primary-blue text-white hover:bg-primary-blue/80 focus-visible:outline-primary-blue shadow-[0_0_15px_rgba(25,140,255,0.4)]',
|
||||
secondary: 'bg-iron-gray text-white border border-charcoal-outline hover:bg-iron-gray/80 focus-visible:outline-primary-blue',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700 focus-visible:outline-red-600',
|
||||
ghost: 'bg-transparent text-gray-400 hover:bg-gray-800 focus-visible:outline-gray-400',
|
||||
'race-performance': 'bg-gradient-to-r from-yellow-400 to-orange-500 text-white shadow-[0_0_15px_rgba(251,191,36,0.4)] hover:from-yellow-500 hover:to-orange-600 focus-visible:outline-yellow-400',
|
||||
'race-final': 'bg-gradient-to-r from-purple-400 to-pink-500 text-white shadow-[0_0_15px_rgba(168,85,247,0.4)] hover:from-purple-500 hover:to-pink-600 focus-visible:outline-purple-400',
|
||||
discord: 'bg-[#5865F2] text-white hover:bg-[#4752C4] shadow-[0_0_20px_rgba(88,101,242,0.3)] hover:shadow-[0_0_30px_rgba(88,101,242,0.6)] focus-visible:outline-[#5865F2]'
|
||||
primary: 'bg-primary-accent text-white hover:bg-primary-accent/90 focus-visible:outline-primary-accent shadow-[0_0_15px_rgba(25,140,255,0.3)] hover:shadow-[0_0_25px_rgba(25,140,255,0.5)]',
|
||||
secondary: 'bg-panel-gray text-white border border-border-gray hover:bg-border-gray/50 focus-visible:outline-primary-accent',
|
||||
danger: 'bg-critical-red text-white hover:bg-critical-red/90 focus-visible:outline-critical-red',
|
||||
ghost: 'bg-transparent text-gray-400 hover:text-white hover:bg-white/5 focus-visible:outline-gray-400',
|
||||
'race-final': 'bg-success-green text-graphite-black hover:bg-success-green/90 focus-visible:outline-success-green',
|
||||
discord: 'bg-[#5865F2] text-white hover:bg-[#4752C4] focus-visible:outline-[#5865F2]',
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'min-h-[36px] px-3 py-1.5 text-xs',
|
||||
md: 'min-h-[44px] px-4 py-2 text-sm',
|
||||
lg: 'min-h-[52px] px-6 py-3 text-base'
|
||||
sm: 'min-h-[32px] px-3 py-1 text-xs font-medium',
|
||||
md: 'min-h-[40px] px-4 py-2 text-sm font-medium',
|
||||
lg: 'min-h-[48px] px-6 py-3 text-base font-medium'
|
||||
};
|
||||
|
||||
const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer';
|
||||
const disabledClasses = disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer';
|
||||
const widthClasses = fullWidth ? 'w-full' : '';
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -13,7 +13,7 @@ interface CardProps extends Omit<BoxProps<'div'>, 'children' | 'className' | 'on
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
variant?: 'default' | 'highlight';
|
||||
variant?: 'default' | 'outline' | 'ghost';
|
||||
p?: Spacing | ResponsiveSpacing;
|
||||
px?: Spacing | ResponsiveSpacing;
|
||||
py?: Spacing | ResponsiveSpacing;
|
||||
@@ -30,17 +30,18 @@ export function Card({
|
||||
variant = 'default',
|
||||
...props
|
||||
}: CardProps) {
|
||||
const baseClasses = 'rounded-lg shadow-card border duration-200';
|
||||
const baseClasses = 'rounded-none transition-all duration-150 ease-smooth';
|
||||
|
||||
const variantClasses = {
|
||||
default: 'bg-iron-gray border-charcoal-outline',
|
||||
highlight: 'bg-gradient-to-r from-blue-900/20 to-blue-700/10 border-blue-500/30'
|
||||
default: 'bg-panel-gray border border-border-gray shadow-card',
|
||||
outline: 'bg-transparent border border-border-gray',
|
||||
ghost: 'bg-transparent border-none'
|
||||
};
|
||||
|
||||
const classes = [
|
||||
baseClasses,
|
||||
variantClasses[variant],
|
||||
onClick ? 'cursor-pointer hover:scale-[1.02]' : '',
|
||||
onClick ? 'cursor-pointer hover:bg-border-gray/30' : '',
|
||||
className
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
@@ -52,7 +53,7 @@ export function Card({
|
||||
<Box
|
||||
className={classes}
|
||||
onClick={onClick}
|
||||
p={hasPadding ? undefined : 6}
|
||||
p={hasPadding ? undefined : 4}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -15,11 +15,11 @@ export function DecorativeBlur({
|
||||
opacity = 10
|
||||
}: DecorativeBlurProps) {
|
||||
const colorClasses = {
|
||||
blue: 'bg-primary-blue',
|
||||
green: 'bg-performance-green',
|
||||
blue: 'bg-primary-accent',
|
||||
green: 'bg-success-green',
|
||||
purple: 'bg-purple-600',
|
||||
yellow: 'bg-yellow-400',
|
||||
red: 'bg-racing-red'
|
||||
yellow: 'bg-warning-amber',
|
||||
red: 'bg-critical-red'
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { DiscordIcon } from '@/ui/icons/DiscordIcon';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
@@ -14,21 +17,25 @@ export function DiscordCTA() {
|
||||
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#';
|
||||
|
||||
return (
|
||||
<Surface
|
||||
<Box
|
||||
as="section"
|
||||
variant="discord"
|
||||
padding={4}
|
||||
bg="graphite-black"
|
||||
position="relative"
|
||||
py={{ base: 4, md: 12, lg: 16 }}
|
||||
py={{ base: 20, md: 32 }}
|
||||
borderBottom
|
||||
borderColor="border-gray/50"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box maxWidth="896px" mx="auto" px={2}>
|
||||
<Glow color="primary" size="xl" position="center" opacity={0.05} />
|
||||
|
||||
<Container size="lg" position="relative" zIndex={10}>
|
||||
<Surface
|
||||
variant="discord-inner"
|
||||
padding={3}
|
||||
variant="default"
|
||||
padding={12}
|
||||
border
|
||||
rounded="xl"
|
||||
rounded="none"
|
||||
position="relative"
|
||||
shadow="discord"
|
||||
className="overflow-hidden bg-panel-gray/40"
|
||||
>
|
||||
{/* Discord brand accent */}
|
||||
<Box
|
||||
@@ -37,120 +44,107 @@ export function DiscordCTA() {
|
||||
left={0}
|
||||
right={0}
|
||||
height="1"
|
||||
backgroundColor="[#5865F2]"
|
||||
opacity={0.6}
|
||||
className="bg-gradient-to-r from-transparent via-[#5865F2]/60 to-transparent"
|
||||
bg="primary-accent"
|
||||
/>
|
||||
|
||||
<Stack align="center" gap={6} center>
|
||||
<Stack align="center" gap={12} center>
|
||||
{/* Header */}
|
||||
<Stack align="center" gap={4}>
|
||||
<Stack align="center" gap={6}>
|
||||
<Box
|
||||
display="flex"
|
||||
center
|
||||
rounded="full"
|
||||
w={{ base: "10", md: "14", lg: "18" }}
|
||||
h={{ base: "10", md: "14", lg: "18" }}
|
||||
backgroundColor="[#5865F2]"
|
||||
opacity={0.2}
|
||||
rounded="none"
|
||||
w={{ base: "16", md: "20" }}
|
||||
h={{ base: "16", md: "20" }}
|
||||
bg="primary-accent/10"
|
||||
border
|
||||
borderColor="[#5865F2]"
|
||||
borderColor="primary-accent/30"
|
||||
className="relative"
|
||||
>
|
||||
<DiscordIcon color="text-[#5865F2]" size={32} />
|
||||
<DiscordIcon color="text-primary-accent" size={40} />
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent" />
|
||||
<Box position="absolute" bottom="-1px" right="-1px" w="2" h="2" borderBottom borderRight borderColor="primary-accent" />
|
||||
</Box>
|
||||
|
||||
<Stack gap={2}>
|
||||
<Text as="h2" size="2xl" weight="semibold" color="text-white">
|
||||
Join us on Discord
|
||||
</Text>
|
||||
<Stack gap={4} align="center">
|
||||
<Heading level={2} weight="bold" color="text-white" fontSize={{ base: '2xl', md: '4xl' }} className="tracking-tight">
|
||||
Join the Grid on Discord
|
||||
</Heading>
|
||||
<Box
|
||||
mx="auto"
|
||||
rounded="full"
|
||||
w={{ base: "16", md: "24", lg: "32" }}
|
||||
h={{ base: "0.5", md: "1" }}
|
||||
backgroundColor="[#5865F2]"
|
||||
className="bg-gradient-to-r from-[#5865F2] to-[#7289DA]"
|
||||
w="16"
|
||||
h="1"
|
||||
bg="primary-accent"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Personal message */}
|
||||
<Box maxWidth="672px" mx="auto">
|
||||
<Stack gap={3}>
|
||||
<Text size="sm" color="text-gray-300" weight="normal" leading="relaxed">
|
||||
GridPilot is a <Text weight="bold" color="text-white">solo developer project</Text>, and I'm building it because I got tired of the chaos in league racing.
|
||||
<Box maxWidth="2xl" mx="auto" textAlign="center">
|
||||
<Stack gap={6}>
|
||||
<Text size="lg" color="text-gray-300" weight="medium" leading="relaxed">
|
||||
GridPilot is a <span className="text-white font-bold">solo developer project</span> built for the community.
|
||||
</Text>
|
||||
<Text size="sm" color="text-gray-400" weight="normal" leading="relaxed">
|
||||
This is <Text weight="bold" color="text-gray-300">early days</Text>, and I need your help. Join the Discord to:
|
||||
<Text size="base" color="text-gray-400" weight="normal" leading="relaxed">
|
||||
We are in early alpha. Join us to help shape the future of motorsport infrastructure. Your feedback directly influences the roadmap.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Benefits grid */}
|
||||
<Box
|
||||
maxWidth="2xl"
|
||||
maxWidth="4xl"
|
||||
mx="auto"
|
||||
mt={4}
|
||||
fullWidth
|
||||
>
|
||||
<Grid cols={2} gap={3} className="md:grid-cols-2">
|
||||
<Grid cols={1} mdCols={2} gap={6}>
|
||||
<BenefitItem
|
||||
icon={MessageSquare}
|
||||
title="Share your pain points"
|
||||
description="Tell me what frustrates you about league racing today"
|
||||
description="Tell us what frustrates you about league racing today."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Lightbulb}
|
||||
title="Shape the product"
|
||||
description="Your ideas will directly influence what gets built"
|
||||
description="Your ideas directly influence our roadmap."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Users}
|
||||
title="Be part of the community"
|
||||
description="Connect with other league racers who get it"
|
||||
title="Connect with racers"
|
||||
description="Join a community of like-minded competitive drivers."
|
||||
/>
|
||||
<BenefitItem
|
||||
icon={Code}
|
||||
title="Get early access"
|
||||
description="Test features first and help iron out the rough edges"
|
||||
title="Early Access"
|
||||
description="Test new features before they go public."
|
||||
/>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* CTA Button */}
|
||||
<Stack gap={3} pt={4}>
|
||||
<Stack gap={6} pt={4} align="center">
|
||||
<Button
|
||||
as="a"
|
||||
href={discordUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="discord"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
icon={<DiscordIcon size={28} />}
|
||||
className="px-16 py-4"
|
||||
icon={<DiscordIcon size={24} />}
|
||||
>
|
||||
Join us on Discord
|
||||
Join Discord
|
||||
</Button>
|
||||
|
||||
<Text size="xs" color="text-primary-blue" weight="light">
|
||||
💡 Get a link to our early alpha view in the Discord
|
||||
</Text>
|
||||
|
||||
{!process.env.NEXT_PUBLIC_DISCORD_URL && (
|
||||
<Text size="xs" color="text-gray-500">
|
||||
Note: Configure NEXT_PUBLIC_DISCORD_URL in your environment variables
|
||||
<Box border borderStyle="dashed" borderColor="primary-accent/50" px={4} py={1}>
|
||||
<Text size="xs" color="text-primary-accent" weight="bold" font="mono" uppercase letterSpacing="widest">
|
||||
Early Alpha Access Available
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* 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's build something that actually works for league racing.
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Surface>
|
||||
</Box>
|
||||
</Surface>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,30 +153,29 @@ function BenefitItem({ icon, title, description }: { icon: LucideIcon, title: st
|
||||
<Surface
|
||||
variant="muted"
|
||||
border
|
||||
padding={3}
|
||||
rounded="lg"
|
||||
padding={6}
|
||||
rounded="none"
|
||||
display="flex"
|
||||
gap={3}
|
||||
className="items-start hover:border-[#5865F2]/30 transition-all"
|
||||
gap={5}
|
||||
className="items-start hover:border-primary-accent/30 transition-all bg-panel-gray/20 group"
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
center
|
||||
rounded="lg"
|
||||
rounded="none"
|
||||
flexShrink={0}
|
||||
w="6"
|
||||
h="6"
|
||||
backgroundColor="[#5865F2]"
|
||||
opacity={0.2}
|
||||
w="10"
|
||||
h="10"
|
||||
bg="primary-accent/5"
|
||||
border
|
||||
borderColor="[#5865F2]"
|
||||
mt={0.5}
|
||||
borderColor="border-gray/50"
|
||||
className="group-hover:border-primary-accent/30 transition-colors"
|
||||
>
|
||||
<Icon icon={icon} size={4} color="text-[#5865F2]" />
|
||||
<Icon icon={icon} size={5} color="text-primary-accent" />
|
||||
</Box>
|
||||
<Stack gap={0.5}>
|
||||
<Text size="xs" weight="medium" color="text-white">{title}</Text>
|
||||
<Text size="xs" color="text-gray-400" leading="relaxed">{description}</Text>
|
||||
<Stack gap={2}>
|
||||
<Text size="base" weight="bold" color="text-white" className="tracking-wide">{title}</Text>
|
||||
<Text size="sm" color="text-gray-400" leading="relaxed">{description}</Text>
|
||||
</Stack>
|
||||
</Surface>
|
||||
);
|
||||
|
||||
@@ -8,41 +8,31 @@ const xUrl = process.env.NEXT_PUBLIC_X_URL || '#';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<Box as="footer" position="relative" bg="bg-deep-graphite">
|
||||
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, var(--primary-blue), transparent)" />
|
||||
<Box as="footer" position="relative" bg="graphite-black" borderTop borderColor="border-gray/50">
|
||||
<Box position="absolute" top="0" left="0" right="0" h="px" bg="linear-gradient(to right, transparent, #198CFF, transparent)" opacity={0.3} />
|
||||
|
||||
<Box maxWidth="4xl" mx="auto" px={{ base: 'calc(1.5rem+var(--sal))', lg: 8 }} py={{ base: 2, md: 8, lg: 12 }} pb={{ base: 'calc(0.5rem+var(--sab))', md: 'calc(1.5rem+var(--sab))' }}>
|
||||
<Box maxWidth="7xl" mx="auto" px={{ base: 6, lg: 8 }} py={{ base: 12, md: 16 }}>
|
||||
{/* Racing stripe accent */}
|
||||
<Box
|
||||
display="flex"
|
||||
gap={1}
|
||||
mb={{ base: 2, md: 4, lg: 6 }}
|
||||
gap={2}
|
||||
mb={8}
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box w={{ base: "12", md: "20", lg: "28" }} h={{ base: "0.5", md: "0.5", lg: "1" }} bg="bg-white" rounded="full" />
|
||||
<Box w={{ base: "12", md: "20", lg: "28" }} h={{ base: "0.5", md: "0.5", lg: "1" }} bg="bg-primary-blue" rounded="full" />
|
||||
<Box w={{ base: "12", md: "20", lg: "28" }} h={{ base: "0.5", md: "0.5", lg: "1" }} bg="bg-white" rounded="full" />
|
||||
<Box w="12" h="1" bg="white" opacity={0.1} />
|
||||
<Box w="12" h="1" bg="primary-accent" />
|
||||
<Box w="12" h="1" bg="white" opacity={0.1} />
|
||||
</Box>
|
||||
|
||||
{/* Personal message */}
|
||||
<Box
|
||||
textAlign="center"
|
||||
mb={{ base: 3, md: 6, lg: 8 }}
|
||||
mb={12}
|
||||
>
|
||||
<Box mb={2} display="flex" justifyContent="center">
|
||||
<Image
|
||||
src="/images/logos/icon-square-dark.svg"
|
||||
alt="GridPilot"
|
||||
width={40}
|
||||
height={40}
|
||||
fullHeight
|
||||
style={{ width: 'auto' }}
|
||||
/>
|
||||
</Box>
|
||||
<Text size={{ base: 'xs', lg: 'sm' }} color="text-gray-300" block mb={{ base: 1, md: 2 }}>
|
||||
<Text size="sm" color="text-gray-300" block mb={2} weight="bold" className="tracking-wide">
|
||||
🏁 Built by a sim racer, for sim racers
|
||||
</Text>
|
||||
<Text size={{ base: 'xs', md: 'xs' }} color="text-gray-400" weight="light" maxWidth="2xl" mx="auto" block>
|
||||
<Text size="xs" color="text-gray-500" weight="normal" maxWidth="2xl" mx="auto" block leading="relaxed">
|
||||
Just a fellow racer tired of spreadsheets and chaos. GridPilot is my passion project to make league racing actually fun again.
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -51,50 +41,39 @@ export function Footer() {
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
gap={{ base: 4, md: 6, lg: 8 }}
|
||||
mb={{ base: 3, md: 6, lg: 8 }}
|
||||
gap={8}
|
||||
mb={12}
|
||||
>
|
||||
<Link
|
||||
href={discordUrl}
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
hoverTextColor="text-neon-aqua"
|
||||
transition
|
||||
px={3}
|
||||
py={2}
|
||||
minHeight="44px"
|
||||
minWidth="44px"
|
||||
size="sm"
|
||||
className="text-gray-400 hover:text-primary-accent transition-colors font-bold uppercase tracking-widest"
|
||||
>
|
||||
💬 Join Discord
|
||||
💬 Discord
|
||||
</Link>
|
||||
<Link
|
||||
href={xUrl}
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
color="text-gray-300"
|
||||
hoverTextColor="text-neon-aqua"
|
||||
transition
|
||||
px={3}
|
||||
py={2}
|
||||
minHeight="44px"
|
||||
minWidth="44px"
|
||||
size="sm"
|
||||
className="text-gray-400 hover:text-primary-accent transition-colors font-bold uppercase tracking-widest"
|
||||
>
|
||||
𝕏 Follow on X
|
||||
𝕏 Twitter
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
{/* Development status */}
|
||||
<Box
|
||||
textAlign="center"
|
||||
pt={{ base: 2, md: 4, lg: 6 }}
|
||||
pt={8}
|
||||
borderTop
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/30"
|
||||
>
|
||||
<Text size={{ base: 'xs', lg: 'sm' }} color="text-gray-500" block mb={{ base: 1, md: 2 }}>
|
||||
<Text size="xs" color="text-gray-600" block mb={1} font="mono" uppercase letterSpacing="widest">
|
||||
⚡ Early development • Feedback welcome
|
||||
</Text>
|
||||
<Text size={{ base: 'xs', md: 'xs' }} color="text-gray-600" block>
|
||||
Questions? Find me on Discord
|
||||
<Text size="xs" color="text-gray-700" block font="mono">
|
||||
© {new Date().getFullYear()} GridPilot
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
49
apps/website/ui/Glow.tsx
Normal file
49
apps/website/ui/Glow.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ interface HeaderProps {
|
||||
|
||||
export function Header({ children }: HeaderProps) {
|
||||
return (
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-deep-graphite/80 backdrop-blur-sm border-b border-white/5">
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-graphite-black/80 backdrop-blur-md border-b border-border-gray/50">
|
||||
<Container>
|
||||
{children}
|
||||
</Container>
|
||||
|
||||
@@ -26,12 +26,12 @@ export function Heading({ level, children, icon, groupHoverColor, truncate, font
|
||||
const Tag = `h${level}` as ElementType;
|
||||
|
||||
const levelClasses = {
|
||||
1: 'text-3xl md:text-4xl font-bold text-white',
|
||||
2: 'text-xl font-semibold text-white',
|
||||
3: 'text-lg font-semibold text-white',
|
||||
4: 'text-base font-semibold text-white',
|
||||
5: 'text-sm font-semibold text-white',
|
||||
6: 'text-xs font-semibold text-white',
|
||||
1: 'text-3xl md:text-4xl font-bold text-white tracking-tight',
|
||||
2: 'text-xl md:text-2xl font-bold text-white tracking-tight',
|
||||
3: 'text-lg font-bold text-white tracking-tight',
|
||||
4: 'text-base font-bold text-white tracking-tight',
|
||||
5: 'text-sm font-bold text-white tracking-tight uppercase tracking-wider',
|
||||
6: 'text-xs font-bold text-white tracking-tight uppercase tracking-widest',
|
||||
};
|
||||
|
||||
const weightClasses = {
|
||||
|
||||
@@ -12,15 +12,15 @@ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className = '', variant = 'default', errorMessage, icon, label, ...props }, ref) => {
|
||||
const baseClasses = 'px-3 py-2 border rounded-lg text-white bg-deep-graphite focus:outline-none focus:border-primary-blue transition-colors w-full';
|
||||
const variantClasses = (variant === 'error' || errorMessage) ? 'border-racing-red' : 'border-charcoal-outline';
|
||||
const baseClasses = 'px-3 py-2 border rounded-sm text-white bg-graphite-black focus:outline-none focus:border-primary-accent transition-all duration-150 ease-smooth w-full text-sm placeholder:text-gray-600';
|
||||
const variantClasses = (variant === 'error' || errorMessage) ? 'border-critical-red' : 'border-border-gray';
|
||||
const iconClasses = icon ? 'pl-10' : '';
|
||||
const classes = `${baseClasses} ${variantClasses} ${iconClasses} ${className}`;
|
||||
|
||||
return (
|
||||
<Stack gap={1.5} fullWidth>
|
||||
{label && (
|
||||
<Text as="label" size="sm" weight="medium" color="text-gray-300">
|
||||
<Text as="label" size="xs" weight="bold" color="text-gray-500" className="uppercase tracking-wider">
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
@@ -34,13 +34,14 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
zIndex={10}
|
||||
display="flex"
|
||||
center
|
||||
className="text-gray-500"
|
||||
>
|
||||
{icon}
|
||||
</Box>
|
||||
)}
|
||||
<input ref={ref} className={classes} {...props} />
|
||||
{errorMessage && (
|
||||
<Text size="xs" color="text-error-red" block mt={1}>
|
||||
<Text size="xs" color="text-critical-red" block mt={1}>
|
||||
{errorMessage}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
46
apps/website/ui/LandingItems.tsx
Normal file
46
apps/website/ui/LandingItems.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Box } from './Box';
|
||||
import { Heading } from './Heading';
|
||||
import { Icon } from './Icon';
|
||||
import { Text } from './Text';
|
||||
import { Stack } from './Stack';
|
||||
import { Image } from './Image';
|
||||
import { PlaceholderImage } from './PlaceholderImage';
|
||||
import { Calendar as LucideCalendar, ChevronRight as LucideChevronRight, Users as LucideUsers } from 'lucide-react';
|
||||
@@ -60,13 +61,13 @@ export function LeagueCard({
|
||||
<Box
|
||||
position="relative"
|
||||
h="full"
|
||||
rounded="xl"
|
||||
bg="bg-iron-gray"
|
||||
rounded="none"
|
||||
bg="panel-gray/40"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/50"
|
||||
overflow="hidden"
|
||||
transition
|
||||
className="hover:border-primary-blue/50 hover:shadow-[0_0_30px_rgba(25,140,255,0.15)] hover:bg-iron-gray/80"
|
||||
className="hover:border-primary-accent/30 hover:bg-panel-gray/60 transition-all duration-300"
|
||||
>
|
||||
{/* Cover Image */}
|
||||
<Box position="relative" h="32" overflow="hidden">
|
||||
@@ -76,10 +77,10 @@ export function LeagueCard({
|
||||
fullWidth
|
||||
fullHeight
|
||||
objectFit="cover"
|
||||
className="transition-transform duration-300 group-hover:scale-105"
|
||||
className="transition-transform duration-500 group-hover:scale-105 opacity-60"
|
||||
/>
|
||||
{/* Gradient Overlay */}
|
||||
<Box position="absolute" inset="0" bg="bg-gradient-to-t from-iron-gray via-iron-gray/60 to-transparent" />
|
||||
<Box position="absolute" inset="0" bg="linear-gradient(to top, #0C0D0F, transparent)" />
|
||||
|
||||
{/* Badges - Top Left */}
|
||||
<Box position="absolute" top="3" left="3" display="flex" alignItems="center" gap={2}>
|
||||
@@ -93,7 +94,7 @@ export function LeagueCard({
|
||||
|
||||
{/* Logo */}
|
||||
<Box position="absolute" left="4" bottom="-6" zIndex={10}>
|
||||
<Box w="12" h="12" rounded="lg" overflow="hidden" border borderColor="border-iron-gray" bg="bg-deep-graphite" shadow="xl">
|
||||
<Box w="12" h="12" rounded="none" overflow="hidden" border borderColor="border-gray/50" bg="graphite-black" shadow="xl">
|
||||
{logoUrl ? (
|
||||
<Image
|
||||
src={logoUrl}
|
||||
@@ -114,34 +115,37 @@ export function LeagueCard({
|
||||
{/* Content */}
|
||||
<Box pt={8} px={4} pb={4} display="flex" flexDirection="col" fullHeight>
|
||||
{/* Title & Description */}
|
||||
<Heading level={3} className="line-clamp-1 group-hover:text-primary-blue transition-colors" mb={1}>
|
||||
{name}
|
||||
</Heading>
|
||||
<Text size="xs" color="text-gray-500" lineClamp={2} mb={3} style={{ height: '2rem' }} block>
|
||||
<Stack direction="row" align="center" gap={2} mb={1}>
|
||||
<Box w="1" h="4" bg="primary-accent" />
|
||||
<Heading level={3} fontSize="lg" weight="bold" className="line-clamp-1 group-hover:text-primary-accent transition-colors tracking-tight">
|
||||
{name}
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Text size="xs" color="text-gray-500" lineClamp={2} mb={4} style={{ height: '2.5rem' }} block leading="relaxed">
|
||||
{description || 'No description available'}
|
||||
</Text>
|
||||
|
||||
{/* Stats Row */}
|
||||
<Box display="flex" alignItems="center" gap={3} mb={3}>
|
||||
<Box display="flex" alignItems="center" gap={3} mb={4}>
|
||||
{/* Primary Slots (Drivers/Teams/Nations) */}
|
||||
<Box flexGrow={1}>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={1}>
|
||||
<Text size="xs" color="text-gray-500">{slotLabel}</Text>
|
||||
<Text size="xs" color="text-gray-400">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" mb={1.5}>
|
||||
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">{slotLabel}</Text>
|
||||
<Text size="xs" color="text-gray-400" font="mono">
|
||||
{usedSlots}/{maxSlots || '∞'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box h="1.5" rounded="full" bg="bg-charcoal-outline" overflow="hidden">
|
||||
<Box h="1" rounded="none" bg="border-gray/30" overflow="hidden">
|
||||
<Box
|
||||
h="full"
|
||||
rounded="full"
|
||||
rounded="none"
|
||||
transition
|
||||
bg={
|
||||
fillPercentage >= 90
|
||||
? 'bg-warning-amber'
|
||||
? 'warning-amber'
|
||||
: fillPercentage >= 70
|
||||
? 'bg-primary-blue'
|
||||
: 'bg-performance-green'
|
||||
? 'primary-accent'
|
||||
: 'success-green'
|
||||
}
|
||||
style={{ width: `${Math.min(fillPercentage, 100)}%` }}
|
||||
/>
|
||||
@@ -150,35 +154,25 @@ export function LeagueCard({
|
||||
|
||||
{/* Open Slots Badge */}
|
||||
{hasOpenSlots && (
|
||||
<Box display="flex" alignItems="center" gap={1} px={2} py={1} rounded="lg" bg="bg-neon-aqua/10" border borderColor="border-neon-aqua/20">
|
||||
<Box w="1.5" h="1.5" rounded="full" bg="bg-neon-aqua" animate="pulse" />
|
||||
<Text size="xs" color="text-neon-aqua" weight="medium">
|
||||
{openSlotsCount} open
|
||||
<Box display="flex" alignItems="center" gap={1.5} px={2} py={1} rounded="none" bg="primary-accent/5" border borderColor="primary-accent/20">
|
||||
<Box w="1.5" h="1.5" rounded="full" bg="primary-accent" className="animate-pulse" />
|
||||
<Text size="xs" color="text-primary-accent" weight="bold" className="uppercase tracking-tighter">
|
||||
{openSlotsCount} OPEN
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Driver count for team leagues */}
|
||||
{isTeamLeague && (
|
||||
<Box display="flex" alignItems="center" gap={2} mb={3}>
|
||||
<Icon icon={LucideUsers} size={3} color="text-gray-500" />
|
||||
<Text size="xs" color="text-gray-500">
|
||||
{usedDriverSlots ?? 0}/{maxDrivers ?? '∞'} drivers
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Spacer to push footer to bottom */}
|
||||
<Box flexGrow={1} />
|
||||
|
||||
{/* Footer Info */}
|
||||
<Box display="flex" alignItems="center" justifyContent="between" pt={3} borderTop style={{ borderColor: 'rgba(38, 38, 38, 0.5)' }} mt="auto">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" pt={3} borderTop borderColor="border-gray/30" mt="auto">
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
{timingSummary && (
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Icon icon={LucideCalendar} size={3} color="text-gray-500" />
|
||||
<Text size="xs" color="text-gray-500">
|
||||
<Text size="xs" color="text-gray-500" font="mono">
|
||||
{timingSummary.split('•')[1]?.trim() || timingSummary}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -186,8 +180,8 @@ export function LeagueCard({
|
||||
</Box>
|
||||
|
||||
{/* View Arrow */}
|
||||
<Box display="flex" alignItems="center" gap={1} className="group-hover:text-primary-blue transition-colors">
|
||||
<Text size="xs" color="text-gray-500">View</Text>
|
||||
<Box display="flex" alignItems="center" gap={1} className="group-hover:text-primary-accent transition-colors">
|
||||
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">VIEW</Text>
|
||||
<Icon icon={LucideChevronRight} size={3} color="text-gray-500" className="transition-transform group-hover:translate-x-0.5" />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -34,8 +34,8 @@ export function Link({
|
||||
const baseClasses = 'inline-flex items-center transition-colors';
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'text-primary-blue hover:text-primary-blue/80',
|
||||
secondary: 'text-purple-300 hover:text-purple-400',
|
||||
primary: 'text-primary-accent hover:text-primary-accent/80',
|
||||
secondary: 'text-telemetry-aqua hover:text-telemetry-aqua/80',
|
||||
ghost: 'text-gray-400 hover:text-gray-300'
|
||||
};
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ interface MainContentProps {
|
||||
}
|
||||
|
||||
export function MainContent({ children }: MainContentProps) {
|
||||
return <div className="pt-16">{children}</div>;
|
||||
return <div className="pt-16 md:pt-20">{children}</div>;
|
||||
}
|
||||
@@ -28,10 +28,10 @@ export function Section({
|
||||
}: SectionProps) {
|
||||
const variantClasses = {
|
||||
default: '',
|
||||
card: 'bg-iron-gray rounded-lg p-6 border border-charcoal-outline',
|
||||
highlight: 'bg-gradient-to-r from-blue-900/20 to-blue-700/10 rounded-lg p-6 border border-blue-500/30',
|
||||
dark: 'bg-iron-gray',
|
||||
light: 'bg-charcoal-outline'
|
||||
card: 'bg-panel-gray rounded-none p-6 border border-border-gray',
|
||||
highlight: 'bg-gradient-to-r from-primary-accent/10 to-transparent rounded-none p-6 border border-primary-accent/30',
|
||||
dark: 'bg-graphite-black',
|
||||
light: 'bg-panel-gray'
|
||||
};
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -17,7 +17,7 @@ export function Surface<T extends ElementType = 'div'>({
|
||||
as,
|
||||
children,
|
||||
variant = 'default',
|
||||
rounded = 'lg',
|
||||
rounded = 'none',
|
||||
border = false,
|
||||
padding = 0,
|
||||
className = '',
|
||||
@@ -26,16 +26,16 @@ export function Surface<T extends ElementType = 'div'>({
|
||||
...props
|
||||
}: SurfaceProps<T> & ComponentPropsWithoutRef<T>) {
|
||||
const variantClasses: Record<string, string> = {
|
||||
default: 'bg-iron-gray',
|
||||
muted: 'bg-iron-gray/50',
|
||||
dark: 'bg-deep-graphite',
|
||||
glass: 'bg-deep-graphite/60 backdrop-blur-md',
|
||||
'gradient-blue': 'bg-gradient-to-br from-primary-blue/20 via-iron-gray/80 to-deep-graphite',
|
||||
'gradient-gold': 'bg-gradient-to-br from-yellow-600/20 via-iron-gray/80 to-deep-graphite',
|
||||
'gradient-purple': 'bg-gradient-to-br from-purple-600/20 via-iron-gray/80 to-deep-graphite',
|
||||
'gradient-green': 'bg-gradient-to-br from-green-600/20 via-iron-gray/80 to-deep-graphite',
|
||||
'discord': 'bg-gradient-to-b from-deep-graphite to-iron-gray',
|
||||
'discord-inner': 'bg-gradient-to-br from-iron-gray via-deep-graphite to-iron-gray'
|
||||
default: 'bg-panel-gray',
|
||||
muted: 'bg-panel-gray/40',
|
||||
dark: 'bg-graphite-black',
|
||||
glass: 'bg-graphite-black/60 backdrop-blur-md',
|
||||
'gradient-blue': 'bg-gradient-to-br from-primary-accent/10 via-panel-gray/80 to-graphite-black',
|
||||
'gradient-gold': 'bg-gradient-to-br from-warning-amber/10 via-panel-gray/80 to-graphite-black',
|
||||
'gradient-purple': 'bg-gradient-to-br from-purple-600/10 via-panel-gray/80 to-graphite-black',
|
||||
'gradient-green': 'bg-gradient-to-br from-success-green/10 via-panel-gray/80 to-graphite-black',
|
||||
'discord': 'bg-gradient-to-b from-graphite-black to-panel-gray',
|
||||
'discord-inner': 'bg-gradient-to-br from-panel-gray via-graphite-black to-panel-gray'
|
||||
};
|
||||
|
||||
const shadowClasses: Record<string, string> = {
|
||||
@@ -72,7 +72,7 @@ export function Surface<T extends ElementType = 'div'>({
|
||||
const classes = [
|
||||
variantClasses[variant],
|
||||
roundedClasses[rounded],
|
||||
border ? 'border border-charcoal-outline' : '',
|
||||
border ? 'border border-border-gray' : '',
|
||||
paddingClasses[padding] || 'p-0',
|
||||
shadowClasses[shadow],
|
||||
display ? display : '',
|
||||
|
||||
@@ -8,8 +8,8 @@ interface TableProps extends HTMLAttributes<HTMLTableElement> {
|
||||
|
||||
export function Table({ children, className = '', ...props }: TableProps) {
|
||||
return (
|
||||
<Box overflow="auto">
|
||||
<table className={`w-full ${className}`} {...props}>
|
||||
<Box overflow="auto" className="border border-border-gray rounded-sm">
|
||||
<table className={`w-full border-collapse text-left ${className}`} {...props}>
|
||||
{children}
|
||||
</table>
|
||||
</Box>
|
||||
@@ -22,7 +22,7 @@ interface TableHeadProps extends HTMLAttributes<HTMLTableSectionElement> {
|
||||
|
||||
export function TableHead({ children, ...props }: TableHeadProps) {
|
||||
return (
|
||||
<thead {...props}>
|
||||
<thead className="bg-graphite-black border-b border-border-gray" {...props}>
|
||||
{children}
|
||||
</thead>
|
||||
);
|
||||
@@ -34,7 +34,7 @@ interface TableBodyProps extends HTMLAttributes<HTMLTableSectionElement> {
|
||||
|
||||
export function TableBody({ children, ...props }: TableBodyProps) {
|
||||
return (
|
||||
<tbody {...props}>
|
||||
<tbody className="divide-y divide-border-gray/50" {...props}>
|
||||
{children}
|
||||
</tbody>
|
||||
);
|
||||
@@ -47,8 +47,8 @@ interface TableRowProps extends BoxProps<'tr'> {
|
||||
}
|
||||
|
||||
export function TableRow({ children, className = '', clickable = false, variant = 'default', ...props }: TableRowProps) {
|
||||
const baseClasses = 'border-b border-charcoal-outline/50 transition-colors';
|
||||
const variantClasses = variant === 'highlight' ? 'bg-primary-blue/5' : '';
|
||||
const baseClasses = 'transition-colors duration-150 ease-smooth';
|
||||
const variantClasses = variant === 'highlight' ? 'bg-primary-accent/5' : 'hover:bg-white/[0.02]';
|
||||
const classes = [
|
||||
baseClasses,
|
||||
variantClasses,
|
||||
@@ -68,7 +68,7 @@ interface TableHeaderProps extends BoxProps<'th'> {
|
||||
}
|
||||
|
||||
export function TableHeader({ children, className = '', ...props }: TableHeaderProps) {
|
||||
const baseClasses = 'py-3 px-4 text-xs font-medium text-gray-400 uppercase';
|
||||
const baseClasses = 'py-2.5 px-4 text-[11px] font-bold text-gray-500 uppercase tracking-wider';
|
||||
const classes = [baseClasses, className].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
@@ -83,7 +83,7 @@ interface TableCellProps extends BoxProps<'td'> {
|
||||
}
|
||||
|
||||
export function TableCell({ children, className = '', ...props }: TableCellProps) {
|
||||
const baseClasses = 'py-3 px-4';
|
||||
const baseClasses = 'py-3 px-4 text-sm text-gray-300';
|
||||
const classes = [baseClasses, className].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
|
||||
@@ -47,53 +47,55 @@ export function TeamCard({
|
||||
onClick,
|
||||
}: TeamCardProps) {
|
||||
return (
|
||||
<Box onClick={onClick} h="full" cursor={onClick ? 'pointer' : 'default'}>
|
||||
<Card h="full" p={0} display="flex" flexDirection="col" overflow="hidden">
|
||||
<Box onClick={onClick} h="full" cursor={onClick ? 'pointer' : 'default'} className="group">
|
||||
<Card h="full" p={0} display="flex" flexDirection="col" overflow="hidden" className="bg-panel-gray/40 border-border-gray/50 hover:border-primary-accent/30 hover:bg-panel-gray/60 transition-all duration-300">
|
||||
{/* Header with Logo */}
|
||||
<Box p={4} pb={0}>
|
||||
<Box p={5} pb={0}>
|
||||
<Stack direction="row" align="start" gap={4}>
|
||||
{/* Logo */}
|
||||
<Box
|
||||
w="14"
|
||||
h="14"
|
||||
rounded="lg"
|
||||
bg="bg-deep-graphite"
|
||||
w="16"
|
||||
h="16"
|
||||
rounded="none"
|
||||
bg="graphite-black"
|
||||
display="flex"
|
||||
center
|
||||
overflow="hidden"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
borderColor="border-gray/50"
|
||||
className="relative"
|
||||
>
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={name}
|
||||
width={56}
|
||||
height={56}
|
||||
width={64}
|
||||
height={64}
|
||||
fullWidth
|
||||
fullHeight
|
||||
objectFit="cover"
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderImage size={56} />
|
||||
<PlaceholderImage size={64} />
|
||||
)}
|
||||
<Box position="absolute" top="-1px" left="-1px" w="2" h="2" borderTop borderLeft borderColor="primary-accent/30" />
|
||||
</Box>
|
||||
|
||||
{/* Title & Badges */}
|
||||
<Box flexGrow={1} minWidth="0">
|
||||
<Stack direction="row" align="start" justify="between" gap={2}>
|
||||
<Heading level={4}>
|
||||
<Heading level={4} weight="bold" fontSize="lg" className="tracking-tight group-hover:text-primary-accent transition-colors">
|
||||
{name}
|
||||
</Heading>
|
||||
{isRecruiting && (
|
||||
<Badge variant="success" icon={UserPlus}>
|
||||
Recruiting
|
||||
<Badge variant="success" size="xs">
|
||||
RECRUITING
|
||||
</Badge>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{/* Performance Level & Category */}
|
||||
<Stack direction="row" align="center" gap={2} wrap mt={1.5}>
|
||||
<Stack direction="row" align="center" gap={2} wrap mt={2}>
|
||||
{performanceBadge}
|
||||
{specializationContent}
|
||||
{categoryBadge}
|
||||
@@ -103,48 +105,43 @@ export function TeamCard({
|
||||
</Box>
|
||||
|
||||
{/* Content */}
|
||||
<Box p={4} display="flex" flexDirection="col" flexGrow={1}>
|
||||
<Box p={5} display="flex" flexDirection="col" flexGrow={1}>
|
||||
{/* Description */}
|
||||
<Text
|
||||
size="xs"
|
||||
color="text-gray-500"
|
||||
mb={3}
|
||||
mb={4}
|
||||
lineClamp={2}
|
||||
block
|
||||
leading="relaxed"
|
||||
style={{ height: '2.5rem' }}
|
||||
>
|
||||
{description || 'No description available'}
|
||||
</Text>
|
||||
|
||||
{/* Region & Languages */}
|
||||
{(region || languagesContent) && (
|
||||
<Stack direction="row" align="center" gap={2} wrap mb={3}>
|
||||
<Stack direction="row" align="center" gap={2} wrap mb={4}>
|
||||
{region && (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1.5}
|
||||
gap={2}
|
||||
px={2}
|
||||
py={1}
|
||||
rounded="md"
|
||||
bg="bg-iron-gray/50"
|
||||
rounded="none"
|
||||
bg="panel-gray/20"
|
||||
border
|
||||
style={{ borderColor: 'rgba(38, 38, 38, 0.3)' }}
|
||||
borderColor="border-gray/30"
|
||||
>
|
||||
<Icon icon={Globe} size={3} color="var(--neon-aqua)" />
|
||||
<Text size="xs" color="text-gray-400">{region}</Text>
|
||||
<Icon icon={Globe} size={3} color="text-primary-accent" />
|
||||
<Text size="xs" color="text-gray-400" weight="bold" className="uppercase tracking-widest">{region}</Text>
|
||||
</Box>
|
||||
)}
|
||||
{languagesContent}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Stats Grid */}
|
||||
{statsContent && (
|
||||
<Box display="grid" gridCols={3} gap={2} mb={4}>
|
||||
{statsContent}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Spacer */}
|
||||
<Box flexGrow={1} />
|
||||
|
||||
@@ -153,21 +150,21 @@ export function TeamCard({
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
pt={3}
|
||||
pt={4}
|
||||
borderTop
|
||||
style={{ borderColor: 'rgba(38, 38, 38, 0.5)' }}
|
||||
borderColor="border-gray/30"
|
||||
mt="auto"
|
||||
>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Users} size={3} color="var(--text-gray-600)" />
|
||||
<Text size="xs" color="text-gray-500">
|
||||
{memberCount} {memberCount === 1 ? 'member' : 'members'}
|
||||
<Icon icon={Users} size={3} color="text-gray-500" />
|
||||
<Text size="xs" color="text-gray-500" font="mono">
|
||||
{memberCount} {memberCount === 1 ? 'MEMBER' : 'MEMBERS'}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" align="center" gap={1}>
|
||||
<Text size="xs" color="text-gray-500">View</Text>
|
||||
<Icon icon={ChevronRight} size={3} color="var(--text-gray-600)" />
|
||||
<Stack direction="row" align="center" gap={1} className="group-hover:text-primary-accent transition-colors">
|
||||
<Text size="xs" color="text-gray-500" weight="bold" className="uppercase tracking-widest">VIEW</Text>
|
||||
<Icon icon={ChevronRight} size={3} color="text-gray-500" className="transition-transform group-hover:translate-x-0.5" />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
43
apps/website/ui/TelemetryLine.tsx
Normal file
43
apps/website/ui/TelemetryLine.tsx
Normal 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`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -24,30 +24,35 @@ export function UpcomingRaceItem({
|
||||
return (
|
||||
<Surface
|
||||
variant="muted"
|
||||
padding={3}
|
||||
rounded="lg"
|
||||
padding={4}
|
||||
rounded="none"
|
||||
border
|
||||
borderColor="border-gray/30"
|
||||
className="hover:border-primary-accent/30 transition-colors bg-panel-gray/20 group"
|
||||
>
|
||||
<Text color="text-white" weight="medium" block>
|
||||
{track}
|
||||
</Text>
|
||||
<Text size="sm" color="text-gray-400" block>
|
||||
{car}
|
||||
</Text>
|
||||
<Stack direction="row" align="center" gap={2} mt={1}>
|
||||
<Text size="xs" color="text-gray-500">
|
||||
{formattedDate}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">
|
||||
•
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">
|
||||
{formattedTime}
|
||||
</Text>
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box w="1" h="8" bg="primary-accent" opacity={0.3} className="group-hover:opacity-100 transition-opacity" />
|
||||
<Box flexGrow={1}>
|
||||
<Text color="text-white" weight="bold" block className="tracking-tight">
|
||||
{track}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block weight="medium" className="uppercase tracking-widest mt-0.5">
|
||||
{car}
|
||||
</Text>
|
||||
</Box>
|
||||
<Stack align="end" gap={1}>
|
||||
<Text size="xs" color="text-gray-400" font="mono" weight="bold">
|
||||
{formattedDate}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-600" font="mono">
|
||||
{formattedTime}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{isMyLeague && (
|
||||
<Box mt={1}>
|
||||
<Badge variant="success">
|
||||
Your League
|
||||
<Box mt={3} display="flex" justifyContent="end">
|
||||
<Badge variant="success" size="xs">
|
||||
YOUR LEAGUE
|
||||
</Badge>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
199
docs/THEME.md
199
docs/THEME.md
@@ -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 engineer’s dashboard — not a game launcher, not corporate SaaS.
|
||||
|
||||
---
|
||||
⸻
|
||||
|
||||
## 1. Design Philosophy
|
||||
1. Identity & Mood
|
||||
|
||||
GridPilot should feel like:
|
||||
- **race control software**, not a game UI
|
||||
- **infrastructure**, not a startup product
|
||||
- **engineered**, not styled
|
||||
- **stable and authoritative**, yet pleasant to use
|
||||
- **built for years**, not trends
|
||||
The theme should communicate:
|
||||
• Precision (like telemetry screens)
|
||||
• Calm intensity (the feeling before a qualifying lap)
|
||||
• Authority without ego (used by serious racers)
|
||||
• Modern minimalism (dev-friendly, clean structure)
|
||||
• Soft gaming hints (subtle neon touches, not RGB vomit)
|
||||
|
||||
The goal is not excitement.
|
||||
The goal is **confidence**.
|
||||
It appeals equally to:
|
||||
• Devs (clean, modular, readable components)
|
||||
• Gamers (immersive, familiar atmosphere)
|
||||
• Simracers (motorsport seriousness)
|
||||
|
||||
Think:
|
||||
**“FIA race control x timing screens x modern tooling — with smooth interaction.”**
|
||||
⸻
|
||||
|
||||
---
|
||||
2. Visual Language
|
||||
|
||||
## 2. Visual Style
|
||||
Primary Look
|
||||
• Dark, matte surfaces
|
||||
• Thin, crisp separators
|
||||
• Soft glow accents in blue / cyan / purple
|
||||
• Avoid aggressive neon or gamer-overload
|
||||
• Subtle gradient tints (barely visible)
|
||||
|
||||
### Core Aesthetic:
|
||||
- dark, neutral background
|
||||
- minimal gradients (almost invisible)
|
||||
- restrained highlights only for meaning
|
||||
- strict hierarchy
|
||||
- dense, readable layouts
|
||||
- smooth transitions only where state changes
|
||||
Color System
|
||||
• Base: Near-black graphite (#0C0D0F)
|
||||
• Surface: Deep charcoal (#141619)
|
||||
• Outline: Soft steel grey (#23272B)
|
||||
• Primary Accent: Electric blue (#198CFF)
|
||||
• Telemetry Accent: Aqua (#4ED4E0)
|
||||
• Warning: Motorsport amber (#FFBE4D)
|
||||
|
||||
Everything should look:
|
||||
**intentional, measured, and calm.**
|
||||
Everything should feel instrument-grade, not decorative.
|
||||
|
||||
---
|
||||
⸻
|
||||
|
||||
### Color Palette (refined)
|
||||
3. Component Philosophy
|
||||
|
||||
- **Graphite Black:** `#0E0F11`
|
||||
- **Panel Gray:** `#171A1E`
|
||||
- **Border Gray:** `#22262A`
|
||||
- **Primary Accent:** `#198CFF` (used sparingly)
|
||||
- **Success Green:** `#6FE37A`
|
||||
- **Warning Amber:** `#FFC556`
|
||||
- **Critical Red:** `#E35C5C`
|
||||
Cards
|
||||
• Functional over flashy
|
||||
• Slight bevel or inset shadow (hint of cockpit panels)
|
||||
• Dense info, clean hierarchy
|
||||
|
||||
No neon.
|
||||
No playful colors.
|
||||
Color = meaning, not decoration.
|
||||
Tables
|
||||
• High-density
|
||||
• Thin row dividers
|
||||
• Highlight on hover only
|
||||
• Telemetry-coded status colors
|
||||
|
||||
---
|
||||
Buttons
|
||||
• Flat by default
|
||||
• Glow + gradient only on hover
|
||||
• Snappy micro-animation (motorsport feedback)
|
||||
|
||||
## 3. Motion & Interaction
|
||||
Modals
|
||||
• Soft frosted blur
|
||||
• Fast open/close animation
|
||||
• Dimmed pit-lane-lighting vibe
|
||||
|
||||
### Animation Philosophy
|
||||
Motion exists only to:
|
||||
- confirm an action
|
||||
- show hierarchy
|
||||
- indicate state change
|
||||
⸻
|
||||
|
||||
Never to impress.
|
||||
4. Motion & Feedback
|
||||
|
||||
### Characteristics:
|
||||
- short durations (120–200ms)
|
||||
- low amplitude
|
||||
- no exaggerated easing
|
||||
- no elastic bounces
|
||||
- no decorative movement
|
||||
Racing-inspired, but minimal:
|
||||
• Short accelerations (fast ease-out)
|
||||
• No bounces — this isn’t a game store
|
||||
• Hover = slight lift + color pulse
|
||||
• Loading = progress line (like pit limiter light)
|
||||
• Switching tabs = sliding underline (chicane motion)
|
||||
|
||||
**Motion should feel like a well-damped suspension — not a show car.**
|
||||
Devs should feel the UI is responsive, not playful.
|
||||
|
||||
---
|
||||
⸻
|
||||
|
||||
## 4. Where Motion Is Allowed
|
||||
5. Layout Structure
|
||||
|
||||
- button press feedback
|
||||
- panel open / close
|
||||
- table row expansion
|
||||
- state changes (pending → approved → completed)
|
||||
- subtle loading indicators
|
||||
Think “Telemetry Workspace”:
|
||||
• Sidebar = dashboard rail
|
||||
• Header = control bar
|
||||
• Content = track map / data table zone
|
||||
• Right panel = session or context info
|
||||
|
||||
If motion does not improve clarity → remove it.
|
||||
Everything modular, easily replaceable, easy to reason about.
|
||||
|
||||
---
|
||||
⸻
|
||||
|
||||
## 5. What We Explicitly Avoid
|
||||
6. Tone for Dev-Facing Text
|
||||
• Short
|
||||
• Neutral
|
||||
• Calm
|
||||
• Technical
|
||||
• Real-world language (not corporate slang)
|
||||
|
||||
- hero animations
|
||||
- animated backgrounds
|
||||
- glowing UI chrome
|
||||
- playful hover gimmicks
|
||||
- “app store” aesthetics
|
||||
- anything that reduces trust
|
||||
Examples:
|
||||
• “Add race”
|
||||
• “Review protest”
|
||||
• “Session updated”
|
||||
• “Export standings”
|
||||
• “Sponsor payout queued”
|
||||
|
||||
GridPilot must feel:
|
||||
**reliable before it feels beautiful.**
|
||||
No hype. No marketing tone inside the app.
|
||||
|
||||
---
|
||||
⸻
|
||||
|
||||
## 6. Components
|
||||
7. Emotional Target
|
||||
|
||||
### Tables
|
||||
- primary UI element
|
||||
- dense, readable
|
||||
- fixed column logic
|
||||
- no playful effects
|
||||
Users should feel:
|
||||
• In control
|
||||
• Supported
|
||||
• Efficient
|
||||
• Connected to motorsport culture
|
||||
• Trusting (nothing looks cheap or gimmicky)
|
||||
|
||||
### Cards
|
||||
- functional grouping
|
||||
- no visual dominance
|
||||
- secondary to tables
|
||||
It’s basically:
|
||||
|
||||
### Modals
|
||||
- simple
|
||||
- fast
|
||||
- decisive
|
||||
|
||||
---
|
||||
|
||||
## 7. Typography
|
||||
|
||||
- neutral sans-serif
|
||||
- excellent numeric readability
|
||||
- no personality fonts
|
||||
|
||||
Primary goal:
|
||||
**information clarity, not brand expression.**
|
||||
|
||||
---
|
||||
|
||||
## 8. Design Principle Summary
|
||||
|
||||
GridPilot is:
|
||||
- not gamer UI
|
||||
- not esports branding
|
||||
- not corporate SaaS
|
||||
|
||||
It is:
|
||||
**modern motorsport infrastructure software.**
|
||||
“A premium cockpit dashboard… built by people who race and code.”
|
||||
Reference in New Issue
Block a user