homepage copy

This commit is contained in:
Chris Parsons 2026-04-22 14:44:09 -07:00
parent 8fadc27c75
commit 654edbf271

View file

@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import { Users, Shuffle, Trophy } from "lucide-react"; import { Users, Shuffle, Trophy, ChevronDown } from "lucide-react";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card"; import { Card, CardContent } from "~/components/ui/card";
import { SectionCardHeader } from "~/components/ui/SectionCardHeader"; import { SectionCardHeader } from "~/components/ui/SectionCardHeader";
@ -21,15 +21,17 @@ function shuffle<T>(arr: T[]): T[] {
return a; return a;
} }
const TOTAL_TICKS = 36; 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 { function getInterval(tick: number): number {
const pct = tick / TOTAL_TICKS; const pct = Math.min((tick / TOTAL_TICKS) * 1.5, 1);
return Math.round(130 - (130 - 38) * (pct * pct)); return Math.round(800 - (800 - 340) * (pct * pct));
} }
function SlotMachineHeadline() { function SlotMachineHeadline() {
const [text, setText] = useState(""); const [text, setText] = useState("\u00A0");
const [landed, setLanded] = useState(false); const [landed, setLanded] = useState(false);
const [animKey, setAnimKey] = useState(0); const [animKey, setAnimKey] = useState(0);
const tickRef = useRef(0); const tickRef = useRef(0);
@ -54,7 +56,7 @@ function SlotMachineHeadline() {
timerRef.current = setTimeout(tick, getInterval(tickRef.current)); timerRef.current = setTimeout(tick, getInterval(tickRef.current));
} }
timerRef.current = setTimeout(tick, 130); timerRef.current = setTimeout(tick, 800);
return () => { return () => {
if (timerRef.current) clearTimeout(timerRef.current); if (timerRef.current) clearTimeout(timerRef.current);
}; };
@ -207,6 +209,25 @@ function BracketDecor({ flip = false }: { flip?: boolean }) {
); );
} }
// ─── 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 (
<div
className="pointer-events-none absolute bottom-8 left-1/2 -translate-x-1/2 animate-bounce transition-opacity duration-500"
style={{ opacity: visible ? 0.35 : 0 }}
>
<ChevronDown className="h-6 w-6 text-white" />
</div>
);
}
// ─── Sport Ticker ───────────────────────────────────────────────────────────── // ─── Sport Ticker ─────────────────────────────────────────────────────────────
const TICKER_ITEMS = [ const TICKER_ITEMS = [
@ -245,17 +266,17 @@ const HOW_IT_WORKS_STEPS = [
{ {
icon: Users, icon: Users,
title: "Create a League", title: "Create a League",
body: "Name it, pick your sports, invite your friends with a link. Takes 30 seconds.", body: "Start a league, pick your sports (we can help), and invite your friends. It takes 30 seconds to set the stage.",
}, },
{ {
icon: Shuffle, icon: Shuffle,
title: "Draft Your Teams", title: "Draft Your Teams",
body: "Snake draft — everyone picks teams from any sport on the calendar. NHL, NBA, World Cup, whatever is running.", 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, icon: Trophy,
title: "Watch & Win", title: "Watch & Win",
body: "Your teams earn points as they win real games. The bracket updates live. Last one standing takes it.", 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.",
}, },
]; ];
@ -301,8 +322,8 @@ export function LandingPage() {
if (i < HOW_IT_WORKS_STEPS.length - 1) { if (i < HOW_IT_WORKS_STEPS.length - 1) {
// Triangles reference #brackt-primary-gradient defined in <BracktGradients /> (mounted in root.tsx) // Triangles reference #brackt-primary-gradient defined in <BracktGradients /> (mounted in root.tsx)
howItWorksItems.push( howItWorksItems.push(
<div key={`arrow-${step.title}`} className="hidden shrink-0 md:flex md:items-center md:justify-center md:px-1"> <div key={`arrow-${step.title}`} className="flex shrink-0 items-center justify-center py-1 md:px-1 md:py-0">
<svg width="20" height="20" viewBox="0 0 20 20"> <svg width="20" height="20" viewBox="0 0 20 20" className="rotate-90 md:rotate-0">
<polygon <polygon
points="3,2 18,10 3,18" points="3,2 18,10 3,18"
fill="url(#brackt-primary-gradient)" fill="url(#brackt-primary-gradient)"
@ -329,11 +350,13 @@ export function LandingPage() {
<BracketDecor flip /> <BracketDecor flip />
</div> </div>
<ScrollHint />
{/* Center content */} {/* Center content */}
<div className="relative z-10 w-full max-w-[680px] text-center"> <div className="relative z-10 w-full max-w-[680px] text-center">
<h1 <h1
className="mb-0 leading-[0.95] tracking-[-0.04em]" className="mb-0 leading-[0.95] tracking-[-0.04em]"
style={{ fontSize: "clamp(52px, 8vw, 88px)", fontWeight: 900 }} style={{ fontSize: "clamp(38px, 8vw, 88px)", fontWeight: 900 }}
> >
<span className="block text-white">Fantasy</span> <span className="block text-white">Fantasy</span>
<SlotMachineHeadline /> <SlotMachineHeadline />
@ -347,7 +370,7 @@ export function LandingPage() {
Compete with your friends in a league all season long. Compete with your friends in a league all season long.
</p> </p>
<div className="mt-8 flex items-center justify-center gap-2.5"> <div className="mt-8 flex flex-col items-center justify-center gap-2.5 sm:flex-row">
<Button asChild size="lg" className="text-sm font-extrabold"> <Button asChild size="lg" className="text-sm font-extrabold">
<Link to="/leagues/new">Create a League</Link> <Link to="/leagues/new">Create a League</Link>
</Button> </Button>