6.6 KiB
Developer guide — World Cup 2026 Hub
Everything you need to run, understand, deploy and maintain the site. For a non-technical overview of what the project is, see the README.
The app is a static single-page app: one index.html, vanilla ES-module JavaScript, and JSON
files as the only "database". There is no backend, no build step, no bundler, and no framework.
You maintain the site by editing JSON — the code never needs to change to update scores or teams.
Run locally
python -m http.server
# then open http://localhost:8000
Any static file server works. A server is required — opening index.html directly from disk
fails, because browsers block fetch() of local JSON files (file://).
Project structure
worldcup2026/
├── index.html SPA shell (header, tabs, hero, panels, modal root)
├── manifest.json PWA manifest · favicon.ico
├── assets/
│ ├── css/ style.css · bracket.css · stats.css · animations.css
│ ├── js/ app.js (entry) · schedule.js · groups.js · bracket.js
│ │ stats.js · modal.js · stadiums.js · storage.js
│ │ i18n.js · calendar.js
│ ├── images/ flags/*.svg · stadiums/*.svg
│ └── icons/ PWA app icons + favicons
└── data/ ← the only thing you edit to maintain the site
├── teams.json 48 teams: { id, name, flag }
├── groups.json { "A": [4 team ids], … } × 12
├── matches.json 104 matches (UTC times; knockout uses bracketRef)
├── results.json one entry per match: scores + status (+ penalties, + optional stats)
├── stadiums.json name, city, capacity, image, IANA timezone
└── bracket-config.json Round-of-32 slots + best-third assignment
Maintaining the data
The data is real World Cup 2026 data, refreshed as the tournament progresses. Everything the site shows is derived from the six
data/*.jsonfiles — no code changes required.The day-to-day refresh routine is documented in
how-refresh-data.md; the original mock → real migration is kept as a schema reference inhow-update.md.
Updating a result
Edit the match's entry in data/results.json:
{ "matchId": 74, "homeScore": 1, "awayScore": 1,
"penalties": { "home": 4, "away": 3 }, "status": "finished" }
status:scheduled→live→finished. Standings and the bracket only countfinishedmatches.penaltiesis optional — only for knockout matches decided on penalties.
Adding / changing matches
data/matches.json. All times are UTC (the UI converts to local or stadium time). Group matches
carry homeTeam/awayTeam; knockout matches carry a bracketRef
(R32-1…R32-16, R16-1…, QF-…, SF-…, THIRD-PLACE, FINAL) and their teams are resolved
automatically from the standings.
After the group stage: fill the third-place slots
data/bracket-config.json is the only file to edit once the 8 best third-placed teams are known.
Map each slot to a group letter (per FIFA's official combination table):
"thirdPlaceAssignment": { "1": "D", "2": "F", "3": "B", "…": "…" }
A slot's team becomes standings[group][3rd]. Slots left null show a "Best 3rd #N" placeholder. The
16 round32 entries define the bracket order (array position = bracket position) — they normally
never change.
Teams, stadiums, images
teams.json—flagis a path relative toassets/images/(e.g.flags/bra.svg).stadiums.json—timezonemust be a valid IANA name (e.g.America/Mexico_City); it drives the "stadium time" display and stays correct across DST.- Replace the SVGs in
assets/images/with new artwork keeping the same file names (or update the JSON paths).
UI labels (EN / PT)
Every user-facing string goes through t(key) — add new labels to both dictionaries in
assets/js/i18n.js. Data values (team/stadium names, cities) come from JSON and are not
translated.
Local storage
The app never modifies the JSON data. User state lives in the browser under wc2026_* keys:
| Key | Content |
|---|---|
wc2026_simulation |
{ "R32-6": { "winner": "FRA", "score": "2-1" }, … } |
wc2026_favorites |
["BRA", "MEX"] |
wc2026_prefs |
{ "lang": "en"|"pt", "timeMode": "local"|"stadium", "lastTab": "bracket", … } |
Clearing site data resets picks, favourites and preferences.
Deployment
The live site is deployed automatically to Hostinger over FTP by GitHub Actions
(.github/workflows/deploy.yml) on every push to master, and is
served at lucaskalil.com/worldcup2026.
- The workflow needs three repository secrets:
FTP_SERVER,FTP_USERNAME,FTP_PASSWORD. - Development/documentation files (
README.md,DEVELOPMENT.md,docs/,.agents/, the specs andhow-*.md) are excluded from the upload — onlyindex.html,assets/anddata/reach the site.
Because every asset and data path in the code is relative (never starting with /), the same
folder also works unchanged on GitHub Pages or any other static host — just publish the directory.
When editing paths, always keep them relative.
Acceptance criteria (spec §18)
- All matches are loaded via JSON
- All results are loaded via JSON
- The bracket is generated dynamically (config + standings + winner pairing)
- Works on a static host (all paths relative, no server-side code)
- Works on desktop and mobile (≤767 / 768–1100 / 1100+ breakpoints)
- Allows knockout-stage simulation (persisted, never mutates JSON)
- Smooth animations (entry, hover, bracket line-draw; reduced-motion safe)
- No backend dependency — fully static
Performance: total JS ≈ 74 KB across the ES modules (budget: < 300 KB), no external dependencies, no blocking third-party requests.
Roadmap (spec §19)
Dark/light theme, real-time statistics via a results API, FIFA ranking integration, World Cup history,
expanded player-level stats, and push notifications. A service-worker offline mode (PWA "Tier 2") is
designed but deliberately deferred — see .agents/issues.md.
Internal documentation
Deeper architecture notes, decisions and gotchas live in the .agents/ folder
(project-map.md, project-memory.md, stats-screen-plan.md, issues.md, TODO.md). Read those
before making significant changes.