website refactor

This commit is contained in:
2026-01-20 01:22:05 +01:00
parent f8e7ec7948
commit 30a31dc44f
21 changed files with 1242 additions and 393 deletions

View File

@@ -0,0 +1,50 @@
'use client';
import React from 'react';
import { Section } from '@/ui/Section';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Group } from '@/ui/Group';
import { Box } from '@/ui/Box';
import { Panel } from '@/ui/Panel';
import { Stack } from '@/ui/Stack';
import { Trophy } from 'lucide-react';
/**
* CtaSection - Final call to action for league admins.
* Redesigned to match the "Modern Precision" theme and "Dieter Rams" style.
*/
export function CtaSection() {
return (
<Section variant="default" py={32}>
<Panel variant="muted" padding="xl">
<Stack gap={12} align="center" textAlign="center">
<Box>
<Box marginBottom={6} display="flex" justifyContent="center">
<Box width="3rem" height="3rem" bg="var(--ui-color-bg-surface)" rounded="full" border={true} display="flex" alignItems="center" justifyContent="center">
<Trophy size={24} className="text-[var(--ui-color-intent-primary)]" />
</Box>
</Box>
<Heading level={2} weight="bold" marginBottom={4}>
Ready to elevate your league?
</Heading>
<Text variant="med" size="lg" maxWidth="36rem" mx="auto">
Join the growing ecosystem of professional sim racing leagues.
Start for free and scale as your community grows.
</Text>
</Box>
<Group gap={6} justify="center">
<Button variant="primary" size="lg">
Create Your League
</Button>
<Button variant="secondary" size="lg">
View Documentation
</Button>
</Group>
</Stack>
</Panel>
</Section>
);
}

View File

@@ -0,0 +1,96 @@
'use client';
import React from 'react';
import { Section } from '@/ui/Section';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Card } from '@/ui/Card';
import { Button } from '@/ui/Button';
import { FeatureGrid } from '@/ui/FeatureGrid';
import { Stack } from '@/ui/Stack';
import { Trophy, Users, Flag } from 'lucide-react';
interface EcosystemGridProps {
leagues: Array<{ id: string; name: string; description: string }>;
teams: Array<{ id: string; name: string; description: string }>;
races: Array<{ id: string; track: string; car: string; formattedDate: string }>;
}
/**
* EcosystemGrid - Discovery section for the live ecosystem.
* Designed with a "grid" layout and precision details.
* Uses ONLY UI components.
*/
export function EcosystemGrid({ leagues, teams, races }: EcosystemGridProps) {
return (
<Section variant="muted" padding="lg">
<Stack direction="col" gap={16}>
<Stack direction={{ base: 'col', md: 'row' }} align="end" justify="between" gap={8}>
<Stack direction="col" gap={4} maxWidth="42rem">
<Text variant="primary" weight="bold" uppercase size="xs">
Live Ecosystem
</Text>
<Heading level={2} weight="bold">
THE GRID
</Heading>
<Text size="lg" variant="med" block>
Explore the leagues, teams, and races that define the GridPilot ecosystem.
</Text>
</Stack>
<Button variant="secondary" size="md">
VIEW ALL DATA
</Button>
</Stack>
<FeatureGrid columns={{ base: 1, lg: 3 }} gap={8}>
{/* Leagues Column */}
<Stack direction="col" gap={6}>
<Stack direction="row" align="center" gap={2} px={2}>
<Trophy className="w-4 h-4 text-[var(--ui-color-text-low)]" />
<Text size="sm" weight="bold" uppercase variant="low">Top Leagues</Text>
</Stack>
{leagues.slice(0, 3).map((league) => (
<Card key={league.id} variant="default" padding="md">
<Heading level={5} weight="bold">{league.name}</Heading>
<Text size="sm" variant="low" leading="snug" block mt={2}>{league.description}</Text>
</Card>
))}
</Stack>
{/* Teams Column */}
<Stack direction="col" gap={6}>
<Stack direction="row" align="center" gap={2} px={2}>
<Users className="w-4 h-4 text-[var(--ui-color-text-low)]" />
<Text size="sm" weight="bold" uppercase variant="low">Active Teams</Text>
</Stack>
{teams.slice(0, 3).map((team) => (
<Card key={team.id} variant="default" padding="md">
<Heading level={5} weight="bold">{team.name}</Heading>
<Text size="sm" variant="low" leading="snug" block mt={2}>{team.description}</Text>
</Card>
))}
</Stack>
{/* Races Column */}
<Stack direction="col" gap={6}>
<Stack direction="row" align="center" gap={2} px={2}>
<Flag className="w-4 h-4 text-[var(--ui-color-text-low)]" />
<Text size="sm" weight="bold" uppercase variant="low">Upcoming Races</Text>
</Stack>
{races.slice(0, 3).map((race) => (
<Card key={race.id} variant="default" padding="md">
<Stack direction="row" justify="between" align="start">
<Stack direction="col" gap={1}>
<Heading level={5} weight="bold">{race.track}</Heading>
<Text size="xs" variant="primary" weight="bold" uppercase>{race.car}</Text>
</Stack>
<Text size="xs" variant="low" mono>{race.formattedDate}</Text>
</Stack>
</Card>
))}
</Stack>
</FeatureGrid>
</Stack>
</Section>
);
}

View File

@@ -0,0 +1,65 @@
'use client';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Section } from '@/ui/Section';
import { ButtonGroup } from '@/ui/ButtonGroup';
import { Stack } from '@/ui/Stack';
import { Box } from '@/ui/Box';
/**
* Hero - Refined with Dieter Rams principles.
* "Less, but better."
* Focuses on clarity, honesty, and unobtrusive design.
*/
export function Hero() {
return (
<Section variant="default" py={32}>
<Box maxWidth="54rem">
<Box marginBottom={24}>
<Text
variant="primary"
weight="bold"
uppercase
size="xs"
leading="none"
block
letterSpacing="0.2em"
marginBottom={10}
>
Sim Racing Infrastructure
</Text>
<Heading level={1} weight="bold" size="4xl">
Professional League Management.<br />
Engineered for Control.
</Heading>
</Box>
<Box marginBottom={24}>
<Text size="xl" variant="med" leading="relaxed" block maxWidth="42rem">
GridPilot eliminates the administrative overhead of running iRacing leagues.
No spreadsheets. No manual points. No protest chaos.
Just pure competition, structured for growth.
</Text>
</Box>
<ButtonGroup gap={10}>
<Button
variant="primary"
size="lg"
>
Create Your League
</Button>
<Button
variant="secondary"
size="lg"
>
View Demo
</Button>
</ButtonGroup>
</Box>
</Section>
);
}

View File

@@ -0,0 +1,151 @@
'use client';
import React from 'react';
import { Section } from '@/ui/Section';
import { Panel } from '@/ui/Panel';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Group } from '@/ui/Group';
import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid';
import { Box } from '@/ui/Box';
import { StatusBadge } from '@/ui/StatusBadge';
import { Trophy, Globe, Settings2, Palette, ShieldCheck, BarChart3 } from 'lucide-react';
/**
* LeagueIdentityPreview - Radically redesigned for "Modern Precision" and "Dieter Rams" style.
* Focuses on the professional identity and deep customization options for admins.
*/
export function LeagueIdentityPreview() {
return (
<Section variant="default" py={32}>
<Box>
<Box marginBottom={24} maxWidth="42rem">
<Heading level={2} weight="bold" marginBottom={8}>Your Brand. Your Rules.</Heading>
<Text variant="med" size="lg" leading="relaxed">
GridPilot is designed to be invisible where it matters, letting your league's identity take center stage.
Professional tools that respect your community's unique culture.
</Text>
</Box>
<Grid cols={{ base: 1, md: 2 }} gap={20}>
{/* Your Brand - Visual Identity */}
<Box>
<Panel variant="muted" padding="lg">
<Stack gap={8}>
<Stack gap={2}>
<Group gap={2}>
<Palette size={16} className="text-[var(--ui-color-intent-primary)]" />
<Text variant="primary" weight="bold" uppercase size="xs" letterSpacing="0.1em">Your Brand</Text>
</Group>
<Heading level={3} weight="bold">Professional Presence</Heading>
<Text variant="low" size="sm">Build prestige with a dedicated home for your competition.</Text>
</Stack>
<Stack gap={4}>
<Panel variant="bordered" padding="md">
<Group gap={4}>
<Globe size={20} className="text-[var(--ui-color-text-low)]" />
<Stack gap={1}>
<Text weight="bold">Custom Subdomains</Text>
<Text size="xs" variant="low">yourleague.gridpilot.racing</Text>
</Stack>
</Group>
</Panel>
<Panel variant="bordered" padding="md">
<Group gap={4}>
<Trophy size={20} className="text-[var(--ui-color-text-low)]" />
<Stack gap={1}>
<Text weight="bold">Live Standings</Text>
<Text size="xs" variant="low">Real-time updates across all car classes.</Text>
</Stack>
</Group>
</Panel>
<Panel variant="bordered" padding="md">
<Group gap={4}>
<BarChart3 size={20} className="text-[var(--ui-color-text-low)]" />
<Stack gap={1}>
<Text weight="bold">Driver Roster</Text>
<Text size="xs" variant="low">Detailed profiles with lifetime league stats.</Text>
</Stack>
</Group>
</Panel>
<Panel variant="elevated" padding="sm">
<Group gap={3}>
<Box width="2.5rem" height="2.5rem" bg="var(--ui-color-bg-surface)" rounded="sm" border={true} display="flex" alignItems="center" justifyContent="center">
<Trophy size={20} className="text-[var(--ui-color-text-low)]" />
</Box>
<Stack gap={0}>
<Text weight="bold" size="sm">Apex Racing League</Text>
<Text size="xs" variant="low">apex.gridpilot.racing</Text>
</Stack>
</Group>
</Panel>
</Stack>
</Stack>
</Panel>
</Box>
{/* Your Rules - Deep Customization */}
<Box>
<Panel variant="muted" padding="lg">
<Stack gap={8}>
<Stack gap={2}>
<Group gap={2}>
<Settings2 size={16} className="text-[var(--ui-color-intent-primary)]" />
<Text variant="primary" weight="bold" uppercase size="xs" letterSpacing="0.1em">Your Rules</Text>
</Group>
<Heading level={3} weight="bold">Absolute Control</Heading>
<Text variant="low" size="sm">The platform adapts to your league, not the other way around.</Text>
</Stack>
<Stack gap={4}>
<Panel variant="bordered" padding="md">
<Group gap={4}>
<ShieldCheck size={20} className="text-[var(--ui-color-text-low)]" />
<Stack gap={1}>
<Text weight="bold">Flexible Points Systems</Text>
<Text size="xs" variant="low">Custom points for positions, fastest laps, and incidents.</Text>
</Stack>
</Group>
</Panel>
<Panel variant="bordered" padding="md">
<Group gap={4}>
<Settings2 size={20} className="text-[var(--ui-color-text-low)]" />
<Stack gap={1}>
<Text weight="bold">Drop Weeks & Playoffs</Text>
<Text size="xs" variant="low">Configure complex season structures with ease.</Text>
</Stack>
</Group>
</Panel>
<Panel variant="bordered" padding="md">
<Group gap={4}>
<Trophy size={20} className="text-[var(--ui-color-text-low)]" />
<Stack gap={1}>
<Text weight="bold">Multi-Class Support</Text>
<Text size="xs" variant="low">Manage GT3, GTP, and more in a single season.</Text>
</Stack>
</Group>
</Panel>
<Panel variant="elevated" padding="sm">
<Stack gap={2}>
<Text size="xs" uppercase weight="bold" variant="low">Points Configuration</Text>
<Group gap={2} wrap>
<StatusBadge variant="info">P1: 25pts</StatusBadge>
<StatusBadge variant="info">P2: 18pts</StatusBadge>
<StatusBadge variant="info">P3: 15pts</StatusBadge>
<StatusBadge variant="warning">INC: -1pt</StatusBadge>
</Group>
</Stack>
</Panel>
</Stack>
</Stack>
</Panel>
</Box>
</Grid>
</Box>
</Section>
);
}

View File

@@ -0,0 +1,47 @@
'use client';
import React from 'react';
import { Section } from '@/ui/Section';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Stack } from '@/ui/Stack';
import { Group } from '@/ui/Group';
import { Panel } from '@/ui/Panel';
import { ArrowRight, Database } from 'lucide-react';
import { routes } from '@/lib/routing/RouteConfig';
/**
* MigrationSection - Offers help with migrating existing league data.
*/
export function MigrationSection() {
return (
<Section variant="default" py={32}>
<Panel variant="bordered" padding="xl">
<Group justify="between" align="center" gap={12} wrap>
<Stack gap={6} flex={1} minWidth="20rem">
<Group gap={3}>
<Database size={20} className="text-[var(--ui-color-intent-primary)]" />
<Text variant="primary" weight="bold" uppercase size="xs" letterSpacing="0.1em">League Migration</Text>
</Group>
<Heading level={2} weight="bold">Moving from Sheets or Discord?</Heading>
<Text variant="med" size="lg">
We know that moving years of history is daunting. Send us your data, and we will handle the migration for you for free.
We support CSV, Google Sheets, and manual data entry.
</Text>
</Stack>
<Button
as="a"
href={routes.league.migration}
variant="primary"
size="lg"
icon={<ArrowRight size={18} />}
>
Start Migration
</Button>
</Group>
</Panel>
</Section>
);
}

View File

@@ -0,0 +1,101 @@
'use client';
import React from 'react';
import { Section } from '@/ui/Section';
import { Panel } from '@/ui/Panel';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { StatusBadge } from '@/ui/StatusBadge';
import { Button } from '@/ui/Button';
import { Group } from '@/ui/Group';
import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid';
import { Box } from '@/ui/Box';
import { Gavel, Clock, User, MessageSquare } from 'lucide-react';
/**
* StewardingPreview - Refined for "Modern Precision" and "Dieter Rams" style.
* Thorough down to the last detail.
*/
export function StewardingPreview() {
return (
<Section variant="muted" py={32}>
<Box>
<Box marginBottom={16} maxWidth="42rem">
<Heading level={2} weight="bold" marginBottom={6}>Structured Stewarding</Heading>
<Text variant="med" size="lg" leading="relaxed">
Protests are part of racing. Managing them shouldn't be a second job.
GridPilot provides a dedicated workflow for drivers to report incidents and for you to resolve them with precision.
</Text>
</Box>
<Panel variant="elevated" padding="lg">
<Stack gap={8}>
<Group justify="between" align="center">
<Stack gap={1}>
<Group gap={2}>
<Text variant="low" size="xs" uppercase weight="bold" letterSpacing="0.1em">Incident Report</Text>
<Text variant="low" size="xs"></Text>
<Text variant="low" size="xs" uppercase weight="bold" letterSpacing="0.1em">ID: 402-WG</Text>
</Group>
<Heading level={3} weight="bold">Turn 1 Contact: Miller vs Chen</Heading>
</Stack>
<StatusBadge variant="warning">UNDER REVIEW</StatusBadge>
</Group>
<Grid cols={{ base: 1, md: 3 }} gap={6}>
<Panel variant="bordered" padding="md">
<Stack gap={3}>
<Group gap={2}>
<User size={14} className="text-[var(--ui-color-intent-primary)]" />
<Text size="xs" uppercase weight="bold" variant="low">Protestor</Text>
</Group>
<Text weight="bold">Alex Miller</Text>
<Text size="sm" variant="low">#42 - Porsche 911 GT3 R</Text>
</Stack>
</Panel>
<Panel variant="bordered" padding="md">
<Stack gap={3}>
<Group gap={2}>
<User size={14} className="text-[var(--ui-color-intent-critical)]" />
<Text size="xs" uppercase weight="bold" variant="low">Defendant</Text>
</Group>
<Text weight="bold">David Chen</Text>
<Text size="sm" variant="low">#18 - BMW M4 GT3</Text>
</Stack>
</Panel>
<Panel variant="bordered" padding="md">
<Stack gap={3}>
<Group gap={2}>
<Clock size={14} className="text-[var(--ui-color-text-low)]" />
<Text size="xs" uppercase weight="bold" variant="low">Session Info</Text>
</Group>
<Text weight="bold">Lap 1, 00:42.150</Text>
<Text size="sm" variant="low">Watkins Glen - Cup</Text>
</Stack>
</Panel>
</Grid>
<Stack gap={4}>
<Group gap={2}>
<MessageSquare size={14} className="text-[var(--ui-color-text-low)]" />
<Text size="xs" uppercase weight="bold" variant="low">Description</Text>
</Group>
<Panel variant="muted" padding="md">
<Text size="sm" variant="high" leading="relaxed">
"David missed his braking point into T1 and hit my rear right corner.
I was forced into the grass and lost 4 positions. Replay attached shows he was never alongside."
</Text>
</Panel>
</Stack>
<Group gap={3} justify="end">
<Button variant="secondary" size="md">Dismiss</Button>
<Button variant="primary" size="md" icon={<Gavel size={16} />}>Apply Penalty</Button>
</Group>
</Stack>
</Panel>
</Box>
</Section>
);
}

View File

@@ -0,0 +1,45 @@
'use client';
import React from 'react';
import { StatsStrip } from '@/ui/StatsStrip';
import { Section } from '@/ui/Section';
import { XCircle, CheckCircle2, AlertCircle, ShieldCheck } from 'lucide-react';
/**
* TelemetryStrip - Redesigned as a "Status of Chaos" vs "Status of Order" strip.
* Focuses on the transition from manual work to GridPilot.
*/
export function TelemetryStrip() {
const stats = [
{
label: "NO SPREADSHEETS",
value: "STRUCTURED",
icon: CheckCircle2,
intent: "success" as const
},
{
label: "NO DM PROTESTS",
value: "CENTRALIZED",
icon: ShieldCheck,
intent: "primary" as const
},
{
label: "NO MANUAL POINTS",
value: "AUTOMATED",
icon: CheckCircle2,
intent: "success" as const
},
{
label: "NO ROSTER CHAOS",
value: "MANAGED",
icon: ShieldCheck,
intent: "primary" as const
},
];
return (
<Section variant="default" py={16}>
<StatsStrip stats={stats} />
</Section>
);
}

View File

@@ -0,0 +1,50 @@
'use client';
import React from 'react';
import { Section } from '@/ui/Section';
import { FeatureGrid } from '@/ui/FeatureGrid';
import { FeatureItem } from '@/ui/FeatureItem';
import { Box } from '@/ui/Box';
import { RefreshCw, Gavel, Layout } from 'lucide-react';
/**
* ValuePillars - Redesigned for League Admin features.
* Focuses on solving the core problems of league management.
*/
export function ValuePillars() {
const pillars = [
{
title: "Automatic Results Sync",
description: "Direct iRacing integration. Positions, incidents, and best laps imported instantly. Standings update the moment the race ends.",
icon: RefreshCw,
},
{
title: "Structured Stewarding",
description: "A dedicated review panel for protests. Drivers submit timestamps and clips; you make the call. Points adjust automatically.",
icon: Gavel,
},
{
title: "Professional Presence",
description: "A clean, modern home for your league. Schedules, standings, and rosters that build prestige and attract drivers.",
icon: Layout,
},
];
return (
<Section variant="default" py={32}>
<Box>
<FeatureGrid columns={{ base: 1, md: 3 }} gap={16}>
{pillars.map((pillar) => (
<FeatureItem
key={pillar.title}
title={pillar.title}
description={pillar.description}
icon={pillar.icon}
/>
))}
</FeatureGrid>
</Box>
</Section>
);
}