This commit is contained in:
FURRO404
2026-06-29 04:51:34 -07:00
parent 2885dbed19
commit cd1743e78a
2 changed files with 91 additions and 1 deletions
+69
View File
@@ -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 (
<div className={`overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm${active ? ' has-trace' : ''}`}>
<p className="border-b border-border px-5 py-3 text-xs font-semibold uppercase tracking-wide text-fury-cyan">
TEAM TOTALS
</p>
<div className="grid grid-cols-[1fr_3rem_3rem_4rem] gap-2 border-b border-border px-5 py-3 text-xs font-semibold uppercase tracking-wide text-text-soft">
<p>Team</p>
<p className="text-center">W</p>
<p className="text-center">L</p>
<p className="text-center">WR</p>
</div>
{teams.map((team) => {
const traced = traceClass([team.name.toLowerCase()], highlight)
const wr = team.total > 0 ? Math.round(team.wins / team.total * 100) : 0
return (
<div
className={`grid grid-cols-[1fr_3rem_3rem_4rem] items-center gap-2 border-b border-surface px-5 py-2.5 text-sm team-total-row${traced}`}
key={team.name}
onMouseEnter={() => onHover?.({ [team.name.toLowerCase()]: 'win' })}
onMouseLeave={() => onHover?.(null)}
>
<button
className="min-w-0 truncate text-left font-semibold transition hover:underline"
onClick={() => navigate(teamPath(team.name))}
type="button"
>
{team.name}
</button>
<span className="text-center text-win tabular-nums">{team.wins}</span>
<span className="text-center text-loss tabular-nums">{team.losses}</span>
<span className="text-center tabular-nums">{wr}%</span>
</div>
)
})}
</div>
)
}
function TournamentDetailPage({ tournamentId, navigate }) { function TournamentDetailPage({ tournamentId, navigate }) {
const [state, setState] = useState({ status: 'loading', data: null, error: null }) const [state, setState] = useState({ status: 'loading', data: null, error: null })
@@ -5564,6 +5627,9 @@ function TournamentDetailPage({ tournamentId, navigate }) {
}, [matches]) }, [matches])
const bracketSides = sides.filter((side) => side.kind === 'bracket') const bracketSides = sides.filter((side) => side.kind === 'bracket')
const listSides = sides.filter((side) => side.kind === 'list') 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 standings = data?.standings || []
const hasStandings = standings.length > 0 const hasStandings = standings.length > 0
// Lit-up teams shared across both brackets, so a run lights in the winner // Lit-up teams shared across both brackets, so a run lights in the winner
@@ -5622,6 +5688,9 @@ function TournamentDetailPage({ tournamentId, navigate }) {
{bracketSides.length ? <h2 className="text-lg font-semibold">Group stage</h2> : null} {bracketSides.length ? <h2 className="text-lg font-semibold">Group stage</h2> : null}
{hasStandings ? <TournamentStandings standings={standings} /> : null} {hasStandings ? <TournamentStandings standings={standings} /> : null}
{listSides.length ? <TournamentMatchList highlight={highlight} navigate={navigate} onHover={onHover} sides={listSides} /> : null} {listSides.length ? <TournamentMatchList highlight={highlight} navigate={navigate} onHover={onHover} sides={listSides} /> : null}
{swissMatches.length ? (
<TeamTotals matches={swissMatches} navigate={navigate} highlight={highlight} onHover={onHover} />
) : null}
</div> </div>
) : null} ) : null}
+22 -1
View File
@@ -1180,11 +1180,32 @@ h3 {
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.bracket-line-flow, .bracket-line-flow,
.tournament-match-node.is-traced > *, .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; 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 { @keyframes scrollPulse {
0% { 0% {
transform: translateY(-100%); transform: translateY(-100%);