brackt/app/routes/how-to-play.tsx
Chris Parsons 9ed0282fd0
New design (#309)
* Redesign home page with new layout and component system

- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Responsive league row layout and mobile polish

- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Improve claude file.

* Add StandingsPreview card component with podium row styling

- New StandingsPreview component with gold/silver/bronze row tints for
  top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
  with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
  preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
  BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
  coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
  fix was sufficient once gradientUnits was corrected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update components on league homepage.

* Finish up league page styling.

* Work on standings page.

* Add story for RecentScoresCard

* Update Point Progression Chart.

* Sort point progression legend by ranking and add team links to standings rows

* Fix standings discrepancy on change.

* Create draft cell component.

* Update draft board page

* Draft room improvements.

* Update some draft room styling.

* Fix context menu missing.

* Move tab navigation and autodraft to header row, narrow sidebar

* Virtualize available participants list, memoize draft room props

Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update draft room UI.

* More draft room fixes.

* Draft room tweaks.

* Fix Rosters page.

* Queue Section fixes.

* Mobile Draft fixes.

* Fix draft board page.

* Create bracket look.

* Bracket work.

* Finish bracket page.

* Homepage initial styling

* homepage copy

* Add privacy policy. Fixes #88.

* how to play copy

* rules copy

* Fix brackets on homepage.

* Add footer to website.

* Glow on dots.

* Landing page copy.

* Fix sidebar.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00

347 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Link } from "react-router";
import { Button } from "app/components/ui/button";
export function meta() {
return [
{ title: "How To Play - Brackt" },
{
name: "description",
content:
"Learn how to play Brackt and compete in multi-sport fantasy leagues",
},
];
}
export default function HowToPlay() {
return (
<div className="container mx-auto px-4 py-8 max-w-4xl">
<h1 className="text-4xl font-bold mb-2">How To Play</h1>
<p className="text-lg text-muted-foreground mb-10">
Brackt is a multi-sport fantasy league where you draft athletes and
teams across dozens of sports, then watch your roster compete all year
long. Here's everything you need to know to get started.
</p>
<div className="space-y-10">
{/* The Big Picture */}
<section>
<h2 className="text-2xl font-semibold mb-3">The Big Picture</h2>
<p className="text-muted-foreground">
Before the season starts, everyone in the league participates in a
draft by picking real teams (and athletes in individual sports) across a set of sports
chosen by your commissioner. Over the following months, those picks
compete in their actual seasons. When it's all said and done,
whoever earned the most points from their picks wins.
</p>
</section>
{/* Building Your Roster */}
<section>
<h2 className="text-2xl font-semibold mb-3">Building Your Roster</h2>
<p className="text-muted-foreground mb-4">
Your league includes a set of sports selected by the commissioner, such as
the NFL, NBA, Golf, Formula 1, Tennis, and more. Your roster has
one dedicated spot for each sport, so you need to draft at least
one pick from every sport in the league.
</p>
<p className="text-muted-foreground mb-4">
Extra draft rounds beyond the number of sports create{" "}
<span className="font-semibold text-foreground">flex spots</span>.
Flex picks can come from any sport. These are great for grabbing a
second (or even a third and a fourth!) player in a sport where you
see value, or doubling down on a safe bet. How you use your flex
spots can be the difference between winning the league and
finishing at the bottom of the table.
</p>
<div className="bg-muted rounded-lg p-4 text-sm text-muted-foreground">
<p>
<span className="font-semibold text-foreground">Example:</span>{" "}
Your league has 20 sports and 24 draft rounds. That means you
fill 20 dedicated sport spots and have 4 flex picks to use
however you like.
</p>
</div>
</section>
{/* How Points Work */}
<section>
<h2 className="text-2xl font-semibold mb-3">How Points Work</h2>
<p className="text-muted-foreground mb-4">
Points are awarded based on where your picks finish: 1st through
8th. The better they finish, the more you earn. Your commissioner
can adjust point values before the draft, but here's what a typical
league looks like:
</p>
<div className="bg-muted rounded-lg overflow-hidden mb-4">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left p-3 font-semibold text-foreground">
Finish
</th>
<th className="text-right p-3 font-semibold text-foreground">
Points
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
<tr>
<td className="p-3">1st Place</td>
<td className="p-3 text-right font-semibold text-foreground">
100
</td>
</tr>
<tr>
<td className="p-3">2nd Place</td>
<td className="p-3 text-right font-semibold text-foreground">
70
</td>
</tr>
<tr>
<td className="p-3">3rd Place</td>
<td className="p-3 text-right font-semibold text-foreground">
50
</td>
</tr>
<tr>
<td className="p-3">4th Place</td>
<td className="p-3 text-right font-semibold text-foreground">
40
</td>
</tr>
<tr>
<td className="p-3">5th6th Place</td>
<td className="p-3 text-right font-semibold text-foreground">
25
</td>
</tr>
<tr>
<td className="p-3">7th8th Place</td>
<td className="p-3 text-right font-semibold text-foreground">
15
</td>
</tr>
</tbody>
</table>
</div>
<p className="text-sm text-muted-foreground">
If nobody in your league drafted a team that finishes in the top 8,
those points aren't awarded to anyone in your league. In other
words, if no one picks Indiana to win the NCAA Football
championship, the manager who drafted Miami, the team that lost to
them, still only would receive the 2nd place points.
</p>
</section>
{/* Major-Based Sports */}
<section>
<h2 className="text-2xl font-semibold mb-3">Sports with Majors</h2>
<p className="text-muted-foreground mb-4">
Some sports don't have a single championship, they have multiple
major tournaments spread throughout the year. Classic examples are
golf and tennis, but other sports may be scored by majors as well.
You can see on your league pages how each sport is scored. For
these sports with majors,
players earn{" "}
<span className="font-semibold text-foreground">
qualifying points (QP)
</span>{" "}
at each one:
</p>
<div className="bg-muted rounded-lg overflow-hidden mb-4">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left p-3 font-semibold text-foreground">
Major Finish
</th>
<th className="text-right p-3 font-semibold text-foreground">
Qualifying Points
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
<tr>
<td className="p-3">1st</td>
<td className="p-3 text-right font-semibold text-foreground">
20 QP
</td>
</tr>
<tr>
<td className="p-3">2nd</td>
<td className="p-3 text-right font-semibold text-foreground">
14 QP
</td>
</tr>
<tr>
<td className="p-3">3rd</td>
<td className="p-3 text-right font-semibold text-foreground">
10 QP
</td>
</tr>
<tr>
<td className="p-3">4th</td>
<td className="p-3 text-right font-semibold text-foreground">
8 QP
</td>
</tr>
<tr>
<td className="p-3">5th6th</td>
<td className="p-3 text-right font-semibold text-foreground">
5 QP
</td>
</tr>
<tr>
<td className="p-3">7th8th</td>
<td className="p-3 text-right font-semibold text-foreground">
3 QP
</td>
</tr>
<tr>
<td className="p-3">9th12th</td>
<td className="p-3 text-right font-semibold text-foreground">
2 QP
</td>
</tr>
<tr>
<td className="p-3">13th16th</td>
<td className="p-3 text-right font-semibold text-foreground">
1 QP
</td>
</tr>
</tbody>
</table>
</div>
<p className="text-muted-foreground">
QP don't go straight to your score they determine the final
ranking within that sport. Once all the majors are done, total QP
are added up, and whoever's ranked 1st through 8th earns the
standard league points. Consistency across majors is the key.
</p>
</section>
{/* The Draft Room */}
<section>
<h2 className="text-2xl font-semibold mb-3">The Draft Room</h2>
<p className="text-muted-foreground mb-6">
Because Brackt is a "draft and done" league, the draft is your only
opportunity to take action. Once the final pick is made, your roster
is locked for the entire year. There are no trades, waivers, or
mid-season pickups. Your success depends entirely on the strategy
you execute during the draft.
</p>
{/* Timing Mechanics */}
<div className="space-y-3 mb-6">
<h3 className="text-lg font-semibold">Timing Mechanics</h3>
<p className="text-muted-foreground">
The draft uses a snake format where the pick order reverses every
round. You can configure the timing of the draft using one of two
primary mechanics:
</p>
<ul className="space-y-2 text-muted-foreground pl-4">
<li>
<span className="font-semibold text-foreground">
Standard Clock:
</span>{" "}
A traditional fixed countdown (such as 60 seconds per pick). It
is a straightforward and consistent pace.
</li>
<li>
<span className="font-semibold text-foreground">
Chess Clock (Fischer Increment):
</span>{" "}
This system uses a Time Bank and an Increment. You start with a
pool of time (for example, 2 minutes). Each time you make a
pick, a set bonus like 15 seconds is added back to your bank.
This allows you to pick quickly in the early rounds to save up
time for more difficult decisions later on.
</li>
</ul>
</div>
{/* Flexible Pacing */}
<div className="space-y-3 mb-6">
<h3 className="text-lg font-semibold">Flexible Pacing</h3>
<p className="text-muted-foreground">
These mechanics can be applied to any draft speed. Whether you
want to finish in an hour or over the course of a week, the system
scales to your needs:
</p>
<ul className="space-y-2 text-muted-foreground pl-4">
<li>
<span className="font-semibold text-foreground">
Live Drafts:
</span>{" "}
Uses short timers (seconds or minutes) for a fast-paced,
single-session event.
</li>
<li>
<span className="font-semibold text-foreground">
Slow Drafts:
</span>{" "}
Uses extended timers (hours) for leagues that want to draft over
several days. Slow drafts include a customizable{" "}
<span className="font-semibold text-foreground">
Overnight Pause
</span>
. The commissioner sets a uniform window (such as 10:00 PM to
8:00 AM) where the clock stops for everyone. This ensures no one
is forced to make a pick in the middle of the night.
</li>
</ul>
</div>
{/* Queue and Autodraft */}
<div className="space-y-3">
<h3 className="text-lg font-semibold">Queue and Autodraft</h3>
<p className="text-muted-foreground">
To keep the draft moving regardless of the pace, you can use the{" "}
<span className="font-semibold text-foreground">Draft Queue</span>{" "}
to pre-rank your targets. If your clock expires, the system
automatically selects the highest available player from your
queue. If your queue is empty, the system will select the
highest-ranked athlete remaining in the pool to ensure the draft
never stalls.
</p>
</div>
</section>
{/* During the Season */}
<section>
<h2 className="text-2xl font-semibold mb-3">After the Draft</h2>
<p className="text-muted-foreground mb-4">
Once the draft ends, your roster is locked. No trades, no waivers,
no pickups. Just watch your picks compete across the year and see
how your draft decisions play out. Points accumulate as sports
seasons wrap up, and standings update in real time.
</p>
<p className="text-muted-foreground">
If there's a tie in the final standings, it's broken by who has
more 1st-place finishes. If that's still a tie, it goes to
2nd-place finishes, then 3rd, and so on.
</p>
</section>
{/* Links */}
<section className="flex flex-col sm:flex-row gap-4 items-center justify-between pt-2">
<p className="text-muted-foreground">
Want the full breakdown? Read the{" "}
<Link
to="/rules"
className="underline underline-offset-4 hover:text-foreground transition-colors"
>
official rules
</Link>
.
</p>
<Button asChild size="lg">
<Link to="/leagues/new">Create a League</Link>
</Button>
</section>
</div>
</div>
);
}