Fix brackets on homepage.

This commit is contained in:
Chris Parsons 2026-04-22 22:17:57 -07:00
parent 924ad5caee
commit 886f9f1847

View file

@ -31,7 +31,7 @@ function getInterval(tick: number): number {
} }
function SlotMachineHeadline() { function SlotMachineHeadline() {
const [text, setText] = useState("\u00A0"); const [text, setText] = useState(SPORTS[0]);
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);
@ -82,129 +82,130 @@ function SlotMachineHeadline() {
// ─── Bracket Decoration ────────────────────────────────────────────────────── // ─── Bracket Decoration ──────────────────────────────────────────────────────
const LIME = "#adf661"; function BracketDecor({ flip = false, mobileTall = false }: { flip?: boolean; mobileTall?: boolean }) {
const LIME_DIM = "rgba(173,246,97,0.07)"; const base = flip ? "r" : "l";
const LIME_DIM_BORDER = "rgba(173,246,97,0.25)"; const id = mobileTall ? `${base}-m` : base;
const LIME_CHAMP = "rgba(173,246,97,0.12)"; const W = 399;
const LIME_CHAMP_BORDER = "rgba(173,246,97,0.8)"; const H = mobileTall ? 2000 : 560;
const SLOT_BG = "rgba(32,33,36,0.95)";
const SLOT_BORDER = "rgba(255,255,255,0.10)";
const LINE_COLOR_R1 = "rgba(255,255,255,0.15)";
const LINE_COLOR_R2 = "rgba(255,255,255,0.28)";
// SVG coordinate-space layout constants const pad = 36;
const SW = 80; const spacing = (H - 2 * pad) / 7;
const SH = 20; const r1Ys = Array.from({ length: 8 }, (_, i) => pad + i * spacing);
const GAP = 10; const r2Ys = [0, 1, 2, 3].map((i) => (r1Ys[i * 2] + r1Ys[i * 2 + 1]) / 2);
const PAIR_H = SH * 2 + GAP; const r3Ys = [0, 1].map((i) => (r2Ys[i * 2] + r2Ys[i * 2 + 1]) / 2);
const R1_X = 10; const champY = (r3Ys[0] + r3Ys[1]) / 2;
const R2_X = 120;
const R3_X = 210;
const BRACKET_W = 300;
const BRACKET_H = 360;
const R1_TOP = 50;
const PAIR_GAP = 20;
type SlotDef = { const x0 = 4;
x: number; y: number; w: number; h: number; const x1 = 77;
winner?: boolean; champ?: boolean; const x2 = 161;
}; const x3 = 252;
function BracketSlot({ x, y, w, h, winner, champ }: SlotDef) { const gradId = `grad-${id}`;
const bg = champ ? LIME_CHAMP : winner ? LIME_DIM : SLOT_BG; const glowId = `glow-${id}`;
const border = champ ? LIME_CHAMP_BORDER : winner ? LIME_DIM_BORDER : SLOT_BORDER; const finalsGlowId = `finals-glow-${id}`;
const bw = champ ? 1.5 : 1; const finalsHaloId = `finals-halo-${id}`;
return ( const finalsFadeMaskGradId = `finals-fade-mask-grad-${id}`;
<g> const finalsFadeMaskId = `finals-fade-mask-${id}`;
<rect x={x} y={y} width={w} height={h} rx={2} fill={bg} stroke={border} strokeWidth={bw} /> const finalsLineGlowId = `finals-line-glow-${id}`;
{(winner || champ) && (
<rect x={x} y={y} width={3} height={h} rx={1} fill={LIME} />
)}
{champ && (
<text
x={x + w / 2} y={y + h + 10}
textAnchor="middle"
fontSize={7} fontWeight={700} fill={LIME}
style={{ fontFamily: "Barlow, sans-serif", letterSpacing: "0.08em" }}
>
CHAMP
</text>
)}
</g>
);
}
function hLine(x1: number, y1: number, x2: number, color: string) {
return <line x1={x1} y1={y1} x2={x2} y2={y1} stroke={color} strokeWidth={1} />;
}
function connector(ax: number, ay: number, bx: number, by: number, cx: number, color: string) {
const midY = (ay + by) / 2;
return (
<path d={`M${ax} ${ay} H${cx} V${midY} H${bx}`} fill="none" stroke={color} strokeWidth={1} />
);
}
function BracketDecor({ flip = false }: { flip?: boolean }) {
const r1Pairs: [SlotDef, SlotDef][] = Array.from({ length: 4 }, (_, i) => {
const pairY = R1_TOP + i * (PAIR_H + PAIR_GAP);
const winner = i % 3 !== 1;
return [
{ x: R1_X, y: pairY, w: SW, h: SH, winner: winner && i % 2 === 0 },
{ x: R1_X, y: pairY + SH + GAP, w: SW, h: SH, winner: winner && i % 2 !== 0 },
];
});
const r2Slots: SlotDef[] = [0, 1].map((i) => {
const top1Y = R1_TOP + (i * 2) * (PAIR_H + PAIR_GAP);
const bot2Y = R1_TOP + (i * 2 + 1) * (PAIR_H + PAIR_GAP) + SH + GAP;
const midY = (top1Y + bot2Y) / 2 - SH / 2;
return { x: R2_X, y: midY, w: SW, h: SH, winner: i === 0 };
});
const r3Y = (r2Slots[0].y + r2Slots[1].y + SH) / 2 - SH / 2;
const r3Slot: SlotDef = { x: R3_X, y: r3Y, w: SW, h: SH + 4, champ: true };
return ( return (
<svg <svg
width={BRACKET_W} height={BRACKET_H} viewBox={`0 0 ${W} ${H}`}
style={{ style={{
position: "absolute", position: "absolute",
top: "50%", top: "50%",
transform: `translateY(-52%)${flip ? " scaleX(-1)" : ""}`, transform: `translateY(-52%)${flip ? " scaleX(-1)" : ""}`,
[flip ? "right" : "left"]: 0, [flip ? "right" : "left"]: 0,
opacity: 0.75,
pointerEvents: "none", pointerEvents: "none",
height: mobileTall ? "auto" : "75vh",
width: mobileTall ? "32vw" : "auto",
}} }}
> >
{r1Pairs.map(([a, b], i) => ( <defs>
<g key={`r1-${a.y}`}> <linearGradient id={gradId} x1="0" y1="0" x2="0" y2={H} gradientUnits="userSpaceOnUse">
<BracketSlot {...a} /> <stop offset="0%" stopColor="#adf661" />
<BracketSlot {...b} /> <stop offset="100%" stopColor="#2ce1c1" />
{connector( </linearGradient>
R1_X + SW, a.y + SH / 2, <filter id={glowId} x="-50%" y="-50%" width="200%" height="200%">
r2Slots[Math.floor(i / 2)].x, r2Slots[Math.floor(i / 2)].y + SH / 2, <feGaussianBlur stdDeviation="3" result="blur" />
R1_X + SW + 14, <feMerge>
LINE_COLOR_R1 <feMergeNode in="blur" />
)} <feMergeNode in="SourceGraphic" />
{hLine(R1_X + SW + 14, (a.y + SH / 2 + b.y + SH / 2) / 2, R2_X, LINE_COLOR_R1)} </feMerge>
</g> </filter>
))} <filter id={finalsGlowId} filterUnits="userSpaceOnUse"
x={x2 - 15} y={r3Ys[0] - 15} width={x3 - x2 + 30} height={r3Ys[1] - r3Ys[0] + 30}>
{r2Slots.map((s, ri) => ( <feGaussianBlur stdDeviation="7" result="blur" />
<g key={`r2-${s.y}`}> <feMerge>
<BracketSlot {...s} /> <feMergeNode in="blur" />
{connector( <feMergeNode in="SourceGraphic" />
R2_X + SW, r2Slots[0].y + SH / 2, </feMerge>
r3Slot.x, r3Slot.y + (r3Slot.h ?? SH) / 2, </filter>
R2_X + SW + 14, <filter id={finalsHaloId} filterUnits="userSpaceOnUse"
LINE_COLOR_R2 x={x2 - 40} y={r3Ys[0] - 40} width={x3 - x2 + 80} height={r3Ys[1] - r3Ys[0] + 80}>
)} <feGaussianBlur stdDeviation="16" />
{ri === 1 && hLine(R2_X + SW + 14, (r2Slots[0].y + SH / 2 + r2Slots[1].y + SH / 2) / 2, R3_X, LINE_COLOR_R2)} </filter>
</g> <linearGradient id={finalsFadeMaskGradId} x1={x3} y1="0" x2={W} y2="0" gradientUnits="userSpaceOnUse">
))} <stop offset="0%" stopColor="white" />
<stop offset="50%" stopColor="white" />
<BracketSlot {...r3Slot} /> <stop offset="100%" stopColor="white" stopOpacity="0" />
</linearGradient>
<mask id={finalsFadeMaskId} maskUnits="userSpaceOnUse" x={x3} y={champY - 20} width={W - x3} height={40}>
<rect x="0" y="0" width={W} height={H} fill={`url(#${finalsFadeMaskGradId})`} />
</mask>
<filter id={finalsLineGlowId} filterUnits="userSpaceOnUse" x={x3 - 10} y={champY - 15} width={W - x3 + 20} height={30}>
<feGaussianBlur stdDeviation="3" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<g filter={`url(#${finalsHaloId})`} stroke={`url(#${gradId})`} strokeWidth={2} fill="none" opacity={0.75}>
{r3Ys.map((midY) => (
<line key={midY} x1={x2} y1={midY} x2={x3} y2={midY} />
))}
<line x1={x3} y1={r3Ys[0]} x2={x3} y2={r3Ys[1]} />
</g>
<g filter={`url(#${finalsGlowId})`} stroke={`url(#${gradId})`} strokeWidth={1.5} fill="none">
{r3Ys.map((midY) => (
<line key={midY} x1={x2} y1={midY} x2={x3} y2={midY} />
))}
<line x1={x3} y1={r3Ys[0]} x2={x3} y2={r3Ys[1]} />
</g>
<g filter={`url(#${glowId})`} stroke={`url(#${gradId})`} strokeWidth={1.5} fill="none">
{r1Ys.map((y) => (
<g key={y}>
<circle cx={x0} cy={y} r={3} fill={`url(#${gradId})`} stroke="none" />
<line x1={x0} y1={y} x2={x1} y2={y} />
</g>
))}
{r2Ys.map((midY, i) => (
<g key={midY}>
<line x1={x1} y1={r1Ys[i * 2]} x2={x1} y2={r1Ys[i * 2 + 1]} />
<circle cx={x1} cy={midY} r={3} fill={`url(#${gradId})`} stroke="none" />
<line x1={x1} y1={midY} x2={x2} y2={midY} />
</g>
))}
{r3Ys.map((midY, i) => (
<g key={midY}>
<line x1={x2} y1={r2Ys[i * 2]} x2={x2} y2={r2Ys[i * 2 + 1]} />
<circle cx={x2} cy={midY} r={3} fill={`url(#${gradId})`} stroke="none" />
<line x1={x2} y1={midY} x2={x3} y2={midY} />
</g>
))}
<line x1={x3} y1={r3Ys[0]} x2={x3} y2={r3Ys[1]} />
<circle cx={x3} cy={champY} r={3} fill={`url(#${gradId})`} stroke="none" />
</g>
<line
x1={x3} y1={champY} x2={W} y2={champY}
stroke={`url(#${gradId})`}
strokeWidth={1.5}
fill="none"
filter={`url(#${finalsLineGlowId})`}
mask={`url(#${finalsFadeMaskId})`}
/>
</svg> </svg>
); );
} }
@ -303,6 +304,7 @@ const FOOTER_LINKS = [
{ label: "Home", to: "/" }, { label: "Home", to: "/" },
{ label: "Rules", to: "/rules" }, { label: "Rules", to: "/rules" },
{ label: "How to Play", to: "/how-to-play" }, { label: "How to Play", to: "/how-to-play" },
{ label: "Support", to: "/support" },
{ label: "Privacy Policy", to: "/privacy-policy" }, { label: "Privacy Policy", to: "/privacy-policy" },
]; ];
@ -345,7 +347,23 @@ export function LandingPage() {
className="relative flex flex-col items-center justify-center overflow-hidden px-8" className="relative flex flex-col items-center justify-center overflow-hidden px-8"
style={{ minHeight: "calc(100vh - var(--navbar-height))" }} style={{ minHeight: "calc(100vh - var(--navbar-height))" }}
> >
{/* Bracket decorations — lg+ only */} {/* Dot-grid texture */}
<div
aria-hidden
className="pointer-events-none absolute inset-0"
style={{
backgroundImage: "radial-gradient(circle, rgba(255,255,255,0.07) 1px, transparent 1px)",
backgroundSize: "32px 32px",
maskImage: "radial-gradient(ellipse 85% 85% at 50% 50%, black 30%, transparent 100%)",
WebkitMaskImage: "radial-gradient(ellipse 85% 85% at 50% 50%, black 30%, transparent 100%)",
}}
/>
{/* Bracket decorations */}
<div className="lg:hidden">
<BracketDecor mobileTall />
<BracketDecor flip mobileTall />
</div>
<div className="hidden lg:block"> <div className="hidden lg:block">
<BracketDecor /> <BracketDecor />
<BracketDecor flip /> <BracketDecor flip />
@ -354,7 +372,7 @@ export function LandingPage() {
<ScrollHint /> <ScrollHint />
{/* Center content */} {/* Center content */}
<div className="relative z-10 w-full max-w-[680px] text-center"> <div className="relative z-10 w-[80%] lg:w-full max-w-[680px] text-center px-6 py-4 lg:px-0 lg:py-0">
<h1 <h1
className="mb-0 leading-[0.95] tracking-[-0.04em]" className="mb-0 leading-[0.95] tracking-[-0.04em]"
style={{ fontSize: "clamp(38px, 8vw, 88px)", fontWeight: 900 }} style={{ fontSize: "clamp(38px, 8vw, 88px)", fontWeight: 900 }}
@ -365,10 +383,10 @@ export function LandingPage() {
<p <p
className="mx-auto mt-8 text-muted-foreground" className="mx-auto mt-8 text-muted-foreground"
style={{ fontSize: "16px", fontWeight: 400, lineHeight: 1.65, maxWidth: "460px" }} style={{ fontSize: "16px", fontWeight: 400, lineHeight: 1.65, maxWidth: "414px" }}
> >
Draft teams across every sport NHL, NBA, NFL, EPL, World Cup, and more. Draft teams across every league and sport, like the NFL, NHL, EPL, F1, and more.
Compete with your friends in a league all season long. Compete against your friends in a season-long league.
</p> </p>
<div className="mt-8 flex flex-col items-center justify-center gap-2.5 sm:flex-row"> <div className="mt-8 flex flex-col items-center justify-center gap-2.5 sm:flex-row">