Best-thirds race: the cross-group table nobody can track by eye

All twelve third-placed teams ranked by points/GD/goals under the
group grid — top 8 highlighted (they reach the Round of 32), the rest
dimmed. Appears once group play has started.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 08:24:49 +02:00
parent 04dfad1a26
commit 74b6b7b77f
3 changed files with 39 additions and 0 deletions
+35
View File
@@ -41,6 +41,39 @@ function Scenarios({ scenarios }: { scenarios: TeamScenario[] }) {
);
}
/** The cross-group race nobody can track by eye: every 3rd-placed team ranked
* by points/GD/goals — the top 8 advance to the Round of 32. */
function BestThirdsRace({ tables }: { tables: Record<string, import('@/lib/types').StandingRow[]> }) {
const t = useT();
const thirds = Object.entries(tables)
.map(([g, rows]) => ({ g, r: rows.find((r) => r.rank === 3) }))
.filter((x): x is { g: string; r: import('@/lib/types').StandingRow } => Boolean(x.r))
.sort((a, b) => b.r.points - a.r.points || b.r.gd - a.r.gd || b.r.gf - a.r.gf || a.r.team.localeCompare(b.r.team));
if (thirds.length < 12 || thirds.every((x) => x.r.played === 0)) return null;
return (
<div className="mt-6 rounded-xl border border-line bg-panel">
<div className="border-b border-line px-4 py-2.5 font-display font-bold text-ink">{t.groups.thirdsRace}</div>
<ul className="px-4 py-2">
{thirds.map(({ g, r }, i) => (
<li
key={r.team}
className={`flex items-center gap-2.5 border-b border-line/40 py-1.5 text-sm last:border-0 ${i < 8 ? '' : 'opacity-55'}`}
>
<span className={`w-5 shrink-0 text-right text-xs font-bold tabular-nums ${i < 8 ? 'text-gold' : 'text-faint'}`}>{i + 1}</span>
<span aria-hidden className="shrink-0">{teamFlag(r.team)}</span>
<Link to="/team/$name" params={{ name: r.team }} className="truncate font-medium text-ink hover:text-accent">{r.team}</Link>
<span className="shrink-0 text-[11px] text-faint">{fmt(t.common.group, { group: g })}</span>
<span className="tnum ml-auto shrink-0 text-xs text-muted">
{r.played} · {r.gd > 0 ? `+${r.gd}` : r.gd} · <span className="font-bold text-ink">{r.points}</span>
</span>
</li>
))}
</ul>
<p className="border-t border-line px-4 py-2 text-[11px] text-faint">{t.groups.thirdsRaceNote}</p>
</div>
);
}
export function GroupsPage() {
const t = useT();
const snapshot = useTournamentStore((s) => s.snapshot);
@@ -91,6 +124,8 @@ export function GroupsPage() {
</div>
)}
<BestThirdsRace tables={tables} />
<div className="mt-5 space-y-1.5 text-xs text-faint">
<div className="flex items-center gap-4">
<span className="inline-flex items-center gap-1.5">
+2
View File
@@ -174,6 +174,8 @@ export const de: Dict = {
scAlive: "Noch möglich ({n}/{total} Ausgänge)",
scOut: "Top 2 nicht mehr erreichbar",
scNote: "Die Szenarien zählen Sieg/Unentschieden/Niederlage der verbleibenden Gruppenspiele; knappe Fälle können trotzdem an der Tordifferenz hängen. Platz 3 läuft über den gruppenübergreifenden Vergleich der besten Dritten.",
thirdsRace: "Rennen der besten Dritten",
thirdsRaceNote: "Alle Gruppendritten, sortiert nach Punkten, Tordifferenz und Toren — die besten 8 erreichen das Sechzehntelfinale. Spiele · TD · Punkte.",
tableHeaderPldPts: "Sp. · Pkt.",
colTeam: "Team",
colPlayed: "Sp",
+2
View File
@@ -173,6 +173,8 @@ export const en = {
scAlive: "Still possible ({n}/{total} outcomes)",
scOut: "Can't reach the top 2",
scNote: "Scenarios count win/draw/loss outcomes of the remaining group matches; tight cases can still hinge on goal difference. Third place runs through the best-thirds race across all groups.",
thirdsRace: "Best-thirds race",
thirdsRaceNote: "All third-placed teams ranked by points, goal difference and goals — the top 8 advance to the Round of 32. Played · GD · Points.",
tableHeaderPldPts: "Pld · Pts",
colTeam: "Team",
colPlayed: "P",