mirror of
https://github.com/LucasKalil-Programador/world-2026-hub.git
synced 2026-07-04 17:41:28 -03:00
docs(agents): add internal project memory
This commit is contained in:
parent
129d55ff26
commit
d1b31cf6e8
3 changed files with 444 additions and 0 deletions
79
.agents/TODO.md
Normal file
79
.agents/TODO.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# TODO — World Cup 2026 Hub
|
||||
|
||||
Checklist of what needs to be done. Organized by build step (approved plan, one approval gate per step).
|
||||
|
||||
Use checkboxes to track progress. Items marked **🔴 BLOCKER** prevent release; **🟡 IMPORTANT** must be done but don't block; **🟢 OPTIONAL** can be deferred.
|
||||
|
||||
---
|
||||
|
||||
## 1. Foundation
|
||||
|
||||
### 🔴 BLOCKER
|
||||
- [x] ~~Step 0 — Project memory: `.agents/` + auto-memory pointer + `git init`~~
|
||||
- [x] ~~Step 1 — File structure + mock data (48 teams, 12 groups, 104 matches UTC, ~30 stadiums, `bracket-config.json` with null third-place slots, mixed results)~~
|
||||
- [x] ~~Step 2 — Base layout: SPA shell, header + nav, hero (next match + countdown), dashboard cards, `style.css` palette, `app.js` loadData/routing, `storage.js`, `i18n.js` (EN/PT toggle)~~
|
||||
|
||||
---
|
||||
|
||||
## 2. Core views
|
||||
|
||||
### 🔴 BLOCKER
|
||||
- [x] ~~Step 3 — Match schedule: list, filters (date/group/phase/team/stadium), search, sort~~
|
||||
- [x] ~~Step 4 — Group standings computed from results (3/1/0, GD, GF)~~
|
||||
- [x] ~~Step 5 — Stadiums page (cards + matches held)~~
|
||||
- [x] ~~Step 6 — Match detail modal (result, penalties, future-stats placeholder, ARIA)~~
|
||||
|
||||
---
|
||||
|
||||
## 3. Bracket
|
||||
|
||||
### 🔴 BLOCKER
|
||||
- [x] ~~Step 7 — Static bracket: R32 from config, sequential pairing to FINAL, placeholders, `resolveBracketTeams()`~~
|
||||
- [x] ~~Step 8 — Interactions: hover path highlight, animations, wheel/pinch zoom, drag/pan~~
|
||||
- [x] ~~Step 9 — Simulation mode: pick winner + score, propagate rounds, `wc2026_simulation`, reset~~
|
||||
|
||||
---
|
||||
|
||||
## 4. Polish & docs
|
||||
|
||||
### 🔴 BLOCKER
|
||||
- [x] ~~Step 10 — Responsiveness (≤767 / 768–1439 / 1440+), accessibility (ARIA, keyboard, focus, contrast), entry animations~~
|
||||
|
||||
### 🟡 IMPORTANT
|
||||
- [x] ~~Step 11 — README (deploy guide, JSON maintenance, `bracket-config.json` how-to) + spec §18 acceptance checklist~~ (done after step 12, at user request)
|
||||
|
||||
---
|
||||
|
||||
## 5. Extra features (complement spec §6–10)
|
||||
|
||||
### 🟡 IMPORTANT
|
||||
- [x] ~~Step 12a — Favorites + "My Matches" filter (`toggleFavorite`, `getFavoriteMatches`, highlights)~~
|
||||
- [x] ~~Step 12b — Timezone toggle "Local / Stadium time" (`wc2026_prefs.timeMode`)~~
|
||||
- [x] ~~Step 12c — `.ics` export (`calendar.js`, CRLF, UTC, 2h duration)~~
|
||||
|
||||
### 🟢 OPTIONAL
|
||||
- [x] ~~Step 12d — Bracket challenge score card (`calculateChallengeScore`)~~
|
||||
- [x] ~~Step 12e — Share/import prediction via `?prediction=` base64 link~~
|
||||
|
||||
---
|
||||
|
||||
## 6. Post-launch (real data)
|
||||
|
||||
### 🟡 IMPORTANT
|
||||
- [ ] Replace mock `data/*.json` with real World Cup 2026 data
|
||||
- [ ] Fill `thirdPlaceAssignment` in `bracket-config.json` after group stage
|
||||
- [ ] Real stadium photos + team flag SVGs in `assets/images/`
|
||||
|
||||
---
|
||||
|
||||
## Quick final checklist
|
||||
|
||||
```
|
||||
[x] All 104 matches load from JSON
|
||||
[x] Standings + bracket fully derived from results.json
|
||||
[x] Simulation works and survives reload (localStorage)
|
||||
[x] GitHub Pages ready (all paths relative — verified; actual deploy pending)
|
||||
[x] Mobile: bracket scroll + zoom + drag
|
||||
[x] JS < 300KB (74 KB measured) [ ] Lighthouse > 90 (run after deploy)
|
||||
[x] EN/PT toggle covers every UI string
|
||||
```
|
||||
154
.agents/project-map.md
Normal file
154
.agents/project-map.md
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
# 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 (steps 1–10 + 12 done):** everything works — all views, bracket interactions, simulation, responsive/a11y pass, favorites, time toggle, challenge, share link, `.ics` export. Remaining: step 11 (full README + acceptance checklist). 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
|
||||
│ └── TODO.md 12-step build checklist
|
||||
│
|
||||
├── index.html ★ SPA shell — header, nav tabs (Home, Matches,
|
||||
│ Groups, Knockout, Stadiums), 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
|
||||
│ │ └── 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
|
||||
│ │ └── calendar.js .ics export (RFC 5545, CRLF, Blob download)
|
||||
│ └── images/ Team flag SVGs, stadium placeholders
|
||||
│
|
||||
├── data/ All content — the only thing edited for real data
|
||||
│ ├── teams.json 48 teams: { id, name, flag }
|
||||
│ ├── groups.json { "A": [4 team ids], ... } × 12 (A–L)
|
||||
│ ├── matches.json 104 matches; UTC times; knockout uses bracketRef
|
||||
│ ├── results.json { matchId, homeScore, awayScore, penalties?, status }
|
||||
│ ├── stadiums.json ~30 stadiums: { id, name, city, capacity, image, timezone }
|
||||
│ └── bracket-config.json ★ 16 R32 slot definitions + thirdPlaceAssignment —
|
||||
│ the ONLY file to edit once real 3rd places are known
|
||||
│
|
||||
├── README.md Setup, GitHub Pages deploy, JSON maintenance guide
|
||||
├── how-update.md Real-data migration runbook (mock → real WC2026 data/*.json)
|
||||
├── 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) — schemas, order of operations, integrity checklist |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
211
.agents/project-memory.md
Normal file
211
.agents/project-memory.md
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
# Project Memory — World Cup 2026 Hub
|
||||
|
||||
Persistent memory for this project. Read this before any significant change.
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Static web app showing the FIFA World Cup 2026 (Mexico/USA/Canada, 48 teams) — schedule, group standings, interactive knockout bracket with user simulation, stadiums. Hosted on GitHub Pages, all content from `data/*.json`. Started 2026-06-11 from two spec documents; built step-by-step with user approval between steps.
|
||||
|
||||
### What this project is
|
||||
- A **personal/portfolio** piece — visual polish (glassmorphism, animations) is a primary goal, not a nice-to-have.
|
||||
- A **static SPA**: one `index.html`, ES-module vanilla JS, JSON as the only "database".
|
||||
- Maintained by editing JSON only — code should never need touching to update scores/teams.
|
||||
|
||||
### What this project is **not**
|
||||
- No backend, no database, no build step, no bundler, no CDN dependencies, no frameworks.
|
||||
- No automated tests, no linter (explicit spec constraint).
|
||||
- Not real-data-complete: ships with mock data (fictional teams) to be replaced later.
|
||||
|
||||
---
|
||||
|
||||
## Priority objectives
|
||||
|
||||
1. **Spec compliance** — both spec files define scope; `complement-spec-worldcup2026-en.md` wins on conflict.
|
||||
2. **Visual quality** — FIFA/UCL/Apple-inspired, glassmorphism, smooth animations; portfolio-grade.
|
||||
3. **Interactive bracket** — hover path highlight, zoom, drag, simulation mode; the centerpiece feature.
|
||||
4. **Easy maintenance** — real data drop-in via JSON; `bracket-config.json` is the only file edited after group stage.
|
||||
5. **Performance/accessibility** — Lighthouse > 90, first render < 2s, JS < 300KB, ARIA + keyboard nav.
|
||||
|
||||
---
|
||||
|
||||
## Technical decisions and rationale
|
||||
|
||||
### Stack
|
||||
- **Vanilla HTML/CSS/JS ES2022+, ES Modules** — spec mandate; GitHub Pages serves static files only. Frameworks/bundlers explicitly forbidden.
|
||||
- **EN/PT-BR UI toggle via `i18n.js`** (user decision 2026-06-11) — not in spec; spec UI examples are EN, user wants both. Tiny dict + `t(key)`, persisted in `wc2026_prefs.lang`. Alternative (EN only) rejected by user.
|
||||
- **`storage.js` pulled forward to step 2** (spec places it in phase 12) — prefs (`lang`, `lastTab`) are needed from the base-layout step; building it late would mean refactoring.
|
||||
|
||||
### Data model
|
||||
- **All match times in UTC** in `matches.json`; converted at render via `Intl.DateTimeFormat` (`formatMatchTime`). `.ics` export depends on this.
|
||||
- **Knockout matches carry `bracketRef` instead of teams** — teams resolved at runtime from standings + `bracket-config.json`; rounds after R32 have no config, generated by sequential pairing of winners (indices 0-1 → 0, 2-3 → 1, …).
|
||||
- **Simulation never mutates JSON** — overlay stored in `localStorage` `wc2026_simulation`, keyed by bracket match ids (`R32-1`, `QF-2`, `FINAL`, …).
|
||||
|
||||
### Mock data design (2026-06-11, step 1)
|
||||
- **Real country names, fictional results** — generated deterministically (seed 2026), script deleted after run; data is now static JSON.
|
||||
- **State crafted to test both bracket modes:** matchdays 1-2 all `finished`; matchday 3 `finished` for groups A–F, `scheduled` for G–L (match 61, Group G, is `live`). So R32 slots fed by A–F resolve to real teams; G–L and all `third` slots show placeholders.
|
||||
- **Knockout results:** `R32-2` (match 74) finished 1-1 + penalties 4-3; `R32-4` (match 76) finished 2-0. Everything else `scheduled` with `null` scores — `results.json` has an entry for **all 104 matches**.
|
||||
- **Image paths:** `flag`/`image` JSON values are relative to `assets/images/` (e.g. `flags/mex.svg`, `stadiums/azteca.svg`).
|
||||
- **Opener** (match 1) is MEX at Estadio Azteca 2026-06-11; **final** (match 104) at MetLife 2026-07-19.
|
||||
|
||||
### Base layout decisions (2026-06-11, step 2)
|
||||
- **Hero priority: live > next scheduled** — a live match replaces the countdown with the score + pulse badge. Mock data keeps match 61 permanently `live`, so the countdown only shows if that status is changed (verified working by temporarily setting it to `scheduled`).
|
||||
- **i18n mechanics:** static HTML uses `data-i18n` / `data-i18n-aria` attributes re-applied by `applyI18n()`; dynamic renders call `t()` and listen for the `langchange` event on `document`. Phases translate via `translatePhase()` (PT: R32 = "16 avos de final").
|
||||
- **Tab routing:** hash (`#matches`) + `wc2026_prefs.lastTab`, `history.replaceState` to avoid history spam; precedence on load: hash → lastTab → home.
|
||||
- **Default language:** `navigator.language` startsWith `pt` → PT, else EN; only persisted when the user clicks the toggle.
|
||||
- **Preview server:** `.claude/launch.json` at `R:\lucas-kalil\Projects\` defines `worldcup2026` (python http.server, port 8126) for the Claude Preview panel.
|
||||
|
||||
### Schedule + performance decisions (2026-06-11, step 3)
|
||||
- **`schedule.js` ⇄ `app.js` circular import is intentional** — `app.js` calls `initSchedule()`, `schedule.js` imports `getData`/`formatMatchTime`/`flagSrc` back. Safe in native ESM because all calls happen after both modules evaluate; keep this pattern for `groups.js`/`bracket.js`/`modal.js`.
|
||||
- **Filter UX:** toolbar is rebuilt only on init/langchange/clear (state restored programmatically); list re-renders on every filter change. Filter state is in-memory only (not persisted) by design.
|
||||
- **Knockout cards show "TBD"** until step 7 swaps `teamColumnHTML`'s lookup to `resolveBracketTeams()`.
|
||||
- **Perf: no `backdrop-filter` on repeated cards** — `.match-card` overrides `.glass` blur (huge paint cost × 104 cards, invisible over the smooth gradient). Same rule applies to any future card grid (stadiums, bracket).
|
||||
- **Perf: fixed gradient lives on `body::before` (position: fixed)**, not `background-attachment: fixed` (the latter repaints the whole background on scroll).
|
||||
|
||||
### Standings decisions (2026-06-11, step 4)
|
||||
- **Only `status: "finished"` matches count toward standings** — live scores are ignored until full-time (keeps standings stable and bracket resolution deterministic).
|
||||
- **Tiebreak order:** points → goal difference → goals for → team id alphabetical (stable fallback). Verified against an independent Python computation for groups A and G.
|
||||
- **`computeStandings()` / `isGroupFinished()` exported from `groups.js`** — bracket.js (step 7) must import these instead of recomputing.
|
||||
|
||||
### Stadiums decisions (2026-06-11, step 5)
|
||||
- **`stadiums.js` module added** — spec §4 has no module for the stadiums view; a dedicated view module keeps the per-view pattern (schedule/groups/bracket) instead of growing `app.js`.
|
||||
- **Cross-link:** "View matches" on a stadium card calls `setStadiumFilter(name)` (exported by `schedule.js`, resets other filters) + `navigateTo('matches')` (exported by `app.js`).
|
||||
|
||||
### Modal decisions (2026-06-11, step 6)
|
||||
- **Native `<dialog>` + `showModal()`** — focus trap, Esc-to-close and `::backdrop` come free; no custom trap code. Backdrop click detected via `event.target === dialog` (content is in a padded inner div).
|
||||
- **Focus restore:** opener element saved in `openMatchModal()` and re-focused on `close`.
|
||||
- **Card → modal wiring is event delegation on `#schedule-root`** (click + Enter/Space keydown), so it survives list re-renders. Cards got `tabindex="0" role="button" aria-label` already in this step.
|
||||
- **`openMatchModal(matchId)` is the public API** — bracket (step 7) and any future view should call it rather than building their own.
|
||||
|
||||
### Bracket decisions (2026-06-11, step 7)
|
||||
- **Tree model is language-neutral**: slots are `{ teamId }` or `{ ph: { kind, … } }`; placeholder text is produced at render time by `slotDisplay()` so language switches never invalidate the tree.
|
||||
- **Tree is cached** in `bracket.js`; `invalidateBracket()` exists for the simulation overlay (step 9). Results are static per page load, so nothing else invalidates it.
|
||||
- **`resolveBracketTeams(matchOrRef)`** accepts a match object (group or knockout) or a bracketRef string and always returns `{ home, away }` as `{ team: Team|null, label: string }` — schedule cards, modal, and search/team filters all consume this, so knockout matches become searchable/filterable once resolved.
|
||||
- **Connector geometry**: all bracket columns share equal height with `flex: 1` slots, so pair children sit at 25%/75% and the next round's node at 50% — pure-CSS connectors (`::before`/`::after` stubs + pair vertical) meet exactly. Column gap 44px = 22px out-stub + 22px in-stub. Breaking the equal-height invariant breaks the lines.
|
||||
- **Final column** holds champion box (top), FINAL (middle, aligns with SF pair), third-place block (bottom); its champion/third slots suppress the incoming connector stub.
|
||||
|
||||
### Bracket interaction decisions (2026-06-11, step 8)
|
||||
- **"Full path" on hover/focus** = hovered node + its entire feeder subtree + its winner's route to the FINAL; THIRD-PLACE lights both SFs. Non-path nodes dim via `.has-path` on the canvas. Computed from ref arithmetic (`floor(i/2)` up, `2i/2i+1` down), no tree lookup.
|
||||
- **Zoom = CSS `transform: scale()` on the canvas + a `#bracket-zoom` box sized to `natural × scale`** (transform doesn't affect layout, so the box gives the scroll container the right scrollable area). Pointer-anchored: scroll adjusted so the point under the cursor stays put. Clamped 0.4–2.
|
||||
- **Natural canvas size is measured lazily** (`ensureMeasured()`) because the bracket panel can be `hidden` at render time (offsetWidth 0).
|
||||
- **Pan + pinch via Pointer Events** with `touch-action: none` on the wrap (we own all gestures there; page scroll over the bracket is intentionally captured, like a map widget).
|
||||
- **Drag–click conflict:** >5px movement sets a flag; a capture-phase click listener on the wrap swallows the click that ends a drag. The flag resets on the next `pointerdown`, so synthetic `el.click()` without pointerdown can be falsely suppressed in tests — dispatch pointerdown/up first.
|
||||
- **Zoom level survives langchange re-renders** (module-level `view.scale`) but intentionally not reloads (not in prefs).
|
||||
|
||||
### Simulation decisions (2026-06-11, step 9)
|
||||
- **Separation of concerns in the tree:** `decide()` applies only real finished results; `applySimulation()` overlays user picks afterwards and never overrides a real result. Stale entries (winner no longer among the resolved teams) are silently ignored — same validation the prediction import (step 12) will use.
|
||||
- **Sim UX:** "Simulation" toggle in the bracket toolbar; eligible nodes (both teams resolved, real result still `scheduled`) get dashed blue borders; clicking one opens a small native `<dialog>` picker. An unequal score auto-selects the winner; a draw requires an explicit pick (penalties implied). Empty score defaults to 1-0 for the picked winner.
|
||||
- **Storage format** is exactly the complement-spec shape: `wc2026_simulation = { "R32-6": { winner: "FRA", score: "2-1" } }` keyed by bracketRef, score oriented home-away.
|
||||
- **`simchange` custom event** fires after any pick/reset; `schedule.js` listens and re-renders so simulated teams appear on knockout cards too (intentional leak — resolved tree is the single source).
|
||||
- **Simulated nodes** show a blue "SIM" corner chip and blue scores; the modal shows none of this (it reads real results only).
|
||||
|
||||
### Responsive/a11y decisions (2026-06-11, step 10)
|
||||
- **Breakpoints:** ≤767 (tight spacing, bracket `--node-w: 168px`/gap 36px — connector stub offsets must stay at gap/2, overridden in the same media query), 768–1439 (single-row header, centered menu), 1440+ (container widens to 1360px).
|
||||
- **Tabs follow the WAI-ARIA pattern:** roving tabindex + ArrowLeft/Right/Home/End in `initTabs()`; focus follows activation.
|
||||
- **Dialogs get `aria-label`** set at open time (match name + phase); schedule count is `aria-live="polite"`; countdown has `role="timer"` + label.
|
||||
- **Entry animations:** every unhidden `.panel` fades in; card grids (`.match-grid/.groups-grid/.stadiums-grid > *`) slide up with a 45ms stagger on the first 6 children, 260ms for the rest. All killed by `prefers-reduced-motion`.
|
||||
|
||||
### Extra features decisions (2026-06-12, step 12 — done before step 11 at user request)
|
||||
- **Favorites:** single global capture-phase click delegation in `app.js` handles every `.fav-btn` (schedule, groups, modal) and dispatches `favchange`; each view re-renders itself. Stars never trigger the card/modal click (guard via `closest('.fav-btn')`). Bracket shows highlight only (no stars — nodes too small). Favorite involvement = gold left border.
|
||||
- **`getFavoriteMatches(matches, favorites)`** lives in `bracket.js` (needs `resolveBracketTeams`), imported by `schedule.js` for the "My matches" filter.
|
||||
- **Time mode:** header `#time-toggle` flips `wc2026_prefs.timeMode` and dispatches `timemodechange`; `formatMatchTime()` already defaulted to the pref, so views just re-render.
|
||||
- **Challenge:** sim entries for real-finished matches can't be created in the UI (locked) but old ones persist in storage — that's by design: predictions are made while matches are `scheduled`, then scored when results land. Card renders only when ≥1 finished knockout match exists.
|
||||
- **Share/import:** `?prediction=` is stripped from the URL via `history.replaceState` whether applied or not (prevents re-prompt loops). Declining `confirm()` keeps local picks; unknown refs are rejected wholesale.
|
||||
- **`.ics`:** RFC 5545 TEXT escaping (`\,` etc.) applied even though the spec template shows raw commas — RFC compliance wins; verified output imports with CRLF-only endings.
|
||||
- **Custom events now in play:** `langchange`, `simchange`, `favchange`, `timemodechange` — all on `document`; views own their re-renders.
|
||||
|
||||
### Build complete (2026-06-12, step 11 — all 12 steps done)
|
||||
- README is the user-facing manual (run, deploy, JSON maintenance, localStorage keys, acceptance checklist). Keep it in sync when data formats change.
|
||||
- **Verified at completion:** spec §18 criteria all pass; JS = 74 KB total (budget 300 KB); no root-absolute paths (GitHub Pages safe). **Not yet verified:** Lighthouse > 90 (needs a deployed URL or local Lighthouse run); actual GitHub Pages deploy.
|
||||
- No commits made yet — repo initialized but empty; commit when the user asks.
|
||||
|
||||
### Workflow
|
||||
- **12-step build plan with approval gates** — user approves each step before the next starts; summary after each step. Plan: `C:\Users\Lucas\.claude\plans\read-r-lucas-kalil-projects-web-worldcup-goofy-meerkat.md`.
|
||||
- **Git repo, commits only when the user asks.**
|
||||
|
||||
---
|
||||
|
||||
## Known gotchas
|
||||
|
||||
### 1. `fetch()` of JSON fails on `file://`
|
||||
**Where:** any local testing of `index.html`
|
||||
**Why:** browsers block `fetch` of local files (CORS/origin rules)
|
||||
**Symptom if forgotten:** blank app, console CORS errors, wasted debugging
|
||||
**Solution:** always serve via `python -m http.server` (or any static server) from the project root
|
||||
|
||||
### 2. `.ics` requires CRLF line endings
|
||||
**Where:** `assets/js/calendar.js`
|
||||
**Why:** RFC 5545 mandates `\r\n` between lines; some calendar apps reject `\n`
|
||||
**Symptom if forgotten:** exported event silently fails to import in Outlook/Apple Calendar
|
||||
**Solution:** join VCALENDAR lines with `\r\n` explicitly
|
||||
|
||||
### 3. Third-place slots are `null` until defined
|
||||
**Where:** `data/bracket-config.json` → `thirdPlaceAssignment`
|
||||
**Why:** the 8 best third-place teams are only known after the group stage
|
||||
**Symptom if forgotten:** crash or "undefined" team names in R32 rendering
|
||||
**Solution:** `resolveBracketTeams()` must return placeholder labels ("Best 3rd #1", "Group A Winner") whenever a slot is `null` or the group isn't finished
|
||||
|
||||
### 4. Claude Preview screenshots can hang (tooling, not app)
|
||||
**Where:** Claude Preview panel during verification
|
||||
**Why:** the preview window's screenshot pipeline occasionally gets stuck; `preview_eval` keeps working
|
||||
**Symptom if forgotten:** wasted debugging hunting a nonexistent app freeze
|
||||
**Solution:** `preview_stop` + `preview_start` recovers it; verify state via `preview_eval` first before suspecting the app
|
||||
|
||||
### 5. Stale JS modules in the dev browser
|
||||
**Where:** any JS edit while previewing via `python -m http.server`
|
||||
**Why:** the server sends no cache headers, so browsers heuristically cache ES modules; a normal reload can keep serving old code
|
||||
**Symptom if forgotten:** "module does not provide an export" errors or old behavior despite correct code on disk
|
||||
**Solution:** `Promise.all(files.map(f => fetch(f, { cache: 'reload' })))` then `location.reload()`, or DevTools hard reload
|
||||
|
||||
### 6. `setPointerCapture` on pointerdown kills element clicks
|
||||
**Where:** `assets/js/bracket.js` drag/pan handling on `#bracket-wrap`
|
||||
**Why:** capturing a pointer retargets the eventual `click` event to the capture element, so delegation via `event.target.closest('.bracket-match')` never matches — modal and simulation clicks silently die
|
||||
**Symptom if forgotten:** bracket nodes unclickable with real input while synthetic `el.click()` tests still pass
|
||||
**Solution:** capture only after the drag threshold (>5px) is exceeded, inside `pointermove`, wrapped in try/catch. **Always verify click flows with `preview_click` (trusted input), not `element.click()`.**
|
||||
|
||||
### 7. GitHub Pages serves under a subpath
|
||||
**Where:** all asset/data URLs in `index.html` and JS `fetch` calls
|
||||
**Why:** project pages live at `https://<user>.github.io/<repo>/`, so root-absolute paths (`/data/...`) break
|
||||
**Symptom if forgotten:** works locally, 404s on GitHub Pages
|
||||
**Solution:** use relative paths (`data/matches.json`, `assets/...`) everywhere
|
||||
|
||||
---
|
||||
|
||||
## Patterns for future changes
|
||||
|
||||
### How to update real-world data (scores, schedule)
|
||||
1. Edit `data/results.json` (scores/status) or `data/matches.json` (schedule).
|
||||
2. Once group stage ends: fill `data/bracket-config.json` → `thirdPlaceAssignment` (slot → group letter). Nothing else changes.
|
||||
|
||||
### Real-data migration (2026-06-12)
|
||||
- `how-update.md` (project root) is the full runbook for replacing mock `data/*.json` with real World Cup 2026 data: file-by-file schemas, order of operations (stadiums → teams → groups → bracket-config.round32 → matches → results → thirdPlaceAssignment), and a cross-file integrity checklist (group membership, id ranges, bracketRef uniqueness, stadium name/city matches).
|
||||
- Flags one open decision: `stadiums.json` has 30 entries (original bid shortlist) vs. the 16 venues actually used by the real tournament — confirm with user whether to trim before/while editing `matches.json`.
|
||||
|
||||
### How to add a UI label
|
||||
1. Add the key to both `en` and `pt` dicts in `assets/js/i18n.js`.
|
||||
2. Use `t("key")` at the render site — never hardcode the string.
|
||||
|
||||
### How to add a new localStorage preference
|
||||
1. Extend the `wc2026_prefs` object shape (document the new field here).
|
||||
2. Read/write only via `storage.js` `get`/`set`.
|
||||
|
||||
### How to add a step summary after finishing a build step
|
||||
1. Mark the step `[x] ~~...~~` in `.agents/TODO.md`.
|
||||
2. Append any new decisions/gotchas here (never rewrite existing entries).
|
||||
3. Rewrite `project-map.md` if structure/functions changed.
|
||||
4. Stop and wait for user approval before the next step.
|
||||
|
||||
---
|
||||
|
||||
## Success metrics
|
||||
|
||||
- Lighthouse > 90; first render < 2s; total JS < 300KB.
|
||||
- Spec §18 acceptance criteria all checked (tracked in README checklist, step 11).
|
||||
|
||||
---
|
||||
|
||||
## Communication
|
||||
|
||||
- User communicates in English/Portuguese mix; docs in English per conventions.
|
||||
- **Ask before each build step** — never chain into the next step without explicit go-ahead.
|
||||
Loading…
Add table
Add a link
Reference in a new issue