Add rules page, rewrite how-to-play, and fix scoring display (#20)
- Add new /rules route with official league rules covering rosters, scoring, major-based QP scoring, tiebreakers, draft, and season rules - Rewrite how-to-play page with a tutorial/marketing tone, highlighting the Fischer increment draft clock as a novel feature - Add Rules link to navbar (desktop and mobile) - Align QP values in both pages with DEFAULT_QP_VALUES in code - Fix tiebreaker description to match placement-count logic in code - Update all fantasy point displays from toFixed(1) to toFixed(2) across StandingsTable, TeamScoreBreakdown, and PointProgressionChart - Update tests to match new two-decimal-place point display format Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7a8ea60e77
commit
e9798263b5
10 changed files with 721 additions and 166 deletions
|
|
@ -136,7 +136,7 @@ export function StandingsTable({
|
||||||
)}
|
)}
|
||||||
<TableCell className="font-medium">{row.teamName}</TableCell>
|
<TableCell className="font-medium">{row.teamName}</TableCell>
|
||||||
<TableCell className="text-right font-semibold">
|
<TableCell className="text-right font-semibold">
|
||||||
{row.totalPoints.toFixed(1)}
|
{row.totalPoints.toFixed(2)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{showPlacementBreakdown && (
|
{showPlacementBreakdown && (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ export function Navbar({ isAdmin }: NavbarProps) {
|
||||||
<Link to="/how-to-play">How To Play</Link>
|
<Link to="/how-to-play">How To Play</Link>
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
|
||||||
|
<Link to="/rules">Rules</Link>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
|
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
|
||||||
|
|
@ -110,6 +115,13 @@ export function Navbar({ isAdmin }: NavbarProps) {
|
||||||
>
|
>
|
||||||
How To Play
|
How To Play
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/rules"
|
||||||
|
className="block px-4 py-2 text-lg font-medium hover:bg-accent rounded-md transition-colors"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
Rules
|
||||||
|
</Link>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Link
|
<Link
|
||||||
to="/admin"
|
to="/admin"
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export function PointProgressionChart({ chartData, teams }: PointProgressionChar
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
}}
|
}}
|
||||||
labelFormatter={(label) => formatDate(label as string)}
|
labelFormatter={(label) => formatDate(label as string)}
|
||||||
formatter={(value: number) => [value.toFixed(1), '']}
|
formatter={(value: number) => [value.toFixed(2), '']}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
wrapperStyle={{ paddingTop: '20px' }}
|
wrapperStyle={{ paddingTop: '20px' }}
|
||||||
|
|
|
||||||
|
|
@ -64,18 +64,18 @@ export function StandingsTable({
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right font-semibold">
|
<TableCell className="text-right font-semibold">
|
||||||
{standing.actualPoints !== null && standing.actualPoints !== undefined
|
{standing.actualPoints !== null && standing.actualPoints !== undefined
|
||||||
? standing.actualPoints.toFixed(1)
|
? standing.actualPoints.toFixed(2)
|
||||||
: standing.totalPoints.toFixed(1)}
|
: standing.totalPoints.toFixed(2)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{standing.projectedPoints !== null && standing.projectedPoints !== undefined ? (
|
{standing.projectedPoints !== null && standing.projectedPoints !== undefined ? (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-semibold text-primary">
|
<span className="font-semibold text-primary">
|
||||||
{standing.projectedPoints.toFixed(1)}
|
{standing.projectedPoints.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
{standing.participantsRemaining > 0 && (
|
{standing.participantsRemaining > 0 && (
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
+{(standing.projectedPoints - (standing.actualPoints ?? standing.totalPoints)).toFixed(1)} EV
|
+{(standing.projectedPoints - (standing.actualPoints ?? standing.totalPoints)).toFixed(2)} EV
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -93,14 +93,14 @@ export function TeamScoreBreakdown({
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-4xl font-bold text-primary">
|
<div className="text-4xl font-bold text-primary">
|
||||||
{breakdown.actualPoints.toFixed(1)}
|
{breakdown.actualPoints.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
Actual Points
|
Actual Points
|
||||||
</div>
|
</div>
|
||||||
{breakdown.projectedPoints > breakdown.actualPoints && (
|
{breakdown.projectedPoints > breakdown.actualPoints && (
|
||||||
<div className="text-xl font-semibold text-electric mt-1">
|
<div className="text-xl font-semibold text-electric mt-1">
|
||||||
{breakdown.projectedPoints.toFixed(1)}
|
{breakdown.projectedPoints.toFixed(2)}
|
||||||
<span className="text-xs text-muted-foreground ml-1">projected</span>
|
<span className="text-xs text-muted-foreground ml-1">projected</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -122,7 +122,7 @@ export function TeamScoreBreakdown({
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">
|
<div className="text-2xl font-bold">
|
||||||
{breakdown.actualPoints.toFixed(1)}
|
{breakdown.actualPoints.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
From {breakdown.completedCount} finished
|
From {breakdown.completedCount} finished
|
||||||
|
|
@ -138,10 +138,10 @@ export function TeamScoreBreakdown({
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-primary">
|
<div className="text-2xl font-bold text-primary">
|
||||||
{breakdown.projectedPoints.toFixed(1)}
|
{breakdown.projectedPoints.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
+{(breakdown.projectedPoints - breakdown.actualPoints).toFixed(1)} expected value
|
+{(breakdown.projectedPoints - breakdown.actualPoints).toFixed(2)} expected value
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -250,7 +250,7 @@ export function TeamScoreBreakdown({
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-2xl font-bold">{sportTotal.toFixed(1)}</div>
|
<div className="text-2xl font-bold">{sportTotal.toFixed(2)}</div>
|
||||||
<div className="text-xs text-muted-foreground">points</div>
|
<div className="text-xs text-muted-foreground">points</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -291,13 +291,13 @@ export function TeamScoreBreakdown({
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{pick.isComplete ? (
|
{pick.isComplete ? (
|
||||||
<span className="font-semibold">
|
<span className="font-semibold">
|
||||||
{pick.points > 0 ? pick.points.toFixed(1) : '0.0'}
|
{pick.points > 0 ? pick.points.toFixed(2) : '0.0'}
|
||||||
</span>
|
</span>
|
||||||
) : pick.projectedPoints !== null ? (
|
) : pick.projectedPoints !== null ? (
|
||||||
<div className="flex flex-col items-end">
|
<div className="flex flex-col items-end">
|
||||||
<span className="text-xs text-muted-foreground">Projected:</span>
|
<span className="text-xs text-muted-foreground">Projected:</span>
|
||||||
<span className="font-semibold text-primary">
|
<span className="font-semibold text-primary">
|
||||||
{pick.projectedPoints.toFixed(1)}
|
{pick.projectedPoints.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -93,9 +93,9 @@ describe("StandingsTable", () => {
|
||||||
it("should display total points for each team", () => {
|
it("should display total points for each team", () => {
|
||||||
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
|
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
|
||||||
|
|
||||||
expect(screen.getByText("250.5")).toBeInTheDocument();
|
expect(screen.getByText("250.50")).toBeInTheDocument();
|
||||||
expect(screen.getByText("245.0")).toBeInTheDocument();
|
expect(screen.getByText("245.00")).toBeInTheDocument();
|
||||||
expect(screen.getByText("200.0")).toBeInTheDocument();
|
expect(screen.getByText("200.00")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show empty state when no standings", () => {
|
it("should show empty state when no standings", () => {
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ describe("TeamScoreBreakdown", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("Test Team")).toBeInTheDocument();
|
expect(screen.getByText("Test Team")).toBeInTheDocument();
|
||||||
expect(screen.getAllByText("150.0").length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText("150.00").length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show team rank badge", () => {
|
it("should show team rank badge", () => {
|
||||||
|
|
@ -244,7 +244,7 @@ describe("TeamScoreBreakdown", () => {
|
||||||
|
|
||||||
// NFL total should be 100 (Team A)
|
// NFL total should be 100 (Team A)
|
||||||
// F1 total should be 50 (Driver B)
|
// F1 total should be 50 (Driver B)
|
||||||
const pointCells = screen.getAllByText(/^\d+\.\d$/)
|
const pointCells = screen.getAllByText(/^\d+\.\d\d$/)
|
||||||
.map((el) => parseFloat(el.textContent!));
|
.map((el) => parseFloat(el.textContent!));
|
||||||
|
|
||||||
expect(pointCells).toContain(100.0);
|
expect(pointCells).toContain(100.0);
|
||||||
|
|
@ -323,9 +323,9 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should have 100.0 and 50.0 for completed picks, and "-" for pending
|
// Should have 100.00 and 50.00 for completed picks, and "-" for pending
|
||||||
const pointValues = screen.getAllByRole("cell")
|
const pointValues = screen.getAllByRole("cell")
|
||||||
.filter((cell) => cell.textContent?.match(/^\d+\.\d$|^-$/));
|
.filter((cell) => cell.textContent?.match(/^\d+\.\d\d$|^-$/));
|
||||||
|
|
||||||
expect(pointValues.length).toBeGreaterThan(0);
|
expect(pointValues.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
@ -432,7 +432,7 @@ describe("TeamScoreBreakdown", () => {
|
||||||
|
|
||||||
// Should still show team name and points
|
// Should still show team name and points
|
||||||
expect(screen.getByText("Test Team")).toBeInTheDocument();
|
expect(screen.getByText("Test Team")).toBeInTheDocument();
|
||||||
expect(screen.getAllByText("150.0").length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText("150.00").length).toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
// But not show rank badge
|
// But not show rank badge
|
||||||
expect(screen.queryByText(/Rank #/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Rank #/)).not.toBeInTheDocument();
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export default [
|
||||||
route("api/autodraft/update", "routes/api/autodraft.update.ts"),
|
route("api/autodraft/update", "routes/api/autodraft.update.ts"),
|
||||||
route("user-profile", "routes/user-profile.tsx"),
|
route("user-profile", "routes/user-profile.tsx"),
|
||||||
route("how-to-play", "routes/how-to-play.tsx"),
|
route("how-to-play", "routes/how-to-play.tsx"),
|
||||||
|
route("rules", "routes/rules.tsx"),
|
||||||
route("test-socket", "routes/test-socket.tsx"),
|
route("test-socket", "routes/test-socket.tsx"),
|
||||||
|
|
||||||
// Admin routes
|
// Admin routes
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
export function meta() {
|
export function meta() {
|
||||||
return [
|
return [
|
||||||
{ title: "How To Play - Brackt" },
|
{ title: "How To Play - Brackt" },
|
||||||
|
|
@ -12,185 +14,344 @@ export function meta() {
|
||||||
export default function HowToPlay() {
|
export default function HowToPlay() {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
||||||
<h1 className="text-4xl font-bold mb-8">How To Play</h1>
|
<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-8">
|
<div className="space-y-10">
|
||||||
|
|
||||||
|
{/* The Big Picture */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold mb-4">🎯 The Goal</h2>
|
<h2 className="text-2xl font-semibold mb-3">The Big Picture</h2>
|
||||||
<p className="text-lg text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Draft teams and athletes across multiple sports and earn points
|
Before the season starts, every participant in your league holds a
|
||||||
based on how well they perform. The player with the most points at
|
draft — picking real athletes and teams across a set of sports
|
||||||
the end of the season wins!
|
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>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Building Your Roster */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
<h2 className="text-2xl font-semibold mb-3">Building Your Roster</h2>
|
||||||
📋 Building Your Roster
|
<p className="text-muted-foreground mb-4">
|
||||||
</h2>
|
Your league includes a set of sports selected by the commissioner —
|
||||||
<p className="text-lg text-muted-foreground mb-4">
|
think NFL, NBA, golf, Formula 1, tennis, and more. Your roster has
|
||||||
Your roster has spots for different sports. You'll draft teams or
|
one dedicated spot for each sport, so you need to draft at least
|
||||||
individual athletes depending on the sport:
|
one pick from every sport in the league.
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc list-inside space-y-2 text-muted-foreground ml-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
<li>Each sport has at least one required spot on your roster</li>
|
Extra draft rounds beyond the number of sports create{" "}
|
||||||
<li>
|
<span className="font-semibold text-foreground">flex spots</span>.
|
||||||
You'll also get flex spots that can be filled with any sport you
|
Flex picks can come from any sport — great for grabbing a second
|
||||||
choose
|
player in a sport where you see value, or doubling down on a safe
|
||||||
</li>
|
bet.
|
||||||
<li>
|
|
||||||
Pick wisely - you're stuck with your roster for the whole season!
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">🏆 How Scoring Works</h2>
|
|
||||||
<p className="text-lg text-muted-foreground mb-4">
|
|
||||||
Points are awarded based on final standings. The better your pick
|
|
||||||
finishes, the more points you earn:
|
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-muted p-6 rounded-lg">
|
<div className="bg-muted rounded-lg p-4 text-sm text-muted-foreground">
|
||||||
<div className="grid grid-cols-2 gap-x-8 gap-y-3">
|
<p>
|
||||||
<div className="flex justify-between items-center">
|
<span className="font-semibold text-foreground">Example:</span>{" "}
|
||||||
<span className="font-semibold">🥇 1st Place</span>
|
Your league has 20 sports and 24 draft rounds. That means you
|
||||||
<span className="text-xl font-bold">100 pts</span>
|
fill 20 dedicated sport spots and have 4 flex picks to use
|
||||||
</div>
|
however you like.
|
||||||
<div className="flex justify-between items-center">
|
</p>
|
||||||
<span className="font-semibold">🥈 2nd Place</span>
|
|
||||||
<span className="text-xl font-bold">70 pts</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-semibold">🥉 3rd Place</span>
|
|
||||||
<span className="text-xl font-bold">50 pts</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-semibold">4th Place</span>
|
|
||||||
<span className="text-xl font-bold">40 pts</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-semibold">5th Place</span>
|
|
||||||
<span className="text-xl font-bold">25 pts</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-semibold">6th Place</span>
|
|
||||||
<span className="text-xl font-bold">25 pts</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-semibold">7th Place</span>
|
|
||||||
<span className="text-xl font-bold">15 pts</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-semibold">8th Place</span>
|
|
||||||
<span className="text-xl font-bold">15 pts</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mt-4 italic">
|
|
||||||
Note: If nobody drafted a team that finishes in the top 8, those
|
|
||||||
points aren't awarded to anyone.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
|
||||||
⛳ Special Scoring for Golf, Tennis & Counter-Strike
|
|
||||||
</h2>
|
|
||||||
<p className="text-lg text-muted-foreground mb-4">
|
|
||||||
Golf, tennis, and Counter-Strike work a bit differently since they
|
|
||||||
each have multiple major tournaments throughout the year:
|
|
||||||
</p>
|
|
||||||
<ul className="list-disc list-inside space-y-2 text-muted-foreground ml-4">
|
|
||||||
<li>
|
|
||||||
Players/teams earn qualifying points at each major tournament
|
|
||||||
throughout the year
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
After all majors are complete, we add up everyone's qualifying
|
|
||||||
points
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
The top 8 by total qualifying points then earn the regular scoring
|
|
||||||
points (100, 70, 50, 40, 25, 25, 15, 15)
|
|
||||||
</li>
|
|
||||||
<li>This rewards consistency across all the major tournaments!</li>
|
|
||||||
</ul>
|
|
||||||
<div className="bg-muted p-4 rounded-lg mt-4">
|
|
||||||
<p className="font-semibold mb-2">Qualifying points per major:</p>
|
|
||||||
<div className="grid grid-cols-2 gap-1 text-sm text-muted-foreground">
|
|
||||||
<span>1st Place</span><span className="font-medium">8 QP</span>
|
|
||||||
<span>2nd Place</span><span className="font-medium">5 QP</span>
|
|
||||||
<span>3rd Place</span><span className="font-medium">3 QP</span>
|
|
||||||
<span>4th Place</span><span className="font-medium">2 QP</span>
|
|
||||||
<span>5th Place</span><span className="font-medium">1 QP</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* How Points Work */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold mb-4">🎲 The Draft</h2>
|
<h2 className="text-2xl font-semibold mb-3">How Points Work</h2>
|
||||||
<p className="text-lg text-muted-foreground mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
Before the season starts, you'll participate in a snake draft:
|
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">5th–6th Place</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
25
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">7th–8th 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. Drafting rare picks can pay
|
||||||
|
off.
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc list-inside space-y-2 text-muted-foreground ml-4">
|
|
||||||
<li>
|
|
||||||
<strong>Snake draft</strong> means the order reverses each round
|
|
||||||
(if you pick last in round 1, you pick first in round 2)
|
|
||||||
</li>
|
|
||||||
<li>You'll have a time limit to make each pick</li>
|
|
||||||
<li>
|
|
||||||
Once you draft a team or athlete, they're yours for the entire
|
|
||||||
season
|
|
||||||
</li>
|
|
||||||
<li>No trading picks or players - choose carefully!</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Major-Based Sports */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-semibold mb-4">📅 During the Season</h2>
|
<h2 className="text-2xl font-semibold mb-3">
|
||||||
<p className="text-lg text-muted-foreground mb-4">
|
Golf, Tennis, CS2 — Sports with Majors
|
||||||
Once the season starts, it's all about watching your picks compete:
|
</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. For these sports,
|
||||||
|
players earn{" "}
|
||||||
|
<span className="font-semibold text-foreground">
|
||||||
|
qualifying points (QP)
|
||||||
|
</span>{" "}
|
||||||
|
at each major:
|
||||||
|
</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">5th–6th</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
5 QP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">7th–8th</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
3 QP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">9th–12th</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
2 QP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">13th–16th</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>
|
</p>
|
||||||
<ul className="list-disc list-inside space-y-2 text-muted-foreground ml-4">
|
|
||||||
<li>No trades - your roster is locked in</li>
|
|
||||||
<li>No waivers or free agent pickups</li>
|
|
||||||
<li>Just sit back and cheer for your teams and athletes!</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* The Draft */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-3">The Draft</h2>
|
||||||
|
<p className="text-muted-foreground mb-6">
|
||||||
|
The draft is a snake draft — pick order reverses every round. If
|
||||||
|
you pick last in round 1, you pick first in round 2. No trading
|
||||||
|
picks, and once you make a selection it's yours for the whole
|
||||||
|
season.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Chess Clock */}
|
||||||
|
<div className="border rounded-lg p-5 mb-6 space-y-3">
|
||||||
|
<h3 className="text-lg font-semibold">
|
||||||
|
The Chess Clock — A Brackt Original
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Most fantasy drafts use a simple countdown: you get X seconds per
|
||||||
|
pick and that's it. Brackt does it differently with a{" "}
|
||||||
|
<span className="font-semibold text-foreground">
|
||||||
|
Fischer increment timer
|
||||||
|
</span>
|
||||||
|
, borrowed from competitive chess.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Every participant starts the draft with a personal time bank.
|
||||||
|
Your clock counts down while it's your turn. When you make a
|
||||||
|
pick,{" "}
|
||||||
|
<span className="font-semibold text-foreground">
|
||||||
|
time is added back
|
||||||
|
</span>{" "}
|
||||||
|
to your bank. Any time you don't use carries over to your next
|
||||||
|
pick.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Pick quickly early on and you'll build up a bigger bank for the
|
||||||
|
later rounds when decisions get harder. Deliberate too long and
|
||||||
|
your bank shrinks. It rewards decisiveness without penalizing you
|
||||||
|
for taking a moment when you really need it.
|
||||||
|
</p>
|
||||||
|
<div className="bg-muted rounded-md p-3 text-sm text-muted-foreground">
|
||||||
|
<span className="font-semibold text-foreground">Example:</span>{" "}
|
||||||
|
On Standard speed, you start with 2 minutes. You make your first
|
||||||
|
pick in 30 seconds — 15 seconds are added back, leaving you with
|
||||||
|
1 minute 45 seconds for your next turn. Keep picking fast and
|
||||||
|
that bank grows.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Draft Queue & Autodraft */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h3 className="text-lg font-semibold">Draft Queue & Autodraft</h3>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Before and during the draft you can build a{" "}
|
||||||
|
<span className="font-semibold text-foreground">draft queue</span>{" "}
|
||||||
|
— an ordered list of picks you want to make. When it's your turn,
|
||||||
|
you can pick manually or let your queue guide you.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
If your time bank runs out,{" "}
|
||||||
|
<span className="font-semibold text-foreground">autodraft</span>{" "}
|
||||||
|
kicks in automatically. It'll grab the first available pick from
|
||||||
|
your queue. If your queue is empty or everything in it has
|
||||||
|
already been taken, it'll grab from the top of the available player pool.
|
||||||
|
Keep your queue updated and you'll never get stuck with a random
|
||||||
|
pick.
|
||||||
|
</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>
|
||||||
|
|
||||||
|
{/* Tips */}
|
||||||
<section className="bg-primary/5 p-6 rounded-lg border border-primary/20">
|
<section className="bg-primary/5 p-6 rounded-lg border border-primary/20">
|
||||||
<h2 className="text-2xl font-semibold mb-4">💡 Quick Tips</h2>
|
<h2 className="text-xl font-semibold mb-4">Quick Tips</h2>
|
||||||
<ul className="space-y-2 text-muted-foreground">
|
<ul className="space-y-3 text-muted-foreground">
|
||||||
<li>
|
<li>
|
||||||
✓ <strong>Diversify:</strong> Don't put all your eggs in one
|
<span className="font-semibold text-foreground">
|
||||||
basket - spread your picks across different sports
|
Know your sports.
|
||||||
|
</span>{" "}
|
||||||
|
Understanding how each sport determines its top 8 (playoffs vs.
|
||||||
|
regular season vs. bracket) is the biggest edge you can have.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
✓ <strong>Know the format:</strong> Some sports score based on
|
<span className="font-semibold text-foreground">
|
||||||
playoffs, others on regular season performance
|
Build your queue early.
|
||||||
|
</span>{" "}
|
||||||
|
The draft moves fast. Having a ranked queue ready means you'll
|
||||||
|
always get a pick you're happy with, even if you step away.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
✓ <strong>Draft early:</strong> The best teams and athletes go
|
<span className="font-semibold text-foreground">
|
||||||
fast!
|
Pick fast early on.
|
||||||
|
</span>{" "}
|
||||||
|
The chess clock rewards quick decisions. Bank extra time for the
|
||||||
|
tricky later rounds.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
✓ <strong>Have fun:</strong> This is about enjoying sports across
|
<span className="font-semibold text-foreground">
|
||||||
the calendar year
|
Flex spots are strategic.
|
||||||
|
</span>{" "}
|
||||||
|
Use them to double up on sports you feel confident about, not
|
||||||
|
just to fill the roster.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="text-center pt-8">
|
{/* Links */}
|
||||||
<p className="text-xl text-muted-foreground mb-6">
|
<section className="flex flex-col sm:flex-row gap-4 items-center justify-between pt-2">
|
||||||
Ready to compete?
|
<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>
|
</p>
|
||||||
<a
|
<Link
|
||||||
href="/leagues/new"
|
to="/leagues/new"
|
||||||
className="inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-11 px-8 text-base"
|
className="inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-11 px-8 text-base whitespace-nowrap"
|
||||||
>
|
>
|
||||||
Create a League
|
Create a League
|
||||||
</a>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
381
app/routes/rules.tsx
Normal file
381
app/routes/rules.tsx
Normal file
|
|
@ -0,0 +1,381 @@
|
||||||
|
export function meta() {
|
||||||
|
return [
|
||||||
|
{ title: "Rules - Brackt" },
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
content: "Official rules for Brackt multi-sport fantasy leagues",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Rules() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
||||||
|
<h1 className="text-4xl font-bold mb-2">Official Rules</h1>
|
||||||
|
<p className="text-muted-foreground mb-10">
|
||||||
|
These rules govern all Brackt leagues. Commissioners may adjust scoring
|
||||||
|
values and draft settings within the options available in league
|
||||||
|
settings, but the core structure below applies to every league.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-10">
|
||||||
|
|
||||||
|
{/* Object */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-1">Object</h2>
|
||||||
|
<div className="border-b mb-4" />
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
The goal is to draft athletes and teams across multiple sports, then
|
||||||
|
earn points based on how they finish at the end of their respective
|
||||||
|
seasons. The participant with the most points at the end of the
|
||||||
|
league season wins.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Rosters */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-1">Rosters</h2>
|
||||||
|
<div className="border-b mb-4" />
|
||||||
|
<div className="space-y-3 text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
The commissioner sets which sports are included in the league and
|
||||||
|
how many draft rounds are held. The number of draft rounds must be
|
||||||
|
at least equal to the number of sports in the league.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Every participant must draft at least one team or individual from
|
||||||
|
each sport included in the league.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Any draft rounds beyond the number of sports are considered{" "}
|
||||||
|
<span className="font-semibold text-foreground">flex rounds</span>
|
||||||
|
. Picks made in flex rounds can be from any sport, including
|
||||||
|
sports that already have a dedicated roster spot filled.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Scoring */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-1">Scoring</h2>
|
||||||
|
<div className="border-b mb-4" />
|
||||||
|
<div className="space-y-6 text-muted-foreground">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-3">
|
||||||
|
Points are awarded based on where a drafted team or individual
|
||||||
|
finishes in their sport. The default point values are listed
|
||||||
|
below. Commissioners may adjust these values before the draft
|
||||||
|
begins.
|
||||||
|
</p>
|
||||||
|
<div className="bg-muted rounded-lg overflow-hidden">
|
||||||
|
<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">5th–6th Place</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
25
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">7th–8th Place</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
15
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If a team or individual finishes in a point-scoring position but
|
||||||
|
was not drafted by anyone, those points are not awarded to anyone.
|
||||||
|
A participant who drafted the next-highest finisher still earns
|
||||||
|
zero points for that position.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-3">
|
||||||
|
When two or more teams or individuals tie for a point-scoring
|
||||||
|
position within a sport, the points for all tied positions are
|
||||||
|
combined and split equally among them, rounded to the nearest
|
||||||
|
whole point.
|
||||||
|
</p>
|
||||||
|
<div className="bg-muted rounded-lg p-4 text-sm space-y-2">
|
||||||
|
<p className="font-semibold text-foreground">Examples:</p>
|
||||||
|
<p>
|
||||||
|
Tie between 2nd and 3rd: (70 + 50) ÷ 2 ={" "}
|
||||||
|
<span className="font-semibold text-foreground">
|
||||||
|
60 points each
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Three-way tie for 6th, 7th, and 8th: (25 + 15 + 15) ÷ 3 ={" "}
|
||||||
|
<span className="font-semibold text-foreground">
|
||||||
|
18 points each
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Major-Based Sports Scoring */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-1">
|
||||||
|
Major-Based Sports Scoring
|
||||||
|
</h2>
|
||||||
|
<div className="border-b mb-4" />
|
||||||
|
<div className="space-y-4 text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
Some sports — such as golf, tennis, and Counter-Strike — are
|
||||||
|
scored across multiple major tournaments throughout the year
|
||||||
|
rather than a single season-ending event. These sports use a
|
||||||
|
qualifying point (QP) system.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
At each major tournament, participants earn qualifying points
|
||||||
|
based on their finish:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="bg-muted rounded-lg overflow-hidden">
|
||||||
|
<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">
|
||||||
|
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">5th–6th</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
5 QP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">7th–8th</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
3 QP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">9th–12th</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
2 QP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">13th–16th</td>
|
||||||
|
<td className="p-3 text-right font-semibold text-foreground">
|
||||||
|
1 QP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Qualifying points are not added to a participant's total league
|
||||||
|
score directly. They are used only to determine final rankings
|
||||||
|
within that sport.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Once the final major tournament is complete, qualifying points
|
||||||
|
from all majors are totaled. Those cumulative totals determine
|
||||||
|
the sport's final rankings, and the standard point values from
|
||||||
|
the Scoring section are then awarded accordingly.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Ties in qualifying points are split equally and rounded to the
|
||||||
|
nearest hundredth of a qualifying point before final rankings are
|
||||||
|
determined. The same tie-splitting logic from the Scoring section
|
||||||
|
then applies when awarding league points.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Season Standings Tiebreakers */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-1">
|
||||||
|
Season Standings Tiebreakers
|
||||||
|
</h2>
|
||||||
|
<div className="border-b mb-4" />
|
||||||
|
<div className="space-y-3 text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
If two or more participants finish the season with the same total
|
||||||
|
score, the following tiebreakers are applied in order:
|
||||||
|
</p>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 ml-4">
|
||||||
|
<li>Most 1st-place finishes across all sports</li>
|
||||||
|
<li>Most 2nd-place finishes</li>
|
||||||
|
<li>Most 3rd-place finishes</li>
|
||||||
|
<li>
|
||||||
|
This pattern continues down through each place until the tie is
|
||||||
|
broken
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* The Draft */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-1">The Draft</h2>
|
||||||
|
<div className="border-b mb-4" />
|
||||||
|
<div className="space-y-3 text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
The draft is a snake draft. Draft order reverses at the start of
|
||||||
|
each new round — if you pick last in round 1, you pick first in
|
||||||
|
round 2, and so on.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Brackt uses a{" "}
|
||||||
|
<span className="font-semibold text-foreground">
|
||||||
|
Fischer increment timer
|
||||||
|
</span>{" "}
|
||||||
|
for all drafts. Each participant has a personal time bank. Your
|
||||||
|
clock counts down while it's your turn, and a fixed amount of
|
||||||
|
time is added back to your bank each time you make a pick. Time
|
||||||
|
not used on one pick carries over to future picks.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The commissioner selects the draft speed, which determines both
|
||||||
|
the starting bank and the per-pick increment:
|
||||||
|
</p>
|
||||||
|
<div className="bg-muted rounded-lg overflow-hidden">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-border">
|
||||||
|
<th className="text-left p-3 font-semibold text-foreground">
|
||||||
|
Speed
|
||||||
|
</th>
|
||||||
|
<th className="text-right p-3 font-semibold text-foreground">
|
||||||
|
Starting Bank
|
||||||
|
</th>
|
||||||
|
<th className="text-right p-3 font-semibold text-foreground">
|
||||||
|
Per-Pick Increment
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-border">
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">Fast</td>
|
||||||
|
<td className="p-3 text-right">1 minute</td>
|
||||||
|
<td className="p-3 text-right">+10 seconds</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">Standard</td>
|
||||||
|
<td className="p-3 text-right">2 minutes</td>
|
||||||
|
<td className="p-3 text-right">+15 seconds</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">Slow</td>
|
||||||
|
<td className="p-3 text-right">8 hours</td>
|
||||||
|
<td className="p-3 text-right">+1 hour</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="p-3">Very Slow</td>
|
||||||
|
<td className="p-3 text-right">12 hours</td>
|
||||||
|
<td className="p-3 text-right">+1 hour</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
If a participant's time bank runs out, the system will
|
||||||
|
automatically draft on their behalf. Autodraft picks from the
|
||||||
|
participant's pre-set draft queue first. If the queue is empty or
|
||||||
|
all queued picks have already been taken, the system selects from
|
||||||
|
the top of the available player pool.
|
||||||
|
</p>
|
||||||
|
<p>Pick trading is not allowed.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* During the Season */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold mb-1">During the Season</h2>
|
||||||
|
<div className="border-b mb-4" />
|
||||||
|
<div className="space-y-3 text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
Once the draft is complete, rosters are locked for the entire
|
||||||
|
season. There is no trading of picks or athletes between
|
||||||
|
participants.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
There are no waivers or free-agent pickups. Every participant
|
||||||
|
competes with the roster they drafted.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue