import { useEffect, useRef, useState } from "react"; import { Link } from "react-router"; import { Users, Shuffle, Trophy } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { SectionCardHeader } from "~/components/ui/SectionCardHeader"; // ─── Slot Machine ──────────────────────────────────────────────────────────── const SPORTS = [ "Football", "Baseball", "Hockey", "Basketball", "Soccer", "Tennis", "Golf", "eSports", "Formula 1", "Aussie Rules", "Lacrosse", "Darts", "College Football", "Snooker", "March Madness", ]; function shuffle(arr: T[]): T[] { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } const TOTAL_TICKS = 36; function getInterval(tick: number): number { const pct = tick / TOTAL_TICKS; return Math.round(130 - (130 - 38) * (pct * pct)); } function SlotMachineHeadline() { const [text, setText] = useState(""); const [landed, setLanded] = useState(false); const [animKey, setAnimKey] = useState(0); const tickRef = useRef(0); const timerRef = useRef | null>(null); const sportsSeq = useRef([]); useEffect(() => { sportsSeq.current = shuffle(SPORTS); let idx = 0; function tick() { tickRef.current++; const sport = sportsSeq.current[idx % sportsSeq.current.length]; idx++; setText(sport); setAnimKey((k) => k + 1); if (tickRef.current >= TOTAL_TICKS) { setLanded(true); return; } timerRef.current = setTimeout(tick, getInterval(tickRef.current)); } timerRef.current = setTimeout(tick, 130); return () => { if (timerRef.current) clearTimeout(timerRef.current); }; }, []); if (landed) { return ( Every Sport. ); } return ( {text} ); } // ─── Bracket Decoration ────────────────────────────────────────────────────── const LIME = "#adf661"; const LIME_DIM = "rgba(173,246,97,0.07)"; const LIME_DIM_BORDER = "rgba(173,246,97,0.25)"; const LIME_CHAMP = "rgba(173,246,97,0.12)"; const LIME_CHAMP_BORDER = "rgba(173,246,97,0.8)"; const SLOT_BG = "rgba(32,33,36,0.95)"; const SLOT_BORDER = "rgba(255,255,255,0.10)"; const LINE_COLOR_R1 = "rgba(255,255,255,0.15)"; const LINE_COLOR_R2 = "rgba(255,255,255,0.28)"; // SVG coordinate-space layout constants const SW = 80; const SH = 20; const GAP = 10; const PAIR_H = SH * 2 + GAP; const R1_X = 10; const R2_X = 120; const R3_X = 210; const BRACKET_W = 300; const BRACKET_H = 360; const R1_TOP = 50; const PAIR_GAP = 20; type SlotDef = { x: number; y: number; w: number; h: number; winner?: boolean; champ?: boolean; }; function BracketSlot({ x, y, w, h, winner, champ }: SlotDef) { const bg = champ ? LIME_CHAMP : winner ? LIME_DIM : SLOT_BG; const border = champ ? LIME_CHAMP_BORDER : winner ? LIME_DIM_BORDER : SLOT_BORDER; const bw = champ ? 1.5 : 1; return ( {(winner || champ) && ( )} {champ && ( CHAMP )} ); } function hLine(x1: number, y1: number, x2: number, color: string) { return ; } function connector(ax: number, ay: number, bx: number, by: number, cx: number, color: string) { const midY = (ay + by) / 2; return ( ); } function BracketDecor({ flip = false }: { flip?: boolean }) { const r1Pairs: [SlotDef, SlotDef][] = Array.from({ length: 4 }, (_, i) => { const pairY = R1_TOP + i * (PAIR_H + PAIR_GAP); const winner = i % 3 !== 1; return [ { x: R1_X, y: pairY, w: SW, h: SH, winner: winner && i % 2 === 0 }, { x: R1_X, y: pairY + SH + GAP, w: SW, h: SH, winner: winner && i % 2 !== 0 }, ]; }); const r2Slots: SlotDef[] = [0, 1].map((i) => { const top1Y = R1_TOP + (i * 2) * (PAIR_H + PAIR_GAP); const bot2Y = R1_TOP + (i * 2 + 1) * (PAIR_H + PAIR_GAP) + SH + GAP; const midY = (top1Y + bot2Y) / 2 - SH / 2; return { x: R2_X, y: midY, w: SW, h: SH, winner: i === 0 }; }); const r3Y = (r2Slots[0].y + r2Slots[1].y + SH) / 2 - SH / 2; const r3Slot: SlotDef = { x: R3_X, y: r3Y, w: SW, h: SH + 4, champ: true }; return ( {r1Pairs.map(([a, b], i) => ( {connector( R1_X + SW, a.y + SH / 2, r2Slots[Math.floor(i / 2)].x, r2Slots[Math.floor(i / 2)].y + SH / 2, R1_X + SW + 14, LINE_COLOR_R1 )} {hLine(R1_X + SW + 14, (a.y + SH / 2 + b.y + SH / 2) / 2, R2_X, LINE_COLOR_R1)} ))} {r2Slots.map((s, ri) => ( {connector( R2_X + SW, r2Slots[0].y + SH / 2, r3Slot.x, r3Slot.y + (r3Slot.h ?? SH) / 2, R2_X + SW + 14, LINE_COLOR_R2 )} {ri === 1 && hLine(R2_X + SW + 14, (r2Slots[0].y + SH / 2 + r2Slots[1].y + SH / 2) / 2, R3_X, LINE_COLOR_R2)} ))} ); } // ─── Sport Ticker ───────────────────────────────────────────────────────────── const TICKER_ITEMS = [ ...SPORTS.map((s) => ({ sport: s.toUpperCase(), key: `${s}-a` })), ...SPORTS.map((s) => ({ sport: s.toUpperCase(), key: `${s}-b` })), ]; function SportTicker() { return (
{TICKER_ITEMS.map(({ sport, key }) => ( {sport} ))}
); } // ─── Static data ────────────────────────────────────────────────────────────── const HOW_IT_WORKS_STEPS = [ { icon: Users, title: "Create a League", body: "Name it, pick your sports, invite your friends with a link. Takes 30 seconds.", }, { icon: Shuffle, title: "Draft Your Teams", body: "Snake draft — everyone picks teams from any sport on the calendar. NHL, NBA, World Cup, whatever is running.", }, { icon: Trophy, title: "Watch & Win", body: "Your teams earn points as they win real games. The bracket updates live. Last one standing takes it.", }, ]; const FEATURES = [ { title: "Every sport, one league", body: "NHL running at the same time as the NBA Finals? Draft from both. Brackt syncs across every major sports calendar.", }, { title: "Live bracket view", body: "Watch your bracket update as games end. Heartbreak and glory, all on one screen.", }, { title: "Snake draft", body: "Fair picks for everyone. No one ends up with dregs. The draft is the fun part — we let you enjoy it.", }, { title: "Invite in one click", body: "Share a link. Friends join, pick a name, they're in. No downloads, no friction.", }, ]; const FOOTER_LINKS = [ { label: "Home", to: "/" }, { label: "Rules", to: "/rules" }, { label: "How to Play", to: "/how-to-play" }, ]; // ─── Main Landing Page ──────────────────────────────────────────────────────── export function LandingPage() { // Interleave step cards with gradient triangle separators const howItWorksItems: React.ReactNode[] = []; HOW_IT_WORKS_STEPS.forEach((step, i) => { howItWorksItems.push(

{step.body}

); if (i < HOW_IT_WORKS_STEPS.length - 1) { // Triangles reference #brackt-primary-gradient defined in (mounted in root.tsx) howItWorksItems.push(
); } }); return (
{/* ── Hero ── */}
{/* Bracket decorations — lg+ only */}
{/* Center content */}

Fantasy

Draft teams across every sport — NHL, NBA, NFL, EPL, World Cup, and more. Compete with your friends in a league all season long.

{/* ── How It Works ── */}
{howItWorksItems}
{/* ── Features Grid ── */}
{FEATURES.map(({ title, body }) => (

{title}

{body}

))}
{/* ── CTA Banner ── */}

Your group chat is waiting for this.

Free to play. Five minutes to set up. The real question is — who drafts the Colorado Avalanche?

{/* ── Footer ── */}
); }