From cd1743e78af3812fda94bf2675de1f038051278b Mon Sep 17 00:00:00 2001 From: FURRO404 Date: Mon, 29 Jun 2026 04:51:34 -0700 Subject: [PATCH] meowwww --- frontend/src/App.jsx | 69 +++++++++++++++++++++++++++++++++++++++++ frontend/src/styles.css | 23 +++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9613bbe..3de55e2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5536,6 +5536,69 @@ function inferSwissRounds(sides) { }) } +function TeamTotals({ matches, navigate, highlight, onHover }) { + const teams = useMemo(() => { + const map = {} + for (const match of matches) { + const teamA = displayTeamName(match.team_a_name) + const teamB = displayTeamName(match.team_b_name) + const winner = displayTeamName(match.winner_name) + for (const name of [teamA, teamB]) { + if (!name) continue + if (!map[name]) map[name] = { wins: 0, losses: 0 } + if (winner) { + if (winner === name) map[name].wins++ + else map[name].losses++ + } + } + } + return Object.entries(map) + .map(([name, s]) => ({ name, wins: s.wins, losses: s.losses, total: s.wins + s.losses })) + .sort((a, b) => b.wins - a.wins || a.losses - b.losses || a.name.localeCompare(b.name)) + }, [matches]) + + if (!teams.length) return null + + const active = Boolean(highlight) + + return ( +
+

+ TEAM TOTALS +

+
+

Team

+

W

+

L

+

WR

+
+ {teams.map((team) => { + const traced = traceClass([team.name.toLowerCase()], highlight) + const wr = team.total > 0 ? Math.round(team.wins / team.total * 100) : 0 + return ( +
onHover?.({ [team.name.toLowerCase()]: 'win' })} + onMouseLeave={() => onHover?.(null)} + > + + {team.wins} + {team.losses} + {wr}% +
+ ) + })} +
+ ) +} + function TournamentDetailPage({ tournamentId, navigate }) { const [state, setState] = useState({ status: 'loading', data: null, error: null }) @@ -5564,6 +5627,9 @@ function TournamentDetailPage({ tournamentId, navigate }) { }, [matches]) const bracketSides = sides.filter((side) => side.kind === 'bracket') const listSides = sides.filter((side) => side.kind === 'list') + const swissMatches = useMemo(() => { + return listSides.filter((s) => s.key === 'swiss').flatMap((s) => s.columns.flatMap((c) => c.matches)) + }, [listSides]) const standings = data?.standings || [] const hasStandings = standings.length > 0 // Lit-up teams shared across both brackets, so a run lights in the winner @@ -5622,6 +5688,9 @@ function TournamentDetailPage({ tournamentId, navigate }) { {bracketSides.length ?

Group stage

: null} {hasStandings ? : null} {listSides.length ? : null} + {swissMatches.length ? ( + + ) : null} ) : null} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index c5e99ed..7214934 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1180,11 +1180,32 @@ h3 { @media (prefers-reduced-motion: reduce) { .bracket-line-flow, .tournament-match-node.is-traced > *, - .tournament-match-node.is-traced-loss > * { + .tournament-match-node.is-traced-loss > *, + .team-total-row.is-traced, + .team-total-row.is-traced-loss { animation: none; } } +/* Team totals row trace */ +.team-total-row { + transition: opacity 0.18s ease; +} + +.has-trace .team-total-row:not(.is-traced):not(.is-traced-loss) { + opacity: 0.32; +} + +.team-total-row.is-traced { + box-shadow: inset 0 0 0 1.5px var(--color-win), 0 0 18px -2px color-mix(in srgb, var(--color-win) 70%, transparent); + animation: bracketBreathe 1.6s ease-in-out infinite; +} + +.team-total-row.is-traced-loss { + box-shadow: inset 0 0 0 1.5px var(--color-loss), 0 0 18px -2px color-mix(in srgb, var(--color-loss) 70%, transparent); + animation: bracketBreatheLoss 1.6s ease-in-out infinite; +} + @keyframes scrollPulse { 0% { transform: translateY(-100%);