Colophon
How this site is built
A self-updating World Cup hub — live scores, honest probabilities, a connected bracket, a play-money pool, in English and Brazilian Portuguese — generated by a pure-standard-library Python engine and served as flat files. This is the honest account of the engineering underneath, and none of it is visible from the front page.
What this is
§01This site tracks all 104 matches of the 2026 World Cup — 48 teams, 12 groups, and the knockouts through to the final at MetLife Stadium on July 19. Star any team and it lights up everywhere: its group table, its live chance of reaching the knockouts, and its projected road through the bracket. Around that sits a fully connected knockout tree, a fill-it-yourself fantasy bracket, a play-money betting pool, and a day-by-day calendar you can subscribe to as .ics. Every number is derived from the live results feed — nothing is typed in by hand. The whole thing is bilingual, with a client-side English / Brazilian-Portuguese toggle, and it re-publishes itself within about five minutes of a result landing.
The engine
§02The site is a static-site generator written in pure Python — standard library only. No framework, no bundler, no third-party packages at build time, and zero runtime dependencies. One render pass turns a JSON results feed (plus cached blurbs, odds and squads) into over 120 files: an HTML page for each of the 48 team hubs and 12 groups plus the home, bracket, fantasy, bets and calendar views; 49 subscribable .ics calendar feeds; one stylesheet; the client scripts; and the SVG art. The code is small, focused modules — feed fetch and cache, standings math, bracket resolution, timezone handling, shared HTML-fragment builders, and one module per page. Nothing runs on a server to render a page. The output is flat files a CDN serves directly.
The slot grammar
§03The openfootball feed doesn't name knockout participants until they're known — it encodes each as a slot token. A group finish like 1C (Group C winner) or 3A/B/C/D (one third-placed team), or a match edge like W76 (winner of match 76). One resolver, resolve_slot() in wc/bracket.py, turns each token into either a decided nation or the set of teams still able to fill it. That single abstraction powers the bracket connectors, every team's road, the calendar's “3 possible” cells, the fantasy picker's options, and the betting board — all reading the same resolved slots. As results land, tokens resolve to nations automatically; nothing is ever hand-edited.
1CWinner of Group C2FRunner-up of Group F3A/B/C/DA third-placed team — from Group A, B, C or DW76Winner of match 76L101Loser of match 101 — into the third-place matchHonest probabilities
§04Two different questions, two different methods. For “will they reach the knockouts?” the engine runs a seeded 20,000-iteration Monte-Carlo: it simulates every remaining group match with plausible scorelines, ranks all twelve groups, then takes the eight best third-placed teams — exactly the real qualification rule, cross-group thirds and all. The seed is fixed, so identical data yields identical published percentages. For “have they clinched?” it doesn't sample — it enumerates. With n group games left it walks all 3n win/draw/loss combinations and shows a clinch-or-eliminated badge only when it holds in every single one. A badge is a proof, not a forecast.
Determinism as deploy control
§05The build is deterministic by contract: identical input data produces byte-identical output. The daily job leans on that. A GitHub Action polls the results feed every five minutes through the match window, but rewrites the cached feed and its timestamp only when something actually changed — so an unchanged poll regenerates the exact same bytes, produces no git diff, and triggers no deploy. Each real result is therefore exactly one Cloudflare Pages deploy, keeping the tournament far under the free tier's 500-a-month. Determinism is tested, too: five real tournament snapshots lifted from this repo's own git history — early groups, the thirds race, groups complete, mid-knockout with penalty shootouts, and today — get rebuilt and hash-compared, so any silent change to output is caught.
The live layer
§06openfootball posts a result at full time, which is perfect for standings but blind during a match. So a thin Cloudflare Pages Function proxies ESPN's public scoreboard and returns a small normalized JSON the page polls for in-progress scores, edge-cached about 25 seconds so a crowd can't multiply upstream calls. The polling is kickoff-aware: each match carries its kickoff instant, and the client sleeps until just before the next one rather than hammering the endpoint all day. It's pure progressive enhancement — the static page already shows every kickoff time and every final score, so if the proxy is unreachable nothing breaks and live scores simply don't appear. Name reconciliation (USA ↔ United States) happens client-side to keep the proxy a thin pass-through.
The betting pool
§07The play-money pool is a real backend on a static site: a Cloudflare D1 (SQLite) database and a handful of Functions. Everyone starts with $100 and bets on knockout winners at odds snapshotted the moment the bet is placed. The money handling is written to survive concurrent requests. Stakes are debited with a guarded conditional write — subtracted only if the balance still covers them — so two simultaneous bets can't both overdraw. Cancellations delete-then-refund using SQL RETURNING, so a concurrent edit can't over-refund. Settlement runs on every page load and is idempotent and atomic: winners are credited exactly once inside a single transaction. Identity is deliberately passwordless — a display name plus a shared pool code is the whole credential, a stated friends-only, not-real-money tradeoff.
Bilingual AI editorial
§08Each team's “road to the final” blurb is written by Claude — a short sports-desk paragraph recapping the last match and framing the next — then translated into Brazilian Portuguese by a second Claude pass. Neither runs at build time. Both are cached and keyed by a fingerprint: a SHA-256 of the exact facts that shaped the prompt (the opponent, the round, the kickoff, the candidate pool). When a result moves a team's situation its fingerprint changes and only that one blurb regenerates; everything else is untouched. The Portuguese cache is stamped with the English blurb's fingerprint, and the page serves a translation only while those fingerprints still match — so a stale translation can never publish. It silently falls back to English instead.
Colophon
§09- Stack
- Python 3.12 (standard library) generating static HTML, CSS and vanilla JavaScript; hosted on Cloudflare Pages with Pages Functions and a D1 database. No framework, no bundler, no CSS or JS dependencies.
- Data
- Match results from openfootball/worldcup.json (public domain); betting odds from The Odds API, de-vigged and folded into two-way prices; live in-match scores proxied from ESPN's public scoreboard.
- Editorial
- Road-to-the-final blurbs and their pt-BR translations written by Claude (Anthropic), cached and fingerprinted.
- Type & art
- System neo-grotesque and monospace faces; original SVG marks; a self-hosted flag webfont is the only font fetched over the wire, so Windows renders flag emoji instead of two-letter codes.
- Design
- “Broadcast Ink” — newsprint paper, near-black ink, one vermilion accent, tracked mono labels, hard edges.
- Built by
- Scott Florida.