NilsBriggen 05010771a3 German language option + a full wording-clarity pass
A tiny typed i18n layer (no library): en.ts is the single source of UI
copy and the clarity rewrite lives there — shorter sentences, plain
words, the dense Methodology/Scoreboard copy untangled. de.ts mirrors
its shape exactly, enforced by the compiler, with placeholder parity
covered by tests. Every page now reads from the dictionary via useT();
dates flip locale through useFormat() (en-GB/de-DE); raw server values
(status, outcome, stage) map through total records.

The header gains an EN/DE toggle (persisted, defaults from the
browser language); <html lang> stays in sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:20:07 +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%