diff --git a/app/components/Bracket.tsx b/app/components/Bracket.tsx
index 50d7f84..d044711 100644
--- a/app/components/Bracket.tsx
+++ b/app/components/Bracket.tsx
@@ -1,9 +1,10 @@
"use client";
-import { useState, useCallback } from "react";
+import { useMemo, useState, useCallback } from "react";
import { GroupTable, Match, Team, ThirdPlaceRow } from "@/lib/types";
import { ThirdAssignment, orderedRound } from "@/lib/bracket";
import { resolveBracket, ResolvedSide, ResolvedTie } from "@/lib/resolve-bracket";
+import { STADIUMS, MATCH_STADIUMS } from "@/lib/stadiums";
import Flag from "./Flag";
interface TipState { x: number; y: number; text: string }
@@ -47,16 +48,29 @@ function Side({
);
}
+function fmtMatchInfo(utcDate?: string, stadiumId?: string): string {
+ const city = stadiumId ? STADIUMS[stadiumId]?.city : "";
+ if (!utcDate) return city; // Stadt auch ohne Datum anzeigen
+ const d = new Date(utcDate);
+ const dateStr = d.toLocaleDateString("de-DE", { day: "numeric", month: "numeric" });
+ const timeStr = d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
+ const parts = [`${dateStr}`, `${timeStr}`];
+ if (city) parts.push(city);
+ return parts.join(" · ");
+}
+
function Tie({
- tie, teams, isFinal, onShowTip, onHideTip,
+ tie, teams, isFinal, matchInfo, onShowTip, onHideTip,
}: {
tie: ResolvedTie; teams: Team[]; isFinal?: boolean;
+ matchInfo?: string;
onShowTip: (text: string, x: number, y: number) => void;
onHideTip: () => void;
}) {
return (
Spiel {tie.matchNumber}
+ {matchInfo &&
{matchInfo}
}
@@ -77,6 +91,33 @@ export default function Bracket({
const r32ByNum = new Map(r32Array.map((t) => [t.matchNumber, t]));
+ // Baue slotNumber → formatted info (Stadt aus MATCH_STADIUMS, Datum/Uhrzeit aus Feed-Match)
+ const feedByNum = useMemo(() => {
+ const m = new Map();
+ for (const fm of matches) {
+ if (fm.group == null) m.set(fm.matchNumber, fm);
+ }
+ return m;
+ }, [matches]);
+
+ const matchMeta = useMemo(() => {
+ const meta = new Map();
+ for (let n = 73; n <= 104; n++) {
+ const stadiumId = MATCH_STADIUMS[n];
+ const feedMatch = feedByNum.get(n);
+ const info = fmtMatchInfo(feedMatch?.utcDate, stadiumId);
+ if (info) meta.set(n, info);
+ if (feedMatch?.matchNumber === 0 || !feedMatch) {
+ console.log("[KO-SLOT-DATE]",
+ "slot:", n,
+ "| match.matchNumber:", feedMatch?.matchNumber ?? "—",
+ "| match.utcDate:", feedMatch?.utcDate ?? "—",
+ "| stadt:", STADIUMS[MATCH_STADIUMS[n]]?.city ?? "—");
+ }
+ }
+ return meta;
+ }, [feedByNum]);
+
const sfOrder = orderedRound([104]);
const qfOrder = orderedRound(sfOrder);
const r16Order = orderedRound(qfOrder);
@@ -109,35 +150,35 @@ export default function Bracket({
Letzte 32
- {r32.map((t) => )}
+ {r32.map((t) => )}
Achtelfinale
- {r16.map((t) => )}
+ {r16.map((t) => )}
Viertelfinale
- {qf.map((t) => )}
+ {qf.map((t) => )}
Halbfinale
- {sf.map((t) => )}
+ {sf.map((t) => )}
Finale
- {fin &&
}
+ {fin &&
}
{third && (
)}
diff --git a/app/globals.css b/app/globals.css
index 61dd1d7..2101cd2 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -224,6 +224,10 @@ table.standings { width: 100%; border-collapse: collapse; }
font-family: var(--font-mono); font-size: 9px; color: var(--ink-faint);
letter-spacing: 0.04em; padding: 5px 10px 0;
}
+.tie-meta {
+ font-family: var(--font-mono); font-size: 8px; color: var(--ink-dim);
+ padding: 2px 10px 0;
+}
.side {
display: flex; align-items: center; justify-content: space-between;
padding: 8px 10px; font-size: 13px;
diff --git a/lib/stadiums.ts b/lib/stadiums.ts
new file mode 100644
index 0000000..01bc785
--- /dev/null
+++ b/lib/stadiums.ts
@@ -0,0 +1,27 @@
+// Statische Stadion-Daten für WM 2026.
+export const STADIUMS: Record
= {
+ "1": { name: "Estadio Azteca", city: "Mexico City" },
+ "2": { name: "Estadio Akron", city: "Guadalajara" },
+ "3": { name: "Estadio BBVA", city: "Monterrey" },
+ "4": { name: "AT&T Stadium", city: "Dallas" },
+ "5": { name: "NRG Stadium", city: "Houston" },
+ "6": { name: "GEHA Field at Arrowhead Stadium", city: "Kansas City" },
+ "7": { name: "Mercedes-Benz Stadium", city: "Atlanta" },
+ "8": { name: "Hard Rock Stadium", city: "Miami" },
+ "9": { name: "Gillette Stadium", city: "Boston" },
+ "10": { name: "Lincoln Financial Field", city: "Philadelphia" },
+ "11": { name: "MetLife Stadium", city: "New York/New Jersey" },
+ "12": { name: "BMO Field", city: "Toronto" },
+ "13": { name: "BC Place", city: "Vancouver" },
+ "14": { name: "Lumen Field", city: "Seattle" },
+ "15": { name: "Levi's Stadium", city: "San Francisco Bay Area" },
+ "16": { name: "SoFi Stadium", city: "Los Angeles" },
+};
+
+// K.o.-Spielnummer → stadium_id (FIFA-Quelle, statisch).
+export const MATCH_STADIUMS: Record = {
+ 73: "16", 74: "9", 75: "3", 76: "5", 77: "11", 78: "4", 79: "1", 80: "7",
+ 81: "15", 82: "14", 83: "12", 84: "16", 85: "13", 86: "8", 87: "6", 88: "4",
+ 89: "10", 90: "5", 91: "11", 92: "1", 93: "4", 94: "14", 95: "7", 96: "13",
+ 97: "9", 98: "16", 99: "8", 100: "6", 101: "4", 102: "7", 103: "8", 104: "11",
+};