import { useEffect, useRef, useState } from "react"; import { Link } from "react-router"; import { ChevronDown } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { SectionCardHeader } from "~/components/ui/SectionCardHeader"; import { BRACKT_GRADIENT } from "~/lib/brand"; import { BracketDecor } from "./BracketDecor"; import { SPORTS, HOW_IT_WORKS_STEPS, FEATURES } from "~/lib/landing-data"; // ─── Slot Machine ──────────────────────────────────────────────────────────── 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} ); } // ─── 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} ))}
); } // ─── 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}

))}
{/* ── Footer CTA ── */}

Stop managing your league and start watching it.

Brackt is built for the sports fan who wants a stake in everything without the daily chore of a traditional fantasy roster. Setting up a league takes less than five minutes. Choose your sports, invite your friends, and let the draft decide the winner.

); }