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 }) {
|
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
@@ -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%);
|
||||||
|
|||||||
Reference in New Issue
Block a user