import { useEffect, useRef, useState } from "react"; import { Link } from "react-router"; import { Users, Shuffle, Trophy, ChevronDown } 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 = 12; // WCAG 2.3.1: no more than 3 flashes/second → minimum interval 334 ms. // Curve runs 800 ms → 340 ms over 12 ticks (ramps to full speed at tick 8). function getInterval(tick: number): number { const pct = Math.min((tick / TOTAL_TICKS) * 1.5, 1); return Math.round(800 - (800 - 340) * (pct * pct)); } function SlotMachineHeadline() { const [text, setText] = useState(SPORTS[0]); 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, 800); return () => { if (timerRef.current) clearTimeout(timerRef.current); }; }, []); if (landed) { return ( Every Sport. ); } return ( {text} ); } // ─── Bracket Decoration ────────────────────────────────────────────────────── function BracketDecor({ flip = false, mobileTall = false }: { flip?: boolean; mobileTall?: boolean }) { const base = flip ? "r" : "l"; const id = mobileTall ? `${base}-m` : base; const W = 399; const H = mobileTall ? 2000 : 560; const pad = 36; const spacing = (H - 2 * pad) / 7; const r1Ys = Array.from({ length: 8 }, (_, i) => pad + i * spacing); const r2Ys = [0, 1, 2, 3].map((i) => (r1Ys[i * 2] + r1Ys[i * 2 + 1]) / 2); const r3Ys = [0, 1].map((i) => (r2Ys[i * 2] + r2Ys[i * 2 + 1]) / 2); const champY = (r3Ys[0] + r3Ys[1]) / 2; const x0 = 4; const x1 = 77; const x2 = 161; const x3 = 252; const gradId = `grad-${id}`; const glowId = `glow-${id}`; const dotsGlowId = `dots-glow-${id}`; const finalsGlowId = `finals-glow-${id}`; const finalsHaloId = `finals-halo-${id}`; const finalsFadeMaskGradId = `finals-fade-mask-grad-${id}`; const finalsFadeMaskId = `finals-fade-mask-${id}`; const finalsLineGlowId = `finals-line-glow-${id}`; return ( {r3Ys.map((midY) => ( ))} {r3Ys.map((midY) => ( ))} {r1Ys.map((y) => ( ))} {r2Ys.map((midY, i) => ( ))} {r3Ys.map((midY, i) => ( ))} ); } // ─── Scroll Hint ───────────────────────────────────────────────────────────── function ScrollHint() { const [visible, setVisible] = useState(true); useEffect(() => { const onScroll = () => { if (window.scrollY > 50) setVisible(false); }; window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); return (
); } // ─── 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: "Start a league, pick your sports (we can help), and invite your friends. It takes 30 seconds to set the stage.", }, { icon: Shuffle, title: "Draft Your Teams", body: "Pick teams from all the sports in order to build a superteam to win you bragging rights! Draft your way, slow or fast, with standard timers or high-pressure chess clocks.", }, { icon: Trophy, title: "Watch & Win", body: "Your teams earn points for how well they perform in the playoffs. Follow your rooting interests along the way, and gloat over your leaguemates when they fall.", }, ]; 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.", }, ]; // ─── 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 ── */}
{/* Dot-grid texture */}
{/* Bracket decorations */}
{/* Center content */}

Fantasy

Draft teams across every league and sport, like the NFL, NHL, EPL, F1, and more. Compete against your friends in a season-long league.

{/* ── 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?

); }