From 1c02c51387164fc15467364058f8f00212f599e4 Mon Sep 17 00:00:00 2001 From: Clippii Date: Tue, 23 Jun 2026 03:09:54 +0100 Subject: [PATCH 1/8] slop --- frontend/src/App.jsx | 55 ++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7a67560..1157588 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5319,26 +5319,47 @@ function TournamentStandings({ standings }) { ) } +function TournamentListSide({ side, navigate }) { + const [collapsed, setCollapsed] = useState(false) + return ( +
+ + {collapsed ? null : ( +
+ {side.columns.map((column) => ( +
+

{column.label}

+
+ {column.matches.map((match) => ( + + ))} +
+
+ ))} +
+ )} +
+ ) +} + function TournamentMatchList({ sides, navigate }) { return (
- {sides.map((side) => { - const matches = side.columns.flatMap((column) => column.matches) - return ( -
-

{side.label}

-
- {matches.map((match) => ( - - ))} -
-
- ) - })} + {sides.map((side) => ( + + ))}
) } From 285bddb968fcb4b6ac9cb7fc7e0e3d86097b7be1 Mon Sep 17 00:00:00 2001 From: Clippii Date: Tue, 23 Jun 2026 03:16:58 +0100 Subject: [PATCH 2/8] slop --- frontend/src/App.jsx | 51 ++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1157588..efff9f7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5319,6 +5319,34 @@ function TournamentStandings({ standings }) { ) } +function TournamentListColumn({ column, navigate }) { + const [collapsed, setCollapsed] = useState(false) + return ( +
+ + {collapsed ? null : ( +
+ {column.matches.map((match) => ( + + ))} +
+ )} +
+ ) +} + function TournamentListSide({ side, navigate }) { const [collapsed, setCollapsed] = useState(false) return ( @@ -5333,22 +5361,13 @@ function TournamentListSide({ side, navigate }) { {collapsed ? null : ( -
- {side.columns.map((column) => ( -
-

{column.label}

-
- {column.matches.map((match) => ( - - ))} -
-
- ))} -
+ +
+ {side.columns.map((column) => ( + + ))} +
+
)} ) From 0795bced60f43ce6200c4c586dbd7ab0babbe1a4 Mon Sep 17 00:00:00 2001 From: Clippii Date: Tue, 23 Jun 2026 03:31:15 +0100 Subject: [PATCH 3/8] slop --- frontend/src/App.jsx | 34 ++++++++++++++++++++++++++---- frontend/src/styles.css | 46 +++++++++++++++++++++++++++++++++++++++++ vite.config.js | 3 +++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index efff9f7..df31d12 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5065,11 +5065,26 @@ function noOpponentTooltip(match, roundLabel) { // threshold pans (and suppresses the click). Move/up live on the window so a fast // drag keeps panning even when the cursor leaves the canvas. function BracketViewport({ children }) { + const containerRef = useRef(null) const ref = useRef(null) + const [fsActive, setFsActive] = useState(false) + + useEffect(() => { + const handler = () => setFsActive(Boolean(document.fullscreenElement)) + document.addEventListener('fullscreenchange', handler) + return () => document.removeEventListener('fullscreenchange', handler) + }, []) + + const toggleFs = (event) => { + event.stopPropagation() + if (document.fullscreenElement) document.exitFullscreen() + else containerRef.current?.requestFullscreen() + } const onPointerDown = (event) => { const el = ref.current if (!el || event.button !== 0) return + if (event.target.closest('[data-fs-btn]')) return const start = { x: event.clientX, y: event.clientY, sl: el.scrollLeft, st: el.scrollTop, moved: false } bracketPan.dragged = false @@ -5102,8 +5117,19 @@ function BracketViewport({ children }) { } return ( -
- {children} +
+
+ {children} +
+
) } @@ -5322,7 +5348,7 @@ function TournamentStandings({ standings }) { function TournamentListColumn({ column, navigate }) { const [collapsed, setCollapsed] = useState(false) return ( -
+
{collapsed ? null : ( -
+
{column.matches.map((match) => ( Date: Tue, 23 Jun 2026 03:36:44 +0100 Subject: [PATCH 4/8] slop --- frontend/src/App.jsx | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index df31d12..e1e0072 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5409,6 +5409,35 @@ function TournamentMatchList({ sides, navigate }) { ) } +// Swiss tournaments often have round: null on every match because the bot doesn't +// populate that field. Matches ARE returned in round order (sorted by time_start / +// match_id), so we can infer round boundaries: floor(uniqueTeams / 2) matches per +// round. Only kicks in when a list side has exactly one column (all-null rounds). +function inferSwissRounds(sides) { + return sides.map((side) => { + if (side.kind !== 'list' || side.columns.length !== 1) return side + const allMatches = side.columns[0].matches + if (!allMatches.length) return side + const teams = new Set() + allMatches.forEach((m) => { + if (m.team_a_name) teams.add(m.team_a_name) + if (m.team_b_name) teams.add(m.team_b_name) + }) + const matchesPerRound = Math.floor(teams.size / 2) + if (matchesPerRound <= 0 || allMatches.length <= matchesPerRound) return side + const columns = [] + for (let i = 0; i < allMatches.length; i += matchesPerRound) { + const roundNum = columns.length + 1 + columns.push({ + id: `${side.key}:inferred:${roundNum}`, + label: `Round ${roundNum}`, + matches: allMatches.slice(i, i + matchesPerRound), + }) + } + return { ...side, columns } + }) +} + function TournamentDetailPage({ tournamentId, navigate }) { const [state, setState] = useState({ status: 'loading', data: null, error: null }) @@ -5431,7 +5460,10 @@ function TournamentDetailPage({ tournamentId, navigate }) { const data = state.data const matches = useMemo(() => data?.matches || [], [data]) const format = useMemo(() => tournamentFormatMeta(data?.format, matches), [data?.format, matches]) - const { sides } = useMemo(() => buildBracket(matches), [matches]) + const { sides } = useMemo(() => { + const result = buildBracket(matches) + return { ...result, sides: inferSwissRounds(result.sides) } + }, [matches]) const bracketSides = sides.filter((side) => side.kind === 'bracket') const listSides = sides.filter((side) => side.kind === 'list') const standings = data?.standings || [] From 07a943ab7cfa15f1afdf21c93229fe8568f896c6 Mon Sep 17 00:00:00 2001 From: Clippii Date: Tue, 23 Jun 2026 03:48:19 +0100 Subject: [PATCH 5/8] slop --- frontend/src/App.jsx | 33 ++++++++++++--------------------- frontend/src/styles.css | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e1e0072..3f4cc85 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5348,15 +5348,15 @@ function TournamentStandings({ standings }) { function TournamentListColumn({ column, navigate }) { const [collapsed, setCollapsed] = useState(false) return ( -
+
{collapsed ? null : (
@@ -5374,27 +5374,18 @@ function TournamentListColumn({ column, navigate }) { } function TournamentListSide({ side, navigate }) { - const [collapsed, setCollapsed] = useState(false) return (
- - {collapsed ? null : ( - -
- {side.columns.map((column) => ( - - ))} -
-
- )} +

+ +
+ {side.columns.map((column) => ( + + ))} +
+
) } diff --git a/frontend/src/styles.css b/frontend/src/styles.css index f28ad29..4ea1416 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -921,13 +921,42 @@ h3 { } /* List-style column inside the bracket viewport (Swiss / group rounds). - Two cards side by side; wider than the 200px bracket column. */ + Label sits on the left as a vertical strip; cards fill the right. */ .tournament-list-column { position: relative; z-index: 1; display: flex; + flex-direction: row; + align-items: flex-start; + gap: 1rem; min-width: 440px; - flex-direction: column; +} + +.tournament-list-column.is-collapsed { + min-width: 0; +} + +.tournament-list-round-btn { + writing-mode: vertical-lr; + align-self: stretch; + display: flex; + align-items: center; + justify-content: center; + padding: 0.5rem 0.375rem; + border-right: 1px solid var(--color-border); + font-size: 0.65rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--color-text-muted); + cursor: pointer; + transition: color 0.15s, border-color 0.15s; + white-space: nowrap; +} + +.tournament-list-round-btn:hover { + color: var(--color-text); + border-color: var(--color-text-muted); } .bracket-caret { From ffd68f0e13b445dceb2fad2d2ab799ec0d5feded Mon Sep 17 00:00:00 2001 From: Clippii Date: Tue, 23 Jun 2026 03:53:10 +0100 Subject: [PATCH 6/8] slop --- frontend/src/App.jsx | 1 + frontend/src/styles.css | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3f4cc85..5fe090f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5357,6 +5357,7 @@ function TournamentListColumn({ column, navigate }) { type="button" > {column.label} + {collapsed ? null : (
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 4ea1416..c5e99ed 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -959,6 +959,26 @@ h3 { border-color: var(--color-text-muted); } +/* Caret resets to horizontal so it renders upright inside the vertical label. */ +.tournament-list-round-caret { + writing-mode: horizontal-tb; + display: inline-block; + font-size: 1rem; + line-height: 1; + transition: transform 0.18s ease; + margin-top: 0.4rem; +} + +.tournament-list-round-caret.is-collapsed { + transform: rotate(-90deg); +} + +/* Divider between round columns. */ +.tournament-list-column:not(:last-child) { + border-right: 1px solid var(--color-border); + padding-right: 1rem; +} + .bracket-caret { display: inline-block; font-size: 1.35rem; From 8b8f645ec031459ae5baec3960476c5b49c5cfc7 Mon Sep 17 00:00:00 2001 From: Clippii Date: Tue, 23 Jun 2026 03:56:48 +0100 Subject: [PATCH 7/8] slop --- frontend/src/App.jsx | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5fe090f..b7d061b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5345,7 +5345,7 @@ function TournamentStandings({ standings }) { ) } -function TournamentListColumn({ column, navigate }) { +function TournamentListColumn({ column, navigate, highlight, onHover }) { const [collapsed, setCollapsed] = useState(false) return (
@@ -5361,29 +5361,31 @@ function TournamentListColumn({ column, navigate }) { {collapsed ? null : (
- {column.matches.map((match) => ( - - ))} + {column.matches.map((match) => { + const traced = traceClass(teamsOf(match), highlight) + return ( +
+ +
+ ) + })}
)}
) } -function TournamentListSide({ side, navigate }) { +function TournamentListSide({ side, navigate, highlight, onHover }) { + const active = Boolean(highlight) return (

{side.label}

-
+
{side.columns.map((column) => ( - + ))}
@@ -5391,11 +5393,11 @@ function TournamentListSide({ side, navigate }) { ) } -function TournamentMatchList({ sides, navigate }) { +function TournamentMatchList({ sides, navigate, highlight, onHover }) { return (
{sides.map((side) => ( - + ))}
) @@ -5515,7 +5517,7 @@ function TournamentDetailPage({ tournamentId, navigate }) {
{bracketSides.length ?

Group stage

: null} {hasStandings ? : null} - {listSides.length ? : null} + {listSides.length ? : null}
) : null} From e3f3220245ff4747193b7141b306016f2d93f07a Mon Sep 17 00:00:00 2001 From: Clippii Date: Tue, 23 Jun 2026 04:01:19 +0100 Subject: [PATCH 8/8] slop --- frontend/src/App.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b7d061b..29e5cc7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5364,7 +5364,7 @@ function TournamentListColumn({ column, navigate, highlight, onHover }) { {column.matches.map((match) => { const traced = traceClass(teamsOf(match), highlight) return ( -
+
)