brackt/app/components/scoring/BracketTreeView.tsx

561 lines
18 KiB
TypeScript
Raw Normal View History

import { avatarColor } from "~/lib/avatar-colors";
import { BRACKT_GRADIENT } from "~/lib/brand";
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
import {
computeGroupLayout,
describeSlotSource,
matchKey,
type BracketLayout,
type FeederMap,
} from "~/lib/bracket-layout";
import type { BracketTemplate } from "~/lib/bracket-templates";
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
export interface BracketMatch {
id: string;
round: string;
matchNumber: number;
participant1Id: string | null;
participant2Id: string | null;
winnerId: string | null;
loserId: string | null;
isComplete: boolean;
participant1Score: string | null;
participant2Score: string | null;
isScoring?: boolean;
participant1?: { id: string; name: string } | null;
participant2?: { id: string; name: string } | null;
winner?: { id: string; name: string } | null;
loser?: { id: string; name: string } | null;
}
export interface BracketOwnership {
participantId: string;
teamName: string;
teamId: string;
ownerName?: string;
}
const COLUMN_WIDTH = 152;
const CONNECTOR_WIDTH = 24;
export const SLOT_WIDTH = 2 * COLUMN_WIDTH + CONNECTOR_WIDTH;
export const LABEL_HEIGHT = 32;
export const CARD_GAP = 14;
export const DESIRED_CARD_HEIGHT = 112;
export const MAX_CARD_HEIGHT = 140;
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
function formatScore(score: string | null): string | null {
if (!score) return null;
const n = parseFloat(score);
if (isNaN(n)) return null;
return Number.isInteger(n) ? String(n) : n.toFixed(1);
}
// ─── Match Slot ──────────────────────────────────────────────────────────────
interface ParticipantRowProps {
name: string | null;
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
/** What fills this slot when it's still empty, e.g. "Winner of Winners SF 2". */
feedLabel?: string | null;
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
isTbd: boolean;
isWinner: boolean;
isLoser: boolean;
isOwned: boolean;
ownership: BracketOwnership | null;
score: string | null;
rowHeight: number;
showScore: boolean;
showOwner: boolean;
showText: boolean;
}
function ParticipantRow({
name,
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feedLabel,
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
isTbd,
isWinner,
isLoser,
isOwned,
ownership,
score,
rowHeight,
showScore,
showOwner,
showText,
}: ParticipantRowProps) {
const formattedScore = formatScore(score);
return (
<div
className="relative flex items-center overflow-hidden"
style={{ height: rowHeight }}
>
{showText && (
<div className={[
"flex items-center flex-1 min-w-0 gap-1.5 px-2 pl-[7px] ml-2",
isWinner && isOwned ? "border border-electric/50 bg-electric/8 rounded-md mr-2 my-0.5 self-stretch py-1" :
isWinner ? "border border-white/15 bg-white/5 rounded-md mr-2 my-0.5 self-stretch py-1" : "",
].filter(Boolean).join(" ")}>
{/* Manager avatar — always present for alignment; empty box when unowned */}
<div
className="shrink-0 rounded-[3px] flex items-center justify-center text-[8px] font-bold"
style={{
width: 18,
height: 18,
background: ownership ? "#000" : "transparent",
color: ownership ? avatarColor(ownership.teamName) : undefined,
}}
>
{ownership &&
ownership.teamName
.split(/\s+/)
.slice(0, 2)
.map((w) => w[0]?.toUpperCase() ?? "")
.join("")}
</div>
<div className="flex-1 min-w-0">
<span
className={[
"text-[13px] leading-tight block truncate",
isTbd ? "text-muted-foreground/50 italic" : "",
isLoser && isOwned ? "text-electric/50 line-through" :
isLoser ? "text-muted-foreground line-through" : "",
isWinner ? "font-semibold" : "",
]
.filter(Boolean)
.join(" ")}
>
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
{name ?? feedLabel ?? "TBD"}
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
</span>
{/* Owner name below participant name */}
{showOwner && !isTbd && ownership && (
<span className="text-[10px] text-muted-foreground truncate block leading-none">
{ownership.ownerName ?? ownership.teamName}
</span>
)}
</div>
{/* Score */}
{showScore && formattedScore && (
<span
className={[
"shrink-0 text-xs tabular-nums font-medium ml-auto pl-1",
isLoser ? "text-muted-foreground/60" : "",
isWinner ? "text-yellow-400" : "",
]
.filter(Boolean)
.join(" ")}
>
{formattedScore}
</span>
)}
</div>
)}
</div>
);
}
interface BracketMatchSlotProps {
match: BracketMatch;
slotHeight: number;
ownershipMap: Map<string, BracketOwnership>;
userParticipantIds: Set<string>;
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders?: FeederMap;
template?: BracketTemplate;
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
}
export function BracketMatchSlot({
match,
slotHeight,
ownershipMap,
userParticipantIds,
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders,
template,
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
}: BracketMatchSlotProps) {
const rowHeight = slotHeight / 2;
const showText = rowHeight >= 10;
const showScore = rowHeight >= 20 && (!!match.participant1Score || match.isComplete);
const showOwner = rowHeight >= 36;
const p1Id = match.participant1Id;
const p2Id = match.participant2Id;
const p1IsWinner = match.isComplete && match.winnerId === p1Id;
const p2IsWinner = match.isComplete && match.winnerId === p2Id;
const p1IsLoser = match.isComplete && match.loserId === p1Id;
const p2IsLoser = match.isComplete && match.loserId === p2Id;
const p1IsOwned = !!(p1Id && userParticipantIds.has(p1Id));
const p2IsOwned = !!(p2Id && userParticipantIds.has(p2Id));
const matchHasOwned = p1IsOwned || p2IsOwned;
const isTbd1 = !p1Id;
const isTbd2 = !p2Id;
const p1Ownership = p1Id ? ownershipMap.get(p1Id) ?? null : null;
const p2Ownership = p2Id ? ownershipMap.get(p2Id) ?? null : null;
// Corona glow: gradient for complete matches, electric for user's picks, subtle white for pending
const coronaStyle: React.CSSProperties = match.isComplete
? { background: BRACKT_GRADIENT }
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
: matchHasOwned
? { background: "rgba(44, 225, 193, 0.4)" }
: { background: "rgba(255, 255, 255, 0.07)" };
const INSET = Math.max(1, Math.min(2, Math.floor(slotHeight / 20)));
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
// An empty slot reads better as "Loser of Winners SF 2" than "TBD" — especially for
// the feeds that cross between the winners and elimination brackets, which render as
// separate trees and so can never be joined by a line.
const slotSources = feeders?.get(matchKey(match.round, match.matchNumber));
const feed1 = describeSlotSource(slotSources?.[0], template);
const feed2 = describeSlotSource(slotSources?.[1], template);
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
return (
<div className="relative overflow-hidden" style={{ height: slotHeight }}>
{/* Corona layer — left-2 matches card borderRadius to prevent gradient bleed at left corners */}
<div className="absolute left-2 right-0 rounded-lg" style={{ ...coronaStyle, top: INSET - 2, bottom: INSET - 2 }} />
{/* Inner card, inset on right to expose corona glow strip */}
<div
className="absolute flex flex-col overflow-hidden"
style={{
top: 0,
right: INSET + 2,
bottom: 0,
left: 0,
borderRadius: 8,
background: "var(--color-muted)",
paddingTop: INSET,
paddingBottom: INSET,
}}
>
<ParticipantRow
name={match.participant1?.name ?? null}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feedLabel={feed1}
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
isTbd={isTbd1}
isWinner={p1IsWinner}
isLoser={p1IsLoser}
isOwned={p1IsOwned}
ownership={p1Ownership}
score={match.participant1Score}
rowHeight={rowHeight - INSET}
showScore={showScore}
showOwner={showOwner}
showText={showText}
/>
<ParticipantRow
name={match.participant2?.name ?? null}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feedLabel={feed2}
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
isTbd={isTbd2}
isWinner={p2IsWinner}
isLoser={p2IsLoser}
isOwned={p2IsOwned}
ownership={p2Ownership}
score={match.participant2Score}
rowHeight={rowHeight - INSET}
showScore={showScore}
showOwner={showOwner}
showText={showText}
/>
</div>
</div>
);
}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
// ─── Connector column ─────────────────────────────────────────────────────────
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
interface ConnectorColumnProps {
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
/** Edges crossing this gutter, in slot units. */
edges: { fromCenter: number; toCenter: number }[];
rowHeight: number;
offset: number;
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
bracketHeight: number;
}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
/**
* Draws the feeder edges crossing one gutter. Because the layout assigns columns by
* depth from the final, every edge spans exactly one gutter so a card that enters the
* bracket late is drawn in the column where it actually plays, and there is never an
* edge to route across a skipped column.
*/
function ConnectorColumn({ edges, rowHeight, offset, bracketHeight }: ConnectorColumnProps) {
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
const mid = CONNECTOR_WIDTH / 2;
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
// Merge the two edges feeding one card into a single elbow, so a pair reads as one
// bracket join rather than two overlapping lines.
const byTarget = new Map<number, number[]>();
for (const { fromCenter, toCenter } of edges) {
const sources = byTarget.get(toCenter) ?? [];
sources.push(fromCenter);
byTarget.set(toCenter, sources);
}
const paths: string[] = [];
for (const [toCenter, sources] of byTarget) {
const destY = toCenter * rowHeight - offset;
const ys = sources.map((c) => c * rowHeight - offset).toSorted((a, b) => a - b);
if (ys.length === 1) {
paths.push(`M 0 ${ys[0]} H ${mid} V ${destY} H ${CONNECTOR_WIDTH}`);
continue;
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
}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
paths.push(`M 0 ${ys[0]} H ${mid} V ${ys[ys.length - 1]} H 0`);
for (const y of ys.slice(1, -1)) paths.push(`M 0 ${y} H ${mid}`);
paths.push(`M ${mid} ${destY} H ${CONNECTOR_WIDTH}`);
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
}
return (
<div style={{ flex: `0 0 ${CONNECTOR_WIDTH}px`, position: "relative" }}>
<svg
style={{
position: "absolute",
top: LABEL_HEIGHT,
left: 0,
width: CONNECTOR_WIDTH,
height: bracketHeight,
overflow: "visible",
pointerEvents: "none",
}}
>
{paths.map((d) => (
<path key={d} d={d} fill="none" stroke="rgb(255 255 255 / 22%)" strokeWidth={1.5} />
))}
</svg>
</div>
);
}
// ─── Tree columns (shared by full + paginated) ───────────────────────────────
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
export interface BracketGeometry {
layout: BracketLayout<BracketMatch>;
/** Height of one leaf row. */
rowHeight: number;
/** Height of the card area, excluding the round labels. */
bracketHeight: number;
/** Narrowest the columns and gutters can be drawn without overlapping. */
minWidth: number;
/** Pixels trimmed off the top, non-zero only for a cropped column window. */
offset: number;
}
/**
* Lay out a group's matches from the feeder graph and derive its pixel geometry.
*
* Height comes from the number of leaf rows rather than the largest round, so a bracket
* whose widest column isn't its first still gets the room it needs.
*/
export function bracketGeometry(
visibleRounds: string[],
matchesByRound: Map<string, BracketMatch[]>,
feeders: FeederMap | undefined,
templateRoundOrder: string[]
): BracketGeometry {
const layout = computeGroupLayout(
visibleRounds,
matchesByRound,
feeders ?? new Map(),
templateRoundOrder
);
const rowHeight = DESIRED_CARD_HEIGHT + CARD_GAP;
const columnCount = Math.max(layout.columns.length, 1);
return {
layout,
rowHeight,
bracketHeight: Math.max(layout.leafCount, 1) * rowHeight,
minWidth: columnCount * COLUMN_WIDTH + (columnCount - 1) * CONNECTOR_WIDTH,
offset: 0,
};
}
/**
* Crop a layout to a window of columns, as the mobile pager does.
*
* Card positions are absolute within the whole bracket, so showing a slice of columns
* means trimming the empty space above them rather than re-flowing otherwise a later
* page would render its two columns stranded at the bottom of a full-height bracket.
*/
export function windowGeometry(
geometry: BracketGeometry,
firstColumn: number,
lastColumn: number
): BracketGeometry {
const centers = geometry.layout.columns
.slice(firstColumn, lastColumn + 1)
.flatMap((c) => c.matches.map((m) => m.center));
if (centers.length === 0) return geometry;
const min = Math.min(...centers);
const max = Math.max(...centers);
return {
...geometry,
bracketHeight: (max - min + 1) * geometry.rowHeight,
offset: (min - 0.5) * geometry.rowHeight,
};
}
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
interface TreeColumnsProps {
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
geometry: BracketGeometry;
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
ownershipMap: Map<string, BracketOwnership>;
userParticipantIds: Set<string>;
transitionDuration?: number;
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders?: FeederMap;
template?: BracketTemplate;
/** Restrict rendering to a window of columns (used by the mobile pager). */
columnRange?: [number, number];
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
}
export function TreeColumns({
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
geometry,
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
ownershipMap,
userParticipantIds,
transitionDuration,
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders,
template,
columnRange,
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
}: TreeColumnsProps) {
const tr = transitionDuration ? `${transitionDuration}ms ease` : undefined;
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
const { layout, rowHeight, bracketHeight, offset } = geometry;
const [firstColumn, lastColumn] = columnRange ?? [0, layout.columns.length - 1];
const visible = layout.columns.slice(firstColumn, lastColumn + 1);
// Cards keep a fixed height regardless of how many share a column — stretching a
// lone final to fill its column is what made it tower over the rest of the bracket.
const cardHeight = Math.min(
Math.max(rowHeight - CARD_GAP, 1),
MAX_CARD_HEIGHT
);
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
return (
<div style={{ display: "flex", width: "100%", height: bracketHeight + LABEL_HEIGHT, transition: tr ? `height ${tr}` : undefined }}>
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
{visible.map((column, vi) => {
const ci = firstColumn + vi;
const gutterEdges = layout.edges.filter((e) => e.fromColumn === ci);
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
return (
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
<div key={column.label + ci} style={{ display: "contents" }}>
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
{/* Round column */}
<div style={{ flex: "1 1 0", minWidth: COLUMN_WIDTH, position: "relative" }}>
<div
className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground truncate text-center"
style={{ height: LABEL_HEIGHT, display: "flex", alignItems: "center", justifyContent: "center" }}
>
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
{column.label}
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
</div>
<div style={{ position: "relative", height: bracketHeight, transition: tr ? `height ${tr}` : undefined }}>
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
{column.matches.map(({ match, center }) => (
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
<div
key={match.id}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
data-match-id={match.id}
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
style={{
position: "absolute",
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
top: center * rowHeight - offset - cardHeight / 2,
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
left: 0,
right: 0,
height: cardHeight,
transition: tr ? `top ${tr}, height ${tr}` : undefined,
}}
>
<BracketMatchSlot
match={match}
slotHeight={cardHeight}
ownershipMap={ownershipMap}
userParticipantIds={userParticipantIds}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders={feeders}
template={template}
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
/>
</div>
))}
</div>
</div>
{/* Connector between this column and the next */}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
{vi < visible.length - 1 && (
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
<ConnectorColumn
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
edges={gutterEdges}
rowHeight={rowHeight}
offset={offset}
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
bracketHeight={bracketHeight}
/>
)}
</div>
);
})}
</div>
);
}
// ─── Full desktop tree ────────────────────────────────────────────────────────
interface BracketTreeViewProps {
rounds: string[];
matchesByRound: Map<string, BracketMatch[]>;
ownershipMap: Map<string, BracketOwnership>;
userParticipantIds: Set<string>;
thirdPlaceRound?: string;
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders?: FeederMap;
template?: BracketTemplate;
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
}
export function BracketTreeView({
rounds,
matchesByRound,
ownershipMap,
userParticipantIds,
thirdPlaceRound,
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders,
template,
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
}: BracketTreeViewProps) {
const mainRounds = thirdPlaceRound ? rounds.filter((r) => r !== thirdPlaceRound) : rounds;
const thirdPlaceMatch = thirdPlaceRound ? (matchesByRound.get(thirdPlaceRound) ?? [])[0] : undefined;
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
const geometry = bracketGeometry(
mainRounds,
matchesByRound,
feeders,
template?.rounds.map((r) => r.name) ?? mainRounds
);
const { bracketHeight, minWidth } = geometry;
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
return (
<div
className="w-full overflow-x-auto"
style={{ minHeight: bracketHeight + LABEL_HEIGHT + 2 }}
>
<div style={{ minWidth }}>
<TreeColumns
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
geometry={geometry}
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
ownershipMap={ownershipMap}
userParticipantIds={userParticipantIds}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders={feeders}
template={template}
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
/>
{thirdPlaceMatch && (
<div style={{ display: "flex", paddingTop: 20 }}>
<div style={{ flex: 1 }} />
<div style={{ minWidth: COLUMN_WIDTH }}>
<div
className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground text-center"
style={{ height: LABEL_HEIGHT, display: "flex", alignItems: "center", justifyContent: "center" }}
>
3rd Place
</div>
<div style={{ height: Math.min(DESIRED_CARD_HEIGHT, MAX_CARD_HEIGHT) }}>
<BracketMatchSlot
match={thirdPlaceMatch}
slotHeight={Math.min(DESIRED_CARD_HEIGHT, MAX_CARD_HEIGHT)}
ownershipMap={ownershipMap}
userParticipantIds={userParticipantIds}
Lay out brackets from the feeder graph The LLWS bracket didn't read as a bracket: cards sat above games that don't feed them, connectors joined the wrong pairs, and several games had no line at all. The stored data was correct — LLWS_ADVANCEMENT already matches the official 2026 LLBWS bracket game for game. The renderer was the problem. TreeColumns placed cards at `index * (height / roundSize)` and ConnectorColumn assumed matches 2k and 2k+1 feed match k, which holds only for an exact halving. The LLWS winners bracket is not one: two of the four Opening Round games skip Winners Round 2 and go straight to the semifinals, so those two got stranded in column one with nothing beside them, and the halving branch drew confident, wrong connectors for the rest. Lay out from the graph instead. app/lib/bracket-layout.ts inverts a template's advancement into "what fills each slot", then assigns columns by depth from the group's final, orders each column by the parent's slot order, and centres each card on its feeders. Counting back from the final is what makes a printed bracket line up: a team entering late is drawn in the column where it actually plays. This reproduces the official International bracket exactly, and fixes Elimination Round 3, where the official bracket prints the later game on top but match-number sort put it below. Because column is depth, every in-group edge spans exactly one gutter, so connectors now draw for unplayed games too. Cards also take a fixed height rather than stretching to fill their column, which is what made a lone final tower over the rest. Empty slots name their source — "Loser of Winners SF 1" rather than "TBD". That is the only way to show the feeds crossing between the winners and elimination brackets, which render as separate trees. Also: - Move the LLWS routing table to app/lib/llws-bracket.ts so the renderer can import it without pulling the database context into the browser bundle; models/playoff-match re-exports it. - Page the mobile view one group at a time, matching desktop. A whole double-elimination phase is a DAG, not a tree, so its columns would be arbitrary. - Add a clear-bracket admin action. Nothing else could rewrite a match's participants, so a mis-seeded bracket had no repair path at all. - Lift the PDF transcription into app/test/fixtures/llws-bracket.ts so the routing and layout tests check against one copy of the official bracket. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnzbrCHoM8ESbtbDamaqFb
2026-08-21 17:50:59 +00:00
feeders={feeders}
template={template}
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
/>
</div>
</div>
</div>
)}
</div>
</div>
);
}