NilsBriggen b823bef2d0 Match center v2: xG race, shot map, attack momentum, weather, officials
The captured FotMob/FIFA/Open-Meteo data becomes visible. Match pages
now carry: a cumulative xG race with goal dots and team totals, a full-
pitch shot map (each side attacking its own end, dots sized by xG,
goals filled), the 90-minute attack-momentum chart, player-of-the-match
with rating, the official info line (stadium · attendance · referee),
and a kickoff-hour weather line for upcoming matches with an altitude
chip above 1,000m. All hand-rolled SVG on the existing token
conventions — design-ready for the upcoming visual overhaul. EN/DE
included, with an honest 'xG and ratings are FotMob estimates' note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:10:35 +02:00

Cup26 — World Cup 2026 dashboard

A local-first PWA for the 2026 FIFA World Cup: live scores, a model-driven prediction engine, and data-story visualizations — all in one TypeScript app.

  • Live — fixtures, group tables and a knockout bracket that update over a WebSocket as results come in.
  • Predict — an Elo + Dixon-Coles + Monte-Carlo model that simulates the whole tournament: per-match win/draw/loss, championship odds, and a title-race chart that moves with every result.
  • Story — StatsBomb open-data visualizations (shot map, xG race, pass networks) of the 2022 World Cup final.

Stack

Vite + React 19 + TypeScript (strict), Tailwind 4, TanStack Router, Zustand, a Fastify WebSocket server, Recharts + custom SVG. Packaged as an installable PWA and a single Docker image. Bun for tooling.

Data sources

Source Used for Notes
openfootball/worldcup.json 2026 fixtures + bracket structure public domain, no key
football-data.org live scores & status free tier, World Cup included, 10 req/min, delayed
SofaScore (unofficial) near-real-time scores/events optional, fragile, feature-flagged
martj42 international results Elo / goals model training 49k matches, 1872→now
StatsBomb Open Data the Story page 2022 WC final event data

All external calls happen server-side, so the browser only talks to its own origin and the client CSP stays locked to 'self'.

Develop

bun install
bun run data:build      # generate icons + fixtures.json + ratings.json
bun run dev             # web UI on http://localhost:5173 (proxies /api + /ws)
bun run dev:server      # the Fastify live/model server on :8787 (separate shell)

Without a FOOTBALL_DATA_TOKEN the app runs in seed-only mode (full schedule, model and story; no live scores). To exercise the live pipeline in dev:

curl -XPOST localhost:8787/api/dev/score -H content-type:application/json \
  -d '{"num":1,"homeScore":2,"awayScore":1,"status":"finished"}'

Test & build

bun run test            # vitest (model + standings invariants)
bun run build           # tsc -b && vite build  (the deploy gate)

Deploy

A self-contained Docker image serves the built PWA and the live/model server.

docker build -t cup26 .
docker run -p 8787:8787 -e FOOTBALL_DATA_TOKEN=... cup26

deploy/cup26.compose.yml is a Traefik-ready Compose project (mirrors the sibling TTRPG deploy): rsync the repo to the host, drop it in as docker-compose.yml, set FOOTBALL_DATA_TOKEN, then docker compose up -d --build.

Environment

Var Default Purpose
PORT 8787 server port
FOOTBALL_DATA_TOKEN football-data.org API token (enables live scores)
ENABLE_SOFASCORE false layer the unofficial real-time source on top
SIM_ITER 20000 Monte-Carlo iterations per recompute
ALLOWED_ORIGINS comma-separated WebSocket origin allowlist

Odds are model estimates for fun, not betting advice.

S
Description
World Cup 2026 data dashboard — live scores, prediction model, StatsBomb data-story viz
Readme 4.7 MiB
Languages
TypeScript 98.1%
JavaScript 1%
CSS 0.7%
Dockerfile 0.1%