world-2026-hub/.agents/project-map.md

172 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Project Map — World Cup 2026 Hub
Navigation map of the codebase. Use this to find which file owns a concern before reading code.
> **Status 2026-06-12 (all 12 steps + real-data migration done):** everything works with **real World Cup 2026 data** — all views, bracket interactions, simulation, responsive/a11y pass, favorites, time toggle, challenge, share link, `.ics` export. Remaining: keep `results.json` current, fill `thirdPlaceAssignment` after the group stage (~Jun 27), Lighthouse run + GitHub Pages deploy. Spec source of truth: `world-cup-2026-hub-spec-en.md` + `complement-spec-worldcup2026-en.md` (complement **wins on conflict**).
---
## File tree
```
worldcup2026/
├── .agents/ ← Internal documentation for AI agents
│ ├── project-map.md This file
│ ├── project-memory.md Context, decisions, gotchas
│ ├── stats-screen-plan.md Plan for the post-tournament "final stats" screen
│ │ (NOT implemented — planning only, 2026-06-14)
│ └── TODO.md 12-step build checklist
├── .github/workflows/
│ └── deploy.yml CI: FTP deploy to Hostinger on push to master
│ (needs FTP_SERVER/USERNAME/PASSWORD secrets)
│ .gitignore OS/editor junk
├── index.html ★ SPA shell — header, nav tabs (Home, Matches,
│ Groups, Knockout, Stadiums, Stats), hero, dashboard,
│ modal container; loads app.js as ES module
├── assets/
│ ├── css/
│ │ ├── style.css ★ Palette variables, glassmorphism base, layout,
│ │ │ components — mobile-first
│ │ ├── bracket.css Bracket columns, connectors, highlight states
│ │ ├── stats.css Stats tab: hero "pulse", overview cards, goals-by-stage chart
│ │ └── animations.css Entry (fade-in, slide-up/left) + interaction
│ │ (hover-scale/glow, pulse, line-draw)
│ ├── js/
│ │ ├── app.js ★ Entry point: loadData() (Promise.all over data/),
│ │ │ tab routing + lastTab, formatMatchTime(), hero,
│ │ │ dashboard, countdown
│ │ ├── schedule.js Match list, filters, search, sort, "My Matches"
│ │ ├── groups.js Standings computation (3/1/0, GD, GF) + group tables
│ │ ├── stadiums.js Stadium cards + "view matches" cross-link
│ │ ├── bracket.js ★ Bracket tree resolution, resolveBracketTeams(),
│ │ │ simulation, challenge score, share prediction
│ │ ├── modal.js Match detail modal (ARIA dialog)
│ │ ├── storage.js localStorage wrapper — wc2026_* keys, auto-JSON
│ │ ├── i18n.js EN/PT-BR dicts + t(key), lang toggle
│ │ ├── stats.js ★ Stats tab: tournament-to-date aggregates (finished
│ │ │ matches only), hero pulse + overview + goals-by-stage.
│ │ │ PARTIAL (during-cup) — grows into the post-cup plan.
│ │ └── calendar.js .ics export (RFC 5545, CRLF, Blob download)
│ └── images/ Team flag SVGs, stadium placeholders
├── data/ All content — REAL WC2026 data since 2026-06-12
│ ├── teams.json 48 real qualifiers: { id, name, flag } (FIFA codes)
│ ├── groups.json Official draw { "A": [4 team ids], ... } × 12 (AL)
│ ├── matches.json 104 real fixtures; UTC times; ids 172 chronological
│ │ group games, 73104 = FIFA match numbers (bracketRef)
│ ├── results.json { matchId, homeScore, awayScore, penalties?, status } —
│ │ update as the tournament progresses
│ ├── stadiums.json 16 real venues: { id, name, city, capacity, image, timezone }
│ └── bracket-config.json ★ official R32 structure + thirdPlaceAssignment (all null) —
│ the ONLY file to edit once real 3rd places are known
│ (slot → allowed-groups table in project-memory.md)
├── README.md Setup, GitHub Pages deploy, JSON maintenance guide
├── how-update.md Real-data migration runbook (mock → real — DONE 2026-06-12)
├── how-refresh-data.md ★ Daily refresh runbook during the tournament:
│ results.json scores/status + one-time
│ thirdPlaceAssignment; everything else frozen
├── world-cup-2026-hub-spec-en.md Main spec
└── complement-spec-worldcup2026-en.md Complement spec (precedence on conflict)
```
★ = critical files. Most changes touch one of them.
---
## Key flows
### 1. Data load → render
```
index.html (type="module")
└─ app.js: loadData() ── Promise.all ──> data/*.json
├─ hero + dashboard (app.js)
├─ schedule.js ──┐
├─ groups.js ├── render into tab panels
├─ bracket.js ──┘
└─ modal.js (on match click, from any view)
```
### 2. Bracket resolution
```
groups.json + results.json
└─ groups.js: standings (pts 3/1/0 → GD → GF)
└─ bracket.js: R32 from bracket-config.json
├─ type:"group" → standings[ref][pos-1]
├─ type:"third" → standings[thirdPlaceAssignment[slot]][2] (null → placeholder)
└─ R16…FINAL: sequential pairing of winners (0-1→0, 2-3→1, …)
└─ wc2026_simulation overlays user picks (never mutates JSON)
```
### 3. Time display
```
matches.json time (UTC) ── formatMatchTime(match, stadium, mode)
├─ mode "local" → Intl.DateTimeFormat() (browser tz)
└─ mode "stadium" → Intl.DateTimeFormat({ timeZone: stadium.timezone })
```
---
## Conventions
### Imports
- ES Modules only (`type="module"`), relative paths, no bundler/CDN.
### Naming
- Team ids: 3-letter uppercase (`MEX`, `BRA`). Knockout match ids: `R32-1``R32-16`, `R16-1`…, `QF-1`…, `SF-1`/`SF-2`, `THIRD-PLACE`, `FINAL`.
- localStorage keys prefixed `wc2026_`, accessed only through `storage.js`.
### Content / i18n
- All user-facing strings go through `i18n.js` `t(key)` — never hardcode UI text in HTML/JS.
- Data values (team names, stadium names, cities) come from JSON and are not translated.
---
## Where is each thing?
| Question | Answer |
|---|---|
| Where do I update scores / match status? | `data/results.json` |
| Where do I set the 8 best third-place teams? | `data/bracket-config.json``thirdPlaceAssignment` |
| Where do I add/translate a UI label? | `assets/js/i18n.js` (both EN and PT dicts) |
| Where is the standings math? | `assets/js/groups.js` |
| Where are knockout teams resolved (placeholders, TBD)? | `assets/js/bracket.js``resolveBracketTeams()` |
| Where is simulation state stored/cleared? | `localStorage` key `wc2026_simulation`, via `assets/js/storage.js` |
| Where do I change colors/theme? | CSS variables at the top of `assets/css/style.css` |
| Where do I add a stadium? | `data/stadiums.json` + image in `assets/images/` |
| How do I replace mock data with real WC2026 data? | `how-update.md` (root) — done 2026-06-12; kept as schema reference |
| How do I update scores during the tournament? | `how-refresh-data.md` (root) — daily results.json routine + thirdPlaceAssignment how-to |
---
## Main functions
(Planned signatures from the complement spec — confirmed/updated as each step is implemented.)
| Function | File | Parameters | Returns | Description |
|---|---|---|---|---|
| `loadData` | `assets/js/app.js` | `()` | `Promise<AppData>` | Fetches all `data/*.json` in parallel, caches in memory |
| `formatMatchTime` | `assets/js/app.js` | `(match, stadium, mode)` | `string` | UTC → display time; `mode` is `"local"` or `"stadium"` |
| `get` / `set` | `assets/js/storage.js` | `(key, fallback)` / `(key, value)` | `any` / `void` | localStorage wrapper, auto JSON parse/stringify |
| `t` | `assets/js/i18n.js` | `(key)` | `string` | Translated UI string for current lang |
| `resolveBracketTeams` | `assets/js/bracket.js` | `(matchOrRef)` | `{ home, away }` of `{ team, label }` | Display slots for any match (group or knockout); reused by schedule/modal/filters |
| `getBracketTree` | `assets/js/bracket.js` | `()` | `{ rounds, third, nodesByRef, champion }` | Cached resolved bracket tree |
| `invalidateBracket` | `assets/js/bracket.js` | `()` | `void` | Drop the cached tree (simulation overlay, step 9) |
| `calculateChallengeScore` | `assets/js/bracket.js` | `(simulation, results, bracketTree)` | `{ correct, total, byPhase }` | Compares user picks vs finished results |
| `getShareableLink` | `assets/js/bracket.js` | `()` | `string` | Current URL + `?prediction=base64(simulation)` |
| `loadPredictionFromURL` | `assets/js/bracket.js` | `()` | `void` | Decodes, validates, confirms before overwriting local simulation |
| `toggleFavorite` | `assets/js/storage.js` | `(teamId)` | `void` | Add/remove team in `wc2026_favorites` |
| `computeStandings` | `assets/js/groups.js` | `()` | `{ [letter]: Row[] }` | Sorted standings per group, finished matches only |
| `isGroupFinished` | `assets/js/groups.js` | `(letter)` | `boolean` | All 6 group matches have status finished |
| `navigateTo` | `assets/js/app.js` | `(tab)` | `void` | Programmatic tab switch (cross-view links) |
| `setStadiumFilter` | `assets/js/schedule.js` | `(stadiumName)` | `void` | Reset filters + show one stadium's matches |
| `getFavoriteMatches` | `assets/js/bracket.js` | `(matches, favorites)` | `Match[]` | Matches involving favorited teams (resolved slots); used by schedule |
| `calculateChallengeScore` | `assets/js/bracket.js` | `(simulation, results, bracketTree)` | `{ correct, total, byPhase }` | Picks vs real finished knockout results |
| `getShareableLink` / `loadPredictionFromURL` | `assets/js/bracket.js` | `()` | `string` / `void` | base64 `?prediction=` export/import with validation + confirm |
| `exportMatchToICS` | `assets/js/calendar.js` | `(match, stadium)` | `void` | RFC 5545 VEVENT (UTC, 2h, CRLF, escaped TEXT), Blob download |