2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00
2026-06-22 15:32:32 -05:00

WM 2026 Board

Live-Gruppentabellen, Drittplatzierten-Wertung und vollständiger K.o.-Baum der FIFA WM 2026 — mit korrekter Annex-C-Zuordnung der acht besten Gruppendritten und optionalen Polymarket-Wahrscheinlichkeiten pro Spiel.

Gebaut als Next.js 15 (App Router) mit serverseitigem Feed-Proxy und Caching, Docker-Standalone-Image, ausgelegt für Caddy als Reverse Proxy.

Warum ein Backend-Proxy?

Das Frontend kann die Feeds nicht direkt aufrufen:

  • football-data.org erlaubt im Free-Tier nur 10 Anfragen/Minute und verlangt einen X-Auth-Token (im Browser nicht sicher unterzubringen).
  • Polymarket Gamma liefert outcomePrices als JSON-String und hat Rate Limits.
  • Beide setzen kein CORS für beliebige Browser-Origins.

Deshalb holt die Route app/api/matches beide Feeds serverseitig, cached sie in-memory (Spiele 60 s, Odds 120 s) und liefert ein fertiges JSON. Das Frontend pollt nur diese eine eigene Route (alle 30 s).

Architektur

Browser ──poll 30s──> /api/matches (Next.js, gecacht)
                          ├── football-data.org/v4/competitions/WC/matches
                          └── gamma-api.polymarket.com/events?slug=...
                          │
                          ├── computeGroupTables()      (FIFA-Tiebreaker)
                          ├── computeThirdPlaceTable()  (beste 8 Dritte)
                          └── resolveAnnexC()           (495 Szenarien, lib/annexc-data.ts)

Die Annex-C-Tabelle (lib/annexc-data.ts) enthält alle 495 möglichen Kombinationen aus den FIFA-Wettbewerbsregeln. Sobald genau 8 Gruppendritte feststehen, ordnet resolveAnnexC() jedem der acht betroffenen Gruppensieger (A, B, D, E, G, I, K, L) seinen Drittplatzierten zu, und der Bracket „verbindet" sich automatisch.

Schnellstart (lokal)

cp .env.example .env      # FOOTBALL_DATA_TOKEN eintragen
npm install
npm run dev               # http://localhost:3000

Token kostenlos registrieren: https://www.football-data.org/client/register

Deployment auf dem VPS (Docker + Caddy)

Das Projekt geht davon aus, dass Caddy bereits als Reverse Proxy in einem externen Docker-Netz läuft. Heißt dein Netz anders, in docker-compose.yml und im Caddy-Snippet anpassen.

# 1. Netz prüfen/anlegen (falls noch nicht vorhanden)
docker network ls | grep caddy_net || docker network create caddy_net

# 2. .env anlegen
cp .env.example .env && nano .env

# 3. Bauen und starten
docker compose up -d --build

# 4. Caddy-Route einbinden
#    Inhalt von Caddyfile.snippet in deinen Caddyfile übernehmen,
#    Domain anpassen, dann Caddy neu laden:
docker exec -w /etc/caddy caddy caddy reload

Danach ist die Seite unter https://wm.deine-domain.de erreichbar. Caddy besorgt TLS automatisch.

Konfiguration (.env)

Variable Pflicht Beschreibung
FOOTBALL_DATA_TOKEN ja Token von football-data.org. Ohne ihn liefert die API nur die Wettbewerbsliste.
POLYMARKET_WC_SLUG nein Slug des WM-Events auf Polymarket (Standard world-cup-2026). Den exakten Slug aus der URL nach /event/ ablesen.

Feeds verifizieren

Beide Upstream-Strukturen vor dem Turnierstart kurz gegenchecken (Felder können sich ändern):

# football-data: Gruppenfeld "GROUP_X", score.fullTime, stage
curl -H "X-Auth-Token: $FOOTBALL_DATA_TOKEN" \
  "https://api.football-data.org/v4/competitions/WC/matches" | jq '.matches[0]'

# Polymarket: markets[].outcomes / outcomePrices (JSON-Strings!)
curl "https://gamma-api.polymarket.com/events?slug=world-cup-2026" | jq '.[0].markets[0]'

Stimmen Feldnamen nicht, nur die Adapter in lib/feeds.ts anpassen — der Rest (Tabellen, Annex C, Bracket) ist davon entkoppelt.

Was anpassbar ist

  • Anderer Ergebnis-Feed (API-Football, TheStatsAPI): nur fetchMatchesAndTeams in lib/feeds.ts neu schreiben, sodass es Match[] + Team[] liefert.
  • Odds-Matching: attachOdds verknüpft per Teamname im Markttitel. Bei abweichenden Namen (z. B. „USA" vs. „United States") dort eine Alias-Tabelle ergänzen.
  • Poll-Intervall: in app/page.tsx (setInterval, Standard 30 s) und den Cache-TTLs in lib/feeds.ts.

Korrektheit der Annex-C-Logik

lib/annexc-data.ts wurde gegen die veröffentlichte FIFA-/Wikipedia-Tabelle geprüft. Beispiel Szenario 415 (Dritte aus A,B,C,E,F,H,I,J) ergibt die Zuordnung H J B C A F E I für die Gruppensieger A,B,D,E,G,I,K,L — exakt wie in den offiziellen Regeln.

Lizenz / Hinweise

Kein offizielles FIFA-Produkt. football-data.org ist für nicht-kommerzielle Nutzung kostenlos; bei kommerzieller Nutzung dort anfragen. Polymarket-Zugang und -Daten unterliegen den jeweiligen lokalen Bestimmungen.

Description
No description provided
Readme 944 KiB
Languages
TypeScript 82.4%
CSS 17%
Dockerfile 0.4%
Vim Snippet 0.1%