359 lines
21 KiB
TypeScript
359 lines
21 KiB
TypeScript
import { redirect } from 'next/navigation';
|
|
|
|
import { getAppMode } from '@/lib/mode';
|
|
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 '@/components/ui/MockupStack';
|
|
import Card from '@/components/ui/Card';
|
|
import Button from '@/components/ui/Button';
|
|
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
|
import { ServiceFactory } from '@/lib/services/ServiceFactory';
|
|
|
|
export default async function HomePage() {
|
|
const baseUrl = getWebsiteApiBaseUrl();
|
|
const serviceFactory = new ServiceFactory(baseUrl);
|
|
const sessionService = serviceFactory.createSessionService();
|
|
const landingService = serviceFactory.createLandingService();
|
|
|
|
const session = await sessionService.getSession();
|
|
if (session) {
|
|
redirect('/dashboard');
|
|
}
|
|
|
|
const mode = getAppMode();
|
|
const isAlpha = mode === 'alpha';
|
|
const discovery = await landingService.getHomeDiscovery();
|
|
const upcomingRaces = discovery.upcomingRaces;
|
|
const topLeagues = discovery.topLeagues;
|
|
const teams = discovery.teams;
|
|
|
|
return (
|
|
<main className="min-h-screen">
|
|
<Hero />
|
|
|
|
{/* Section 1: A Persistent Identity */}
|
|
<AlternatingSection
|
|
heading="A Persistent Identity"
|
|
backgroundVideo="/gameplay.mp4"
|
|
description={
|
|
<>
|
|
<p>
|
|
Your races, your seasons, your progress — finally in one place.
|
|
</p>
|
|
<div className="space-y-3 mt-4">
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-r from-slate-900/60 via-slate-800/40 to-slate-900/60 p-4 border border-slate-700/40 hover:border-primary-blue/50 transition-all duration-300 hover:shadow-[0_0_25px_rgba(59,130,246,0.15)]">
|
|
<div className="absolute top-0 left-0 w-full h-0.5 bg-gradient-to-r from-transparent via-primary-blue/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex-shrink-0 w-9 h-9 rounded-lg bg-gradient-to-br from-primary-blue/20 to-blue-900/20 border border-primary-blue/30 flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform">
|
|
<svg className="w-5 h-5 text-primary-blue" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-slate-200 leading-relaxed font-light">
|
|
Lifetime stats and season history across all your leagues
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-r from-slate-900/60 via-slate-800/40 to-slate-900/60 p-4 border border-slate-700/40 hover:border-primary-blue/50 transition-all duration-300 hover:shadow-[0_0_25px_rgba(59,130,246,0.15)]">
|
|
<div className="absolute top-0 left-0 w-full h-0.5 bg-gradient-to-r from-transparent via-primary-blue/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex-shrink-0 w-9 h-9 rounded-lg bg-gradient-to-br from-primary-blue/20 to-blue-900/20 border border-primary-blue/30 flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform">
|
|
<svg className="w-5 h-5 text-primary-blue" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-slate-200 leading-relaxed font-light">
|
|
Track your performance, consistency, and team contributions
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-r from-slate-900/60 via-slate-800/40 to-slate-900/60 p-4 border border-slate-700/40 hover:border-primary-blue/50 transition-all duration-300 hover:shadow-[0_0_25px_rgba(59,130,246,0.15)]">
|
|
<div className="absolute top-0 left-0 w-full h-0.5 bg-gradient-to-r from-transparent via-primary-blue/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex-shrink-0 w-9 h-9 rounded-lg bg-gradient-to-br from-primary-blue/20 to-blue-900/20 border border-primary-blue/30 flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform">
|
|
<svg className="w-5 h-5 text-primary-blue" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-slate-200 leading-relaxed font-light">
|
|
Your own rating that reflects real league competition
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="mt-4">
|
|
iRacing gives you physics. GridPilot gives you a career.
|
|
</p>
|
|
</>
|
|
}
|
|
mockup={<CareerProgressionMockup />}
|
|
layout="text-left"
|
|
/>
|
|
|
|
<FeatureGrid />
|
|
|
|
{/* Section 2: Results That Actually Stay */}
|
|
<AlternatingSection
|
|
heading="Results That Actually Stay"
|
|
backgroundImage="/images/ff1600.jpeg"
|
|
description={
|
|
<>
|
|
<p className="text-sm md:text-base leading-relaxed">
|
|
Every race you run stays with you.
|
|
</p>
|
|
<div className="space-y-3 mt-4 md:mt-6">
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-r from-slate-900/60 via-slate-800/40 to-slate-900/60 p-3.5 md:p-4 border border-slate-700/40 hover:border-red-600/50 transition-all duration-300 hover:shadow-[0_0_25px_rgba(220,38,38,0.15)]">
|
|
<div className="absolute top-0 right-0 w-12 h-12 bg-gradient-to-bl from-red-600/10 to-transparent rounded-bl-3xl opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-2.5 md:gap-3">
|
|
<div className="flex-shrink-0 w-8 h-8 md:w-9 md:h-9 rounded-lg bg-gradient-to-br from-red-600/20 to-red-900/20 border border-red-600/30 flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform">
|
|
<svg className="w-4 h-4 md:w-5 md:h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-slate-200 text-sm md:text-base leading-relaxed font-light">
|
|
Your stats, your team, your story — all connected
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-r from-slate-900/60 via-slate-800/40 to-slate-900/60 p-3.5 md:p-4 border border-slate-700/40 hover:border-red-600/50 transition-all duration-300 hover:shadow-[0_0_25px_rgba(220,38,38,0.15)]">
|
|
<div className="absolute top-0 right-0 w-12 h-12 bg-gradient-to-bl from-red-600/10 to-transparent rounded-bl-3xl opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-2.5 md:gap-3">
|
|
<div className="flex-shrink-0 w-8 h-8 md:w-9 md:h-9 rounded-lg bg-gradient-to-br from-red-600/20 to-red-900/20 border border-red-600/30 flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform">
|
|
<svg className="w-4 h-4 md:w-5 md:h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-slate-200 text-sm md:text-base leading-relaxed font-light">
|
|
One race result updates your profile, team points, rating, and season history
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-r from-slate-900/60 via-slate-800/40 to-slate-900/60 p-3.5 md:p-4 border border-slate-700/40 hover:border-red-600/50 transition-all duration-300 hover:shadow-[0_0_25px_rgba(220,38,38,0.15)]">
|
|
<div className="absolute top-0 right-0 w-12 h-12 bg-gradient-to-bl from-red-600/10 to-transparent rounded-bl-3xl opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-2.5 md:gap-3">
|
|
<div className="flex-shrink-0 w-8 h-8 md:w-9 md:h-9 rounded-lg bg-gradient-to-br from-red-600/20 to-red-900/20 border border-red-600/30 flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform">
|
|
<svg className="w-4 h-4 md:w-5 md:h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-slate-200 text-sm md:text-base leading-relaxed font-light">
|
|
No more fragmented data across spreadsheets and forums
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="mt-4 md:mt-6 text-sm md:text-base leading-relaxed">
|
|
Your racing career, finally in one place.
|
|
</p>
|
|
</>
|
|
}
|
|
mockup={<MockupStack index={1}><RaceHistoryMockup /></MockupStack>}
|
|
layout="text-right"
|
|
/>
|
|
|
|
{/* Section 3: Automatic Session Creation */}
|
|
<AlternatingSection
|
|
heading="Automatic Session Creation"
|
|
description={
|
|
<>
|
|
<p className="text-sm md:text-base leading-relaxed">
|
|
Setting up league races used to mean clicking through iRacing's wizard 20 times.
|
|
</p>
|
|
<div className="space-y-3 mt-4 md:mt-6">
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-br from-slate-900/70 to-slate-800/50 p-3.5 md:p-4 border border-slate-700/50 hover:border-primary-blue/60 transition-all duration-300 hover:shadow-[0_0_30px_rgba(59,130,246,0.2)]">
|
|
<div className="absolute -top-12 -right-12 w-24 h-24 bg-gradient-to-br from-primary-blue/10 to-transparent rounded-full blur-2xl opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-2.5 md:gap-3 relative">
|
|
<div className="flex-shrink-0 w-9 h-9 md:w-10 md:h-10 rounded-xl bg-gradient-to-br from-primary-blue/25 to-blue-900/25 border border-primary-blue/40 flex items-center justify-center shadow-lg group-hover:shadow-primary-blue/20 group-hover:scale-110 transition-all">
|
|
<span className="text-primary-blue font-bold text-sm">1</span>
|
|
</div>
|
|
<span className="text-slate-200 text-sm md:text-base leading-relaxed font-light">
|
|
Our companion app syncs with your league schedule
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-br from-slate-900/70 to-slate-800/50 p-3.5 md:p-4 border border-slate-700/50 hover:border-primary-blue/60 transition-all duration-300 hover:shadow-[0_0_30px_rgba(59,130,246,0.2)]">
|
|
<div className="absolute -top-12 -right-12 w-24 h-24 bg-gradient-to-br from-primary-blue/10 to-transparent rounded-full blur-2xl opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-2.5 md:gap-3 relative">
|
|
<div className="flex-shrink-0 w-9 h-9 md:w-10 md:h-10 rounded-xl bg-gradient-to-br from-primary-blue/25 to-blue-900/25 border border-primary-blue/40 flex items-center justify-center shadow-lg group-hover:shadow-primary-blue/20 group-hover:scale-110 transition-all">
|
|
<span className="text-primary-blue font-bold text-sm">2</span>
|
|
</div>
|
|
<span className="text-slate-200 text-sm md:text-base leading-relaxed font-light">
|
|
When it's race time, it creates the iRacing session automatically
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="group relative overflow-hidden rounded-lg bg-gradient-to-br from-slate-900/70 to-slate-800/50 p-3.5 md:p-4 border border-slate-700/50 hover:border-primary-blue/60 transition-all duration-300 hover:shadow-[0_0_30px_rgba(59,130,246,0.2)]">
|
|
<div className="absolute -top-12 -right-12 w-24 h-24 bg-gradient-to-br from-primary-blue/10 to-transparent rounded-full blur-2xl opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
<div className="flex items-start gap-2.5 md:gap-3 relative">
|
|
<div className="flex-shrink-0 w-9 h-9 md:w-10 md:h-10 rounded-xl bg-gradient-to-br from-primary-blue/25 to-blue-900/25 border border-primary-blue/40 flex items-center justify-center shadow-lg group-hover:shadow-primary-blue/20 group-hover:scale-110 transition-all">
|
|
<span className="text-primary-blue font-bold text-sm">3</span>
|
|
</div>
|
|
<span className="text-slate-200 text-sm md:text-base leading-relaxed font-light">
|
|
No clicking through wizards. No manual setup
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="mt-4 md:mt-6 text-sm md:text-base leading-relaxed">
|
|
Automation instead of repetition.
|
|
</p>
|
|
</>
|
|
}
|
|
mockup={<CompanionAutomationMockup />}
|
|
layout="text-left"
|
|
/>
|
|
|
|
{/* Section 4: Game-Agnostic Platform */}
|
|
<AlternatingSection
|
|
heading="Built for iRacing. Ready for the future."
|
|
backgroundImage="/images/lmp3.jpeg"
|
|
description={
|
|
<>
|
|
<p className="text-sm md:text-base leading-relaxed">
|
|
Right now, we're focused on making iRacing league racing better.
|
|
</p>
|
|
<p className="mt-4 md:mt-6 text-sm md:text-base leading-relaxed">
|
|
But sims come and go. Your leagues, your teams, your rating — those stay.
|
|
</p>
|
|
<p className="mt-4 md:mt-6 text-sm md:text-base leading-relaxed">
|
|
GridPilot is built to outlast any single platform.
|
|
</p>
|
|
<p className="mt-4 md:mt-6 text-sm md:text-base leading-relaxed">
|
|
When the next sim arrives, your competitive identity moves with you.
|
|
</p>
|
|
</>
|
|
}
|
|
mockup={<SimPlatformMockup />}
|
|
layout="text-right"
|
|
/>
|
|
|
|
{/* Alpha-only discovery section */}
|
|
{isAlpha && (
|
|
<section className="max-w-7xl mx-auto mt-20 mb-20 px-6">
|
|
<div className="flex items-baseline justify-between mb-8">
|
|
<div>
|
|
<h2 className="text-2xl font-semibold text-white">Discover the grid</h2>
|
|
<p className="text-sm text-gray-400">
|
|
Explore leagues, teams, and races that make up the GridPilot ecosystem.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-8 lg:grid-cols-3">
|
|
{/* Top leagues */}
|
|
<Card className="bg-iron-gray/80">
|
|
<div className="flex items-baseline justify-between mb-4">
|
|
<h3 className="text-sm font-semibold text-white">Featured leagues</h3>
|
|
<Button
|
|
as="a"
|
|
href="/leagues"
|
|
variant="secondary"
|
|
className="text-[11px] px-3 py-1.5"
|
|
>
|
|
View all
|
|
</Button>
|
|
</div>
|
|
<ul className="space-y-3 text-sm">
|
|
{topLeagues.slice(0, 4).map(league => (
|
|
<li key={league.id} className="flex items-start gap-3">
|
|
<div className="w-10 h-10 rounded-md bg-primary-blue/15 border border-primary-blue/30 flex items-center justify-center text-xs font-semibold text-primary-blue">
|
|
{league.name
|
|
.split(' ')
|
|
.map(word => word[0])
|
|
.join('')
|
|
.slice(0, 3)
|
|
.toUpperCase()}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-white truncate">{league.name}</p>
|
|
<p className="text-xs text-gray-400 line-clamp-2">
|
|
{league.description}
|
|
</p>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</Card>
|
|
|
|
{/* Teams */}
|
|
<Card className="bg-iron-gray/80">
|
|
<div className="flex items-baseline justify-between mb-4">
|
|
<h3 className="text-sm font-semibold text-white">Teams on the grid</h3>
|
|
<Button
|
|
as="a"
|
|
href="/teams"
|
|
variant="secondary"
|
|
className="text-[11px] px-3 py-1.5"
|
|
>
|
|
Browse teams
|
|
</Button>
|
|
</div>
|
|
<ul className="space-y-3 text-sm">
|
|
{teams.slice(0, 4).map(team => (
|
|
<li key={team.id} className="flex items-start gap-3">
|
|
<div className="w-10 h-10 rounded-md bg-charcoal-outline flex items-center justify-center text-xs font-semibold text-white">
|
|
{team.tag}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-white truncate">{team.name}</p>
|
|
<p className="text-xs text-gray-400 line-clamp-2">
|
|
{team.description}
|
|
</p>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</Card>
|
|
|
|
{/* Upcoming races */}
|
|
<Card className="bg-iron-gray/80">
|
|
<div className="flex items-baseline justify-between mb-4">
|
|
<h3 className="text-sm font-semibold text-white">Upcoming races</h3>
|
|
<Button
|
|
as="a"
|
|
href="/races"
|
|
variant="secondary"
|
|
className="text-[11px] px-3 py-1.5"
|
|
>
|
|
View schedule
|
|
</Button>
|
|
</div>
|
|
{upcomingRaces.length === 0 ? (
|
|
<p className="text-xs text-gray-400">
|
|
No races scheduled in this demo snapshot.
|
|
</p>
|
|
) : (
|
|
<ul className="space-y-3 text-sm">
|
|
{upcomingRaces.map(race => (
|
|
<li key={race.id} className="flex items-start justify-between gap-3">
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-white truncate">{race.track}</p>
|
|
<p className="text-xs text-gray-400 truncate">{race.car}</p>
|
|
</div>
|
|
<div className="text-right text-xs text-gray-500 whitespace-nowrap">
|
|
{race.formattedDate}
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
<DiscordCTA />
|
|
<FAQ />
|
|
<Footer />
|
|
</main>
|
|
);
|
|
}
|