NilsBriggen dc3c375081 Mobile overhaul: round-picker bracket, 5-tab nav + More sheet, real icons
- Bracket on phones: a segmented round picker with full-width cards
  replaces the 1040px sideways scroll; desktop keeps the columns.
- Odds table: sticky team column; the three lowest-value columns hide
  on phones so Team/QF/SF/Final/Champion fit without scrolling.
- Bottom nav: 4 primary tabs + a More sheet (vs Markt, Modell, Story,
  Teams, Daten, plus language & theme toggles) — 7 tabs were cramped.
- Emoji → icons: timeline events use a ball icon, card-shaped color
  chips and substitution arrows; unknown-team fallbacks use a shield.
  Country-flag emojis stay (intentional).
- Readability: bigger scores on match cards, minimum text sizes bumped,
  probability-number triple hides on the narrowest screens (the bar
  carries the information).

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