website refactor

This commit is contained in:
2026-01-19 18:01:30 +01:00
parent 6154d54435
commit 61b5cf3b64
120 changed files with 2226 additions and 2021 deletions

View File

@@ -0,0 +1,57 @@
import React from 'react';
import { Section } from '@/ui/Section';
import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Accordion } from '@/components/shared/Accordion';
import { CardStack } from '@/ui/CardStack';
import { Center } from '@/ui/Center';
interface FAQItem {
question: string;
answer: string;
}
interface FAQSectionProps {
title: string;
subtitle: string;
faqs: FAQItem[];
}
/**
* FAQSection - A semantic component for the FAQ section.
*/
export function FAQSection({
title,
subtitle,
faqs,
}: FAQSectionProps) {
return (
<Section variant="dark" padding="lg">
<Container size="md">
<Center>
<CardStack gap={4}>
<Center>
<Text size="xs" weight="bold" variant="primary" uppercase letterSpacing="widest">
{subtitle}
</Text>
</Center>
<Heading level={2} weight="bold" align="center">
{title}
</Heading>
</CardStack>
</Center>
<CardStack gap={4}>
{faqs.map((faq, index) => (
<Accordion key={index} title={faq.question}>
<Text size="sm" variant="low" leading="relaxed">
{faq.answer}
</Text>
</Accordion>
))}
</CardStack>
</Container>
</Section>
);
}

View File

@@ -1,7 +1,9 @@
'use client';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { CardStack } from '@/ui/CardStack';
import { FeatureList } from '@/ui/FeatureList';
import { FeatureQuote } from '@/ui/FeatureQuote';
interface HomeFeatureDescriptionProps {
lead: string;
@@ -12,7 +14,7 @@ interface HomeFeatureDescriptionProps {
/**
* HomeFeatureDescription - A semantic component for feature descriptions on the home page.
* Refactored to use semantic HTML and Tailwind.
* Refactored to use semantic UI components.
*/
export function HomeFeatureDescription({
lead,
@@ -20,40 +22,21 @@ export function HomeFeatureDescription({
quote,
accentColor = 'primary',
}: HomeFeatureDescriptionProps) {
const borderColor = {
primary: 'primary-accent',
aqua: 'telemetry-aqua',
amber: 'warning-amber',
gray: 'border-gray',
}[accentColor];
const bgColor = {
primary: 'primary-accent/5',
aqua: 'telemetry-aqua/5',
amber: 'warning-amber/5',
gray: 'white/5',
}[accentColor];
const intent = accentColor === 'gray' ? 'low' : accentColor;
return (
<Stack gap={6}>
<Text size="lg" color="text-gray-400" weight="medium" leading="relaxed">
<CardStack gap={6}>
<Text size="lg" variant="med" leading="relaxed">
{lead}
</Text>
<Stack as="ul" gap={2}>
{items.map((item, index) => (
<Stack as="li" key={index} direction="row" align="start" gap={2}>
<Text color="text-primary-accent"></Text>
<Text size="sm" color="text-gray-500">{item}</Text>
</Stack>
))}
</Stack>
<FeatureList items={items} intent={intent} />
{quote && (
<Stack borderLeft borderStyle="solid" borderWidth="2px" borderColor={borderColor} pl={4} py={1} bg={bgColor}>
<Text color="text-gray-600" font="mono" size="xs" uppercase letterSpacing="widest" leading="relaxed">
{quote}
</Text>
</Stack>
<FeatureQuote intent={intent}>
{quote}
</FeatureQuote>
)}
</Stack>
</CardStack>
);
}

View File

@@ -1,13 +1,7 @@
'use client';
import React from 'react';
import { Panel } from '@/ui/Panel';
import { Glow } from '@/ui/Glow';
import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid';
import { Container } from '@/ui/Container';
import { Heading } from '@/ui/Heading';
import { Section } from '@/ui/Section';
import { FeatureSection } from '@/ui/FeatureSection';
interface HomeFeatureSectionProps {
heading: string;
@@ -27,43 +21,13 @@ export function HomeFeatureSection({
layout,
accentColor = 'primary',
}: HomeFeatureSectionProps) {
const glowColor = ({
primary: 'primary',
aqua: 'aqua',
amber: 'amber',
}[accentColor] || 'primary') as 'primary' | 'aqua' | 'amber' | 'purple';
return (
<Section variant="dark" padding="lg">
<Glow
color={glowColor}
size="lg"
position={layout === 'text-left' ? 'bottom-left' : 'top-right'}
opacity={0.02}
/>
<Container>
<Grid cols={{ base: 1, lg: 2 }} gap={12}>
{/* Text Content */}
<Stack gap={8}>
<Stack gap={4}>
<Heading level={2} weight="bold">
{heading}
</Heading>
</Stack>
<Stack>
{description}
</Stack>
</Stack>
{/* Mockup Panel */}
<Panel variant="dark">
<Stack align="center" justify="center">
{mockup}
</Stack>
</Panel>
</Grid>
</Container>
</Section>
<FeatureSection
heading={heading}
description={description}
mockup={mockup}
layout={layout}
intent={accentColor}
/>
);
}

View File

@@ -1,109 +1,42 @@
'use client';
import React from 'react';
import { Button } from '@/ui/Button';
import { Glow } from '@/ui/Glow';
import { Icon } from '@/ui/Icon';
import { DiscordIcon } from '@/ui/icons/DiscordIcon';
import { Code, Lightbulb, LucideIcon, MessageSquare, Users } from 'lucide-react';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid';
import { Card } from '@/ui/Card';
import { Section } from '@/ui/Section';
import { Container } from '@/ui/Container';
import { DiscordCTA } from '@/ui/DiscordCTA';
import { Code, Lightbulb, MessageSquare, Users } from 'lucide-react';
export function HomeFooterCTA() {
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#';
const benefits = [
{
icon: MessageSquare,
title: "Share your pain points",
description: "Tell us what frustrates you about league racing today."
},
{
icon: Lightbulb,
title: "Shape the product",
description: "Your ideas directly influence our roadmap."
},
{
icon: Users,
title: "Connect with racers",
description: "Join a community of like-minded competitive drivers."
},
{
icon: Code,
title: "Early Access",
description: "Test new features before they go public."
},
];
return (
<Section variant="dark" padding="lg">
<Glow color="primary" size="xl" position="center" opacity={0.05} />
<Container>
<Card variant="outline">
<Stack align="center" gap={12}>
{/* Header */}
<Stack align="center" gap={6}>
<DiscordIcon size={40} />
<Stack gap={4} align="center">
<Heading level={2} weight="bold" align="center">
Join the Grid on Discord
</Heading>
</Stack>
</Stack>
{/* Personal message */}
<Stack align="center" gap={6}>
<Text size="lg" variant="high" weight="medium" align="center">
GridPilot is a <Text as="span" variant="high" weight="bold">solo developer project</Text> built for the community.
</Text>
<Text size="base" variant="low" align="center">
We are in early alpha. Join us to help shape the future of motorsport infrastructure. Your feedback directly influences the roadmap.
</Text>
</Stack>
{/* Benefits grid */}
<Grid cols={{ base: 1, md: 2 }} gap={6}>
<BenefitItem
icon={MessageSquare}
title="Share your pain points"
description="Tell us what frustrates you about league racing today."
/>
<BenefitItem
icon={Lightbulb}
title="Shape the product"
description="Your ideas directly influence our roadmap."
/>
<BenefitItem
icon={Users}
title="Connect with racers"
description="Join a community of like-minded competitive drivers."
/>
<BenefitItem
icon={Code}
title="Early Access"
description="Test new features before they go public."
/>
</Grid>
{/* CTA Button */}
<Stack gap={6} align="center">
<Button
as="a"
href={discordUrl}
target="_blank"
rel="noopener noreferrer"
variant="primary"
size="lg"
icon={<DiscordIcon size={24} />}
>
Join Discord
</Button>
<Text size="xs" variant="primary" weight="bold" font="mono" uppercase>
Early Alpha Access Available
</Text>
</Stack>
</Stack>
</Card>
</Container>
</Section>
);
}
function BenefitItem({ icon, title, description }: { icon: LucideIcon, title: string, description: string }) {
return (
<Card variant="dark">
<Stack align="start" gap={5}>
<Icon icon={icon} size={5} intent="primary" />
<Stack gap={2}>
<Text size="base" weight="bold" variant="high">{title}</Text>
<Text size="sm" variant="low">{description}</Text>
</Stack>
</Stack>
</Card>
<DiscordCTA
title="Join the Grid on Discord"
lead="GridPilot is a solo developer project built for the community."
description="We are in early alpha. Join us to help shape the future of motorsport infrastructure. Your feedback directly influences the roadmap."
benefits={benefits}
discordUrl={discordUrl}
/>
);
}

View File

@@ -1,13 +1,6 @@
'use client';
import { Button } from '@/ui/Button';
import { Container } from '@/ui/Container';
import { Glow } from '@/ui/Glow';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Section } from '@/ui/Section';
import { ButtonGroup } from '@/ui/ButtonGroup';
import { LandingHero } from '@/ui/LandingHero';
interface HomeHeaderProps {
title: string;
@@ -26,51 +19,6 @@ interface HomeHeaderProps {
/**
* HomeHeader - Semantic hero section for the landing page.
*/
export function HomeHeader({
title,
subtitle,
description,
primaryAction,
secondaryAction,
}: HomeHeaderProps) {
return (
<Section variant="dark" padding="lg">
<Glow color="primary" size="xl" position="top-right" opacity={0.1} />
<Container>
<Stack gap={8}>
<Text size="xs" weight="bold" uppercase variant="primary">
{subtitle}
</Text>
<Heading level={1} weight="bold">
{title}
</Heading>
<Text size="lg" variant="low">
{description}
</Text>
<ButtonGroup gap={4}>
<Button
as="a"
href={primaryAction.href}
variant="primary"
size="lg"
>
{primaryAction.label}
</Button>
<Button
as="a"
href={secondaryAction.href}
variant="secondary"
size="lg"
>
{secondaryAction.label}
</Button>
</ButtonGroup>
</Stack>
</Container>
</Section>
);
export function HomeHeader(props: HomeHeaderProps) {
return <LandingHero {...props} />;
}

View File

@@ -1,43 +1,39 @@
'use client';
import React from 'react';
import { MetricCard } from '@/ui/MetricCard';
import { StatsStrip } from '@/ui/StatsStrip';
import { Activity, Users, Trophy, Calendar } from 'lucide-react';
import { Container } from '@/ui/Container';
import { Grid } from '@/ui/Grid';
/**
* HomeStatsStrip - A thin strip showing some status or quick info.
* Part of the "Telemetry-workspace" feel.
*/
export function HomeStatsStrip() {
return (
<Container>
<Grid cols={{ base: 2, md: 4 }} gap={4}>
<MetricCard
label="Active Drivers"
value="1,284"
icon={Users}
trend={{ value: 12, isPositive: true }}
/>
<MetricCard
label="Live Sessions"
value="42"
icon={Activity}
intent="telemetry"
/>
<MetricCard
label="Total Races"
value="15,402"
icon={Trophy}
intent="warning"
/>
<MetricCard
label="Next Event"
value="14:00"
icon={Calendar}
/>
</Grid>
</Container>
);
const stats = [
{
label: "Active Drivers",
value: "1,284",
icon: Users,
trend: { value: 12, isPositive: true }
},
{
label: "Live Sessions",
value: "42",
icon: Activity,
intent: "telemetry" as const
},
{
label: "Total Races",
value: "15,402",
icon: Trophy,
intent: "warning" as const
},
{
label: "Next Event",
value: "14:00",
icon: Calendar
},
];
return <StatsStrip stats={stats} />;
}

View File

@@ -2,9 +2,12 @@
import { LeagueCard } from '@/components/leagues/LeagueCard';
import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
import { Heading } from '@/ui/Heading';
import { Link } from '@/ui/Link';
import { Panel } from '@/ui/Panel';
import { Box } from '@/ui/Box';
import { CardStack } from '@/ui/CardStack';
interface League {
id: string;
@@ -20,26 +23,26 @@ interface LeagueSummaryPanelProps {
* LeagueSummaryPanel - Semantic section for featured leagues.
*/
export function LeagueSummaryPanel({ leagues }: LeagueSummaryPanelProps) {
return (
<Box as="section" bg="surface-charcoal" p={6} border borderColor="border-gray" rounded="none">
<Box display="flex" alignItems="center" justifyContent="between" mb={6}>
<Heading level={3} fontSize="xs" weight="bold" letterSpacing="widest" color="text-white">
FEATURED LEAGUES
</Heading>
<Link
href={routes.public.leagues}
size="xs"
weight="bold"
letterSpacing="widest"
variant="primary"
hoverColor="text-white"
transition
>
VIEW ALL
</Link>
</Box>
const actions = (
<Link
href={routes.public.leagues}
size="xs"
weight="bold"
letterSpacing="widest"
variant="primary"
>
VIEW ALL
</Link>
);
<Box display="flex" flexDirection="col" gap={4}>
return (
<Panel
variant="dark"
padding={6}
title="FEATURED LEAGUES"
actions={actions}
>
<CardStack gap={4}>
{leagues.slice(0, 2).map((league) => (
<LeagueCard
key={league.id}
@@ -54,7 +57,7 @@ export function LeagueSummaryPanel({ leagues }: LeagueSummaryPanelProps) {
openSlotsCount={6}
/>
))}
</Box>
</Box>
</CardStack>
</Panel>
);
}

View File

@@ -5,9 +5,13 @@ import { routes } from '@/lib/routing/RouteConfig';
import { Heading } from '@/ui/Heading';
import { Link } from '@/ui/Link';
import { Panel } from '@/ui/Panel';
import { Stack } from '@/ui/Stack';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { CardStack } from '@/ui/CardStack';
import { Center } from '@/ui/Center';
interface Race {
id: string;
track: string;
@@ -23,33 +27,33 @@ interface RecentRacesPanelProps {
* RecentRacesPanel - Semantic section for upcoming/recent races.
*/
export function RecentRacesPanel({ races }: RecentRacesPanelProps) {
return (
<Panel variant="dark" padding={6}>
<Stack direction="row" align="center" justify="between" mb={6}>
<Heading level={3} fontSize="xs" weight="bold" letterSpacing="widest" color="text-white">
UPCOMING RACES
</Heading>
<Link
href={routes.public.races}
size="xs"
weight="bold"
letterSpacing="widest"
variant="primary"
hoverColor="text-white"
transition
>
FULL SCHEDULE
</Link>
</Stack>
const actions = (
<Link
href={routes.public.races}
size="xs"
weight="bold"
letterSpacing="widest"
variant="primary"
>
FULL SCHEDULE
</Link>
);
<Stack gap={3}>
return (
<Panel
variant="dark"
padding={6}
title="UPCOMING RACES"
actions={actions}
>
<CardStack gap={3}>
{races.length === 0 ? (
<Panel variant="muted" padding={12} border>
<Stack center>
<Text size="xs" font="mono" uppercase letterSpacing="widest" color="text-gray-600">
<Center>
<Text size="xs" font="mono" uppercase letterSpacing="widest" variant="low">
No races scheduled
</Text>
</Stack>
</Center>
</Panel>
) : (
races.slice(0, 3).map((race) => (
@@ -63,7 +67,7 @@ export function RecentRacesPanel({ races }: RecentRacesPanelProps) {
/>
))
)}
</Stack>
</CardStack>
</Panel>
);
}

View File

@@ -2,9 +2,12 @@
import { TeamCard } from '@/components/teams/TeamCard';
import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
import { Heading } from '@/ui/Heading';
import { Link } from '@/ui/Link';
import { Panel } from '@/ui/Panel';
import { Box } from '@/ui/Box';
import { CardStack } from '@/ui/CardStack';
interface Team {
id: string;
@@ -21,26 +24,26 @@ interface TeamSummaryPanelProps {
* TeamSummaryPanel - Semantic section for teams.
*/
export function TeamSummaryPanel({ teams }: TeamSummaryPanelProps) {
return (
<Box as="section" bg="surface-charcoal" p={6} border borderColor="border-gray" rounded="none">
<Box display="flex" alignItems="center" justifyContent="between" mb={6}>
<Heading level={3} fontSize="xs" weight="bold" letterSpacing="widest" color="text-white">
TEAMS ON THE GRID
</Heading>
<Link
href={routes.public.teams}
size="xs"
weight="bold"
letterSpacing="widest"
variant="primary"
hoverColor="text-white"
transition
>
BROWSE TEAMS
</Link>
</Box>
const actions = (
<Link
href={routes.public.teams}
size="xs"
weight="bold"
letterSpacing="widest"
variant="primary"
>
BROWSE TEAMS
</Link>
);
<Box display="flex" flexDirection="col" gap={4}>
return (
<Panel
variant="dark"
padding={6}
title="TEAMS ON THE GRID"
actions={actions}
>
<CardStack gap={4}>
{teams.slice(0, 2).map((team) => (
<TeamCard
key={team.id}
@@ -51,7 +54,7 @@ export function TeamSummaryPanel({ teams }: TeamSummaryPanelProps) {
isRecruiting={true}
/>
))}
</Box>
</Box>
</CardStack>
</Panel>
);
}