diff --git a/.agents/project-map.md b/.agents/project-map.md index c4607db..af0df36 100644 --- a/.agents/project-map.md +++ b/.agents/project-map.md @@ -116,7 +116,16 @@ worldcup2026/ │ awards.json, keeper-stats.json, curiosities.json, │ all-time-baselines.json — absent = silent empty default │ -├── README.md Setup, GitHub Pages deploy, JSON maintenance guide +├── README.md ★ Non-technical SHOWCASE (2026-07-04): tagline, badges, +│ live-demo link (lucaskalil.com/worldcup2026), per-page +│ screenshot gallery, plain-language "Under the hood". +│ Dev content lives in DEVELOPMENT.md now. +├── DEVELOPMENT.md Developer guide (2026-07-04): run locally, project +│ structure, JSON maintenance, local storage, deploy, +│ acceptance criteria, roadmap — split out of the old README +├── docs/screenshots/ 6 PNGs (home/matches/groups/bracket/stadiums/stats) for the +│ README gallery — captured via headless Edge, EN UI, 1366px. +│ Excluded from the FTP deploy (docs-only) ├── 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 diff --git a/.agents/project-memory.md b/.agents/project-memory.md index d54b177..0b9b81a 100644 --- a/.agents/project-memory.md +++ b/.agents/project-memory.md @@ -344,10 +344,10 @@ a separate commit keeps the data commit's diff clean. - **Gotcha:** the Hostinger FTP account logs in **already inside `public_html`**, so `server-dir` is relative to it — do **not** prefix `public_html/` (causes `public_html/public_html/...`). Final path: `public_html/worldcup2026/`. If FTPS is rejected, switch `protocol` to `ftp`. -- `exclude` removes `.git*`, `.github/`, `.agents/`, `README.md`, `how-*.md`, `*-en.md` specs — only - `index.html` + `assets/` + `data/` reach the site. New `data/` / `manifest.json` / `assets/icons/` - files **are** deployed. Incremental sync state (`.ftp-deploy-sync-state.json`) lives only on the - server — don't commit it. +- `exclude` removes `.git*`, `.github/`, `.agents/`, **`docs/`**, `README.md`, **`DEVELOPMENT.md`**, + `how-*.md`, `*-en.md` specs — only `index.html` + `assets/` + `data/` reach the site. New `data/` / + `manifest.json` / `assets/icons/` files **are** deployed. Incremental sync state + (`.ftp-deploy-sync-state.json`) lives only on the server — don't commit it. ### Real-data migration (DONE 2026-06-12) All 6 `data/*.json` hold real WC2026 data (sources: Wikipedia per-group + knockout articles, @@ -550,6 +550,21 @@ Edge fades via `mask-image` toggled by `updateTabFades()`; active tab kept visib page). The time button collapses to a 🕐 icon at ≤420px (a11y intact via `data-i18n-aria`). This supersedes the old "768–1439 single-row header" note. +### Docs — README showcase + DEVELOPMENT.md split (2026-07-04) +The root README was reframed (via /grill-me) from a dev/maintenance guide into a **non-technical +showcase** (English): tagline, shields.io badges, prominent live-demo link +(**https://lucaskalil.com/worldcup2026** — the public URL; not previously recorded anywhere), +per-page **screenshot gallery** (Home/Matches/Groups/Knockout/Stadiums/Stats), and a plain-language +"Under the hood" section. All the old technical content (run locally, project structure, JSON +maintenance, local storage, deploy, acceptance criteria, roadmap) moved to a new **`DEVELOPMENT.md`**; +the README's stale "mock data / GitHub Pages" framing was corrected to real-data + the real Hostinger +deploy. Screenshots live in **`docs/screenshots/*.png`**, captured with **headless Edge** +(`msedge --headless=new --window-size=1366,H --virtual-time-budget=6000 --lang=en-US --screenshot`, +`--lang=en-US` forces the EN UI; served from Claude Preview `worldcup2026` on :8126) — repeat that to +refresh them. **Deploy exclude updated** to drop `docs/` + `DEVELOPMENT.md` (docs never ship to the +live site — see Deploy runbook). No app-code/version change (`APP_VERSION` untouched; docs are +excluded from deploy). + ### How to record a decision (after finishing a unit of work) 1. Tick the item in `.agents/TODO.md`. 2. Append the new decision/gotcha/pattern to the right section here (don't rewrite existing entries; diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..690b1d9 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,164 @@ +# 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](README.md). + +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 + +```sh +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/*.json` files — no code changes required. +> +> The day-to-day refresh routine is documented in +> [`how-refresh-data.md`](how-refresh-data.md); the original mock → real migration is kept as a schema +> reference in [`how-update.md`](how-update.md). + +### Updating a result + +Edit the match's entry in `data/results.json`: + +```json +{ "matchId": 74, "homeScore": 1, "awayScore": 1, + "penalties": { "home": 4, "away": 3 }, "status": "finished" } +``` + +- `status`: `scheduled` → `live` → `finished`. Standings and the bracket only count `finished` matches. +- `penalties` is 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): + +```json +"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` — `flag` is a path relative to `assets/images/` (e.g. `flags/bra.svg`). +- `stadiums.json` — `timezone` must 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`](.github/workflows/deploy.yml)) on every push to `master`, and is +served at **[lucaskalil.com/worldcup2026](https://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 and + `how-*.md`) are **excluded** from the upload — only `index.html`, `assets/` and `data/` 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) + +- [x] All matches are loaded via JSON +- [x] All results are loaded via JSON +- [x] The bracket is generated dynamically (config + standings + winner pairing) +- [x] Works on a static host (all paths relative, no server-side code) +- [x] Works on desktop and mobile (≤767 / 768–1100 / 1100+ breakpoints) +- [x] Allows knockout-stage simulation (persisted, never mutates JSON) +- [x] Smooth animations (entry, hover, bracket line-draw; reduced-motion safe) +- [x] 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`](.agents/issues.md). + +--- + +## Internal documentation + +Deeper architecture notes, decisions and gotchas live in the [`.agents/`](.agents/) folder +(`project-map.md`, `project-memory.md`, `stats-screen-plan.md`, `issues.md`, `TODO.md`). Read those +before making significant changes. diff --git a/README.md b/README.md index d9e9d52..ea8e597 100644 --- a/README.md +++ b/README.md @@ -1,155 +1,98 @@ -# World Cup 2026 Hub +
+
+
-## Maintaining the data
+### 📅 Matches
+*All 104 games in one place — search by team, city or stadium, filter by date, group, phase, team or venue, and flag your favourites with “My matches”.*
-> The current files contain **mock data** (real country names, fictional results)
-> so every feature can be exercised. Replace them with real data as the
-> tournament unfolds — no code changes needed.
+
-### Updating a result
+### 📊 Groups
+*Live standings for all 12 groups, worked out automatically from the results — points, goal difference, goals scored — with clear markers for who qualifies (and who’s chasing a best-third-place spot).*
-Edit the match's entry in `data/results.json`:
+
-```json
-{ "matchId": 74, "homeScore": 1, "awayScore": 1,
- "penalties": { "home": 4, "away": 3 }, "status": "finished" }
-```
+### 🏆 Knockout bracket
+*The centrepiece. A gorgeous interactive bracket that fills itself in as teams advance. Hover a team to trace its whole path to the final, zoom and pan around, switch between three layouts — **and predict it yourself**: pick winners, watch the rounds re-draw, then see how your picks score against the real results. You can even share your bracket as a link.*
-- `status`: `scheduled` → `live` → `finished`. Standings and the bracket only
- count `finished` matches.
-- `penalties` is optional — only for knockout draws.
+
-### Adding / changing matches
+### 🏟️ Stadiums
+*All 16 host venues across the three countries, each with its capacity and a jump straight to the matches played there.*
-`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.
+
-### After the group stage: fill the third-place slots
+### 📈 Stats
+*A tournament stats screen: goals by stage and by round, records, a full 1–48 ranking and a head-to-head team comparator. Once the final is played, a champion “verdict” takes over the top of the page.*
-`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:
+
-```json
-"thirdPlaceAssignment": { "1": "C", "2": "A", "3": null, … }
-```
+---
-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.
+## ✨ Under the hood (for the curious)
-### Teams, stadiums, images
+You don't need to be a developer to appreciate a few things that make this special:
-- `teams.json` — `flag` is a path relative to `assets/images/` (e.g. `flags/bra.svg`).
-- `stadiums.json` — `timezone` must be a valid IANA name (e.g. `America/Mexico_City`);
- it drives the "stadium time" display and stays correct across DST.
-- Replace the placeholder SVGs in `assets/images/` with real artwork keeping the
- same file names (or update the JSON paths).
+- **⚡ Loads in a blink.** The whole site is about **74 KB** of JavaScript — smaller than a single
+ phone photo — with **no frameworks and no libraries**. It's just clean, hand-written code.
+- **🔮 Predict the bracket.** Choose winners and the bracket redraws all the way to the final; your
+ picks are saved in your browser and can be handed to a friend as a link to compare.
+- **🔄 Always current.** New scores appear on their own — no need to refresh the page.
+- **📱 Works everywhere.** Phone, tablet or desktop, the layout adapts to fit.
+- **⬇️ Installable.** Add it to your home screen and it opens like a real app (it's a PWA).
+- **🌍 Two languages.** Every label is available in English and Portuguese, switchable on the fly.
+- **♿ Built to be usable by everyone.** Full keyboard navigation, screen-reader labels, and it
+ respects the system “reduce motion” setting.
-### UI labels (EN/PT)
+Curious how it's put together, or want to run it yourself? See **[DEVELOPMENT.md](DEVELOPMENT.md)**.
-Every UI string goes through `t(key)` — add new labels to **both** dictionaries
-in `assets/js/i18n.js`. Data values (team/stadium names) are not translated.
+---
-## Local storage
+