From 99ce17ec1e73d876e0c16c1a3f0dde056f77b75c Mon Sep 17 00:00:00 2001
From: Claude <noreply@anthropic.com>
Date: Thu, 19 Mar 2026 12:06:19 +0000
Subject: Fix tournament bracket connector lines

The bracket connectors had two issues:
1. The center vertical line from each connector went to the wrong position,
   not aligning with child connectors below
2. No vertical lines connected round-0 connectors to player cards

Redesign the Connector component to draw:
- A center stem going UP (connecting to parent round)
- A horizontal line (connecting left/right branches)
- Two legs going DOWN at half-center positions (connecting to children)

Also add vertical stem lines above each player card to bridge the gap
between round-0 connector legs and the player boxes.

https://claude.ai/code/session_01NKA6A2uWXJBRknZZcwMGCn
---
 frontend/app/pages/TournamentPage.tsx | 97 +++++++++++++++++++++++++++++++++--
 1 file changed, 92 insertions(+), 5 deletions(-)

(limited to 'frontend/app/pages/TournamentPage.tsx')

diff --git a/frontend/app/pages/TournamentPage.tsx b/frontend/app/pages/TournamentPage.tsx
index afcb772..87836b2 100644
--- a/frontend/app/pages/TournamentPage.tsx
+++ b/frontend/app/pages/TournamentPage.tsx
@@ -20,6 +20,23 @@ function getBorderColor(match: TournamentMatch, userID?: number): string {
   return "border-gray-400";
 }
 
+function getBgColor(match: TournamentMatch, userID?: number): string {
+  if (!match.winner_user_id) {
+    return "bg-black";
+  }
+  if (userID !== undefined && match.winner_user_id === userID) {
+    return "bg-pink-700";
+  }
+  return "bg-gray-400";
+}
+
+function getWinnerBgColor(match: TournamentMatch | undefined): string {
+  if (!match?.winner_user_id) {
+    return "bg-black";
+  }
+  return "bg-pink-700";
+}
+
 function PlayerCard({ entry }: { entry: TournamentEntry | undefined }) {
   if (!entry) {
     return (
@@ -51,28 +68,44 @@ function Connector({
   position,
   colSpan,
   match,
+  isTopRound,
 }: {
   position: number;
   colSpan: number;
   match: TournamentMatch | undefined;
+  isTopRound: boolean;
 }) {
   const leftHalf = colSpan / 2;
   const rightHalf = colSpan - leftHalf;
 
-  const leftColor = match
+  const leftBorderColor = match
     ? getBorderColor(match, match.player1?.user_id)
     : "border-black";
-  const rightColor = match
+  const rightBorderColor = match
     ? getBorderColor(match, match.player2?.user_id)
     : "border-black";
+  const leftBgColor = match
+    ? getBgColor(match, match.player1?.user_id)
+    : "bg-black";
+  const rightBgColor = match
+    ? getBgColor(match, match.player2?.user_id)
+    : "bg-black";
+  const winnerBg = getWinnerBgColor(match);
 
   return (
     <div
-      className="grid h-8"
       style={{
         gridColumn: `${position * colSpan + 1} / span ${colSpan}`,
       }}
     >
+      {/* Center stem going UP to parent round */}
+      {!isTopRound && (
+        <div className="flex justify-center h-4">
+          <div className={`w-0.5 ${winnerBg}`} />
+        </div>
+      )}
+
+      {/* Horizontal line */}
       <div
         className="grid"
         style={{
@@ -80,14 +113,35 @@ function Connector({
         }}
       >
         <div
-          className={`border-t-4 border-r-2 ${leftColor}`}
+          className={`border-t-4 ${leftBorderColor}`}
           style={{ gridColumn: `1 / span ${leftHalf}` }}
         />
         <div
-          className={`border-t-4 border-l-2 ${rightColor}`}
+          className={`border-t-4 ${rightBorderColor}`}
           style={{ gridColumn: `${leftHalf + 1} / span ${rightHalf}` }}
         />
       </div>
+
+      {/* Two legs going DOWN to child elements */}
+      <div
+        className="grid h-4"
+        style={{
+          gridTemplateColumns: `repeat(${colSpan}, 1fr)`,
+        }}
+      >
+        <div
+          className="flex justify-center"
+          style={{ gridColumn: `1 / span ${leftHalf}` }}
+        >
+          <div className={`w-0.5 ${leftBgColor}`} />
+        </div>
+        <div
+          className="flex justify-center"
+          style={{ gridColumn: `${leftHalf + 1} / span ${rightHalf}` }}
+        >
+          <div className={`w-0.5 ${rightBgColor}`} />
+        </div>
+      </div>
     </div>
   );
 }
@@ -125,6 +179,7 @@ function TournamentBracket({ tournament }: { tournament: Tournament }) {
           position={pos}
           colSpan={colSpan}
           match={match}
+          isTopRound={round === num_rounds - 1}
         />,
       );
     }
@@ -141,6 +196,38 @@ function TournamentBracket({ tournament }: { tournament: Tournament }) {
     );
   }
 
+  // Player stems row (vertical lines from round-0 connectors to player cards)
+  const playerStems: React.ReactNode[] = [];
+  for (let slot = 0; slot < bracket_size; slot++) {
+    const matchPos = Math.floor(slot / 2);
+    const match = matchByKey.get(`0-${matchPos}`);
+    const isPlayer1 = slot % 2 === 0;
+    const stemColor = match
+      ? getBgColor(
+          match,
+          isPlayer1 ? match.player1?.user_id : match.player2?.user_id,
+        )
+      : "bg-black";
+    playerStems.push(
+      <div
+        key={`stem-${slot}`}
+        className="flex justify-center h-4"
+        style={{ gridColumn: `${slot + 1} / span 1` }}
+      >
+        <div className={`w-0.5 ${stemColor}`} />
+      </div>,
+    );
+  }
+  rows.push(
+    <div
+      key="player-stems"
+      className="grid"
+      style={{ gridTemplateColumns: `repeat(${bracket_size}, 1fr)` }}
+    >
+      {playerStems}
+    </div>,
+  );
+
   // Player cards row (bottom)
   const playerCards: React.ReactNode[] = [];
   for (let slot = 0; slot < bracket_size; slot++) {
-- 
cgit v1.3.1

