Files
gridpilot.gg/apps/website/templates/HomeTemplate.tsx
2026-01-14 23:31:57 +01:00

277 lines
11 KiB
TypeScript

'use client';
import React from 'react';
import Hero from '@/components/landing/Hero';
import AlternatingSection from '@/components/landing/AlternatingSection';
import FeatureGrid from '@/components/landing/FeatureGrid';
import DiscordCTA from '@/components/landing/DiscordCTA';
import FAQ from '@/components/landing/FAQ';
import Footer from '@/components/landing/Footer';
import CareerProgressionMockup from '@/components/mockups/CareerProgressionMockup';
import RaceHistoryMockup from '@/components/mockups/RaceHistoryMockup';
import CompanionAutomationMockup from '@/components/mockups/CompanionAutomationMockup';
import SimPlatformMockup from '@/components/mockups/SimPlatformMockup';
import MockupStack from '@/ui/MockupStack';
import { Card } from '@/ui/Card';
import { Button } from '@/ui/Button';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Image } from '@/ui/Image';
import { Link } from '@/ui/Link';
import { Container } from '@/ui/Container';
import { Grid } from '@/ui/Grid';
import { Surface } from '@/ui/Surface';
import { getMediaUrl } from '@/lib/utilities/media';
import { routes } from '@/lib/routing/RouteConfig';
import { FeatureItem, ResultItem, StepItem } from '@/components/landing/LandingItems';
export interface HomeViewData {
isAlpha: boolean;
upcomingRaces: Array<{
id: string;
track: string;
car: string;
formattedDate: string;
}>;
topLeagues: Array<{
id: string;
name: string;
description: string;
}>;
teams: Array<{
id: string;
name: string;
description: string;
logoUrl?: string;
}>;
}
interface HomeTemplateProps {
viewData: HomeViewData;
}
export function HomeTemplate({ viewData }: HomeTemplateProps) {
return (
<Box as="main">
<Hero />
{/* 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" />
</Stack>
<Text>
iRacing gives you physics. GridPilot gives you a career.
</Text>
</Stack>
}
mockup={<CareerProgressionMockup />}
layout="text-left"
/>
<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" />
</Stack>
<Text size="sm">
Your racing career, finally in one place.
</Text>
</Stack>
}
mockup={<MockupStack index={1}><RaceHistoryMockup /></MockupStack>}
layout="text-right"
/>
{/* 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" />
</Stack>
<Text size="sm">
Automation instead of repetition.
</Text>
</Stack>
}
mockup={<CompanionAutomationMockup />}
layout="text-left"
/>
{/* 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"
/>
{/* Alpha-only discovery section */}
{viewData.isAlpha && (
<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} style={{ fontSize: '0.875rem' }}>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} style={{ width: '2.5rem', height: '2.5rem', backgroundColor: 'rgba(59, 130, 246, 0.1)', borderColor: 'rgba(59, 130, 246, 0.3)', 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 style={{ 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} style={{ fontSize: '0.875rem' }}>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} style={{ width: '2.5rem', height: '2.5rem', overflow: 'hidden', backgroundColor: '#262626' }}>
<Image
src={team.logoUrl || getMediaUrl('team-logo', team.id)}
alt={team.name}
width={40}
height={40}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Surface>
<Box style={{ 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} style={{ fontSize: '0.875rem' }}>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 style={{ 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" style={{ whiteSpace: 'nowrap' }}>
{race.formattedDate}
</Text>
</Stack>
</Box>
))}
</Stack>
)}
</Stack>
</Card>
</Grid>
</Stack>
</Container>
)}
<DiscordCTA />
<FAQ />
<Footer />
</Box>
);
}