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
@@ -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>
);
}
@@ -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>
);
}
+65
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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}