meowwww
This commit is contained in:
@@ -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 }) {
|
||||
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 ? <h2 className="text-lg font-semibold">Group stage</h2> : null}
|
||||
{hasStandings ? <TournamentStandings standings={standings} /> : 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>
|
||||
) : null}
|
||||
|
||||
|
||||
+22
-1
@@ -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%);
|
||||
|
||||
Reference in New Issue
Block a user