From 654edbf27167288b09b115cfc94d55d2d29b2465 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Wed, 22 Apr 2026 14:44:09 -0700 Subject: [PATCH] homepage copy --- app/components/marketing/LandingPage.tsx | 49 +++++++++++++++++------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/app/components/marketing/LandingPage.tsx b/app/components/marketing/LandingPage.tsx index f2f49c9..c64f9d1 100644 --- a/app/components/marketing/LandingPage.tsx +++ b/app/components/marketing/LandingPage.tsx @@ -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(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 ( +
+ +
+ ); +} + // ─── 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 (mounted in root.tsx) howItWorksItems.push( -
- +
+
+ + {/* Center content */}

Fantasy @@ -347,7 +370,7 @@ export function LandingPage() { Compete with your friends in a league all season long.

-
+