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:
@@ -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() {
|
export function GroupsPage() {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const snapshot = useTournamentStore((s) => s.snapshot);
|
const snapshot = useTournamentStore((s) => s.snapshot);
|
||||||
@@ -91,6 +124,8 @@ export function GroupsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<BestThirdsRace tables={tables} />
|
||||||
|
|
||||||
<div className="mt-5 space-y-1.5 text-xs text-faint">
|
<div className="mt-5 space-y-1.5 text-xs text-faint">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span className="inline-flex items-center gap-1.5">
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ export const de: Dict = {
|
|||||||
scAlive: "Noch möglich ({n}/{total} Ausgänge)",
|
scAlive: "Noch möglich ({n}/{total} Ausgänge)",
|
||||||
scOut: "Top 2 nicht mehr erreichbar",
|
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.",
|
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.",
|
tableHeaderPldPts: "Sp. · Pkt.",
|
||||||
colTeam: "Team",
|
colTeam: "Team",
|
||||||
colPlayed: "Sp",
|
colPlayed: "Sp",
|
||||||
|
|||||||
@@ -173,6 +173,8 @@ export const en = {
|
|||||||
scAlive: "Still possible ({n}/{total} outcomes)",
|
scAlive: "Still possible ({n}/{total} outcomes)",
|
||||||
scOut: "Can't reach the top 2",
|
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.",
|
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",
|
tableHeaderPldPts: "Pld · Pts",
|
||||||
colTeam: "Team",
|
colTeam: "Team",
|
||||||
colPlayed: "P",
|
colPlayed: "P",
|
||||||
|
|||||||
Reference in New Issue
Block a user