NilsBriggen a1fb6d48c0 Fan Tier 2: venue pages, attack zones, broadcasts, push prefs, squads + UI/UX polish
Features (all from already-stored data):
- Venue pages: /venues hub + /venue/:name with each stadium's schedule,
  altitude badge (>1000 m), local timezone. Linked from the match hero,
  nav More sheet, and footer.
- Attacking-zones card on the match Stats tab (left/center/right share per
  side, from the stored FotMob payload).
- US broadcast networks on the pre-match hero (SUMMARY_V 6).
- Notification preferences: follow multiple teams + an all-matches feed
  ('*'); push_subs keyed by (endpoint, team) with a safe one-time migration;
  device drops its browser subscription only when the last follow is removed.
- FIFA tournament squad card on team pages (26-man list, position groups).
- Player match logs now show DNP rows for unused-squad appearances.
- OG/share meta for /player and /venue.

Polish (adversarial audit: 25 confirmed findings, all fixed):
- Mobile horizontal-scroll eliminated on /teams and /venues (grid-item
  min-w-0 blowout); two-line venue fixture rows.
- Truncation actually fires now across team fixtures, road-to-final,
  recent-form, H2H meetings, venue rows (min-w-0 on flex children).
- Team hero no longer detaches the standing block on wrap; bell denied-state
  no longer overflows the h1.
- AllMatchesBell: 36px touch target, aria-label, own i18n key, even rhythm.
- Server hardening: cached player index (one cross-fixture scan, not per
  request), migration rollback guard, 400 (not 500) on malformed name URLs,
  all-zero zones render nothing, tidyName fixes shouted particles (DE→De).
- German polish: removed stray English ("anytime", "Build-Geocode"), clearer
  squad note.

Gates: 170 tests, build, typecheck:server — all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 10:05:27 +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%