From 7bad12df6816afc9f22ddec1027ffa35a9925a01 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Sat, 27 Jun 2026 15:48:48 -0500 Subject: [PATCH] stadien --- app/components/Bracket.tsx | 57 ++++++++++++++++++++++++++++++++------ app/globals.css | 4 +++ lib/stadiums.ts | 27 ++++++++++++++++++ 3 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 lib/stadiums.ts 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 && (
Spiel um Platz 3
- +
)}
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", +};