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 { 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 { Card, CardContent } from "~/components/ui/card";
import { SectionCardHeader } from "~/components/ui/SectionCardHeader";
@ -21,15 +21,17 @@ function shuffle<T>(arr: T[]): T[] {
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 {
const pct = tick / TOTAL_TICKS;
return Math.round(130 - (130 - 38) * (pct * pct));
const pct = Math.min((tick / TOTAL_TICKS) * 1.5, 1);
return Math.round(800 - (800 - 340) * (pct * pct));
}
function SlotMachineHeadline() {
const [text, setText] = useState("");
const [text, setText] = useState("\u00A0");
const [landed, setLanded] = useState(false);
const [animKey, setAnimKey] = useState(0);
const tickRef = useRef(0);
@ -54,7 +56,7 @@ function SlotMachineHeadline() {
timerRef.current = setTimeout(tick, getInterval(tickRef.current));
}
timerRef.current = setTimeout(tick, 130);
timerRef.current = setTimeout(tick, 800);
return () => {
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 ─────────────────────────────────────────────────────────────
const TICKER_ITEMS = [
@ -245,17 +266,17 @@ 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.",
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: "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,
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) {
// Triangles reference #brackt-primary-gradient defined in <BracktGradients /> (mounted in root.tsx)
howItWorksItems.push(
<div key={`arrow-${step.title}`} className="hidden shrink-0 md:flex md:items-center md:justify-center md:px-1">
<svg width="20" height="20" viewBox="0 0 20 20">
<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" className="rotate-90 md:rotate-0">
<polygon
points="3,2 18,10 3,18"
fill="url(#brackt-primary-gradient)"
@ -329,11 +350,13 @@ export function LandingPage() {
<BracketDecor flip />
</div>
<ScrollHint />
{/* Center content */}
<div className="relative z-10 w-full max-w-[680px] text-center">
<h1
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>
<SlotMachineHeadline />
@ -347,7 +370,7 @@ export function LandingPage() {
Compete with your friends in a league all season long.
</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">
<Link to="/leagues/new">Create a League</Link>
</Button>