mirror of
https://github.com/LucasKalil-Programador/world-2026-hub.git
synced 2026-07-04 17:41:28 -03:00
feat(stats): add tournament-to-date stats tab
This commit is contained in:
parent
ba81e49eac
commit
d5a9dadc5d
9 changed files with 1263 additions and 4 deletions
|
|
@ -13,6 +13,8 @@ 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/
|
||||
|
|
@ -21,7 +23,7 @@ worldcup2026/
|
|||
│ .gitignore OS/editor junk
|
||||
│
|
||||
├── index.html ★ SPA shell — header, nav tabs (Home, Matches,
|
||||
│ Groups, Knockout, Stadiums), hero, dashboard,
|
||||
│ Groups, Knockout, Stadiums, Stats), hero, dashboard,
|
||||
│ modal container; loads app.js as ES module
|
||||
│
|
||||
├── assets/
|
||||
|
|
@ -29,6 +31,7 @@ worldcup2026/
|
|||
│ │ ├── 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/
|
||||
|
|
@ -43,6 +46,9 @@ worldcup2026/
|
|||
│ │ ├── 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
|
||||
│
|
||||
|
|
|
|||
|
|
@ -272,6 +272,27 @@ Follow `how-refresh-data.md` (project root). In short:
|
|||
- **`assets/js/app.js` `loadData()` appends `?v=${DATA_VERSION}`** to every `data/*.json` fetch (`DATA_VERSION` constant near the top of the file, currently `'2026-06-13-rev1'`). Fixes production browsers/Hostinger caching stale `results.json` after a daily refresh — `cache: 'reload'` only helps the developer's own browser, not real visitors.
|
||||
- **Must be bumped on every data refresh** — added as step 4 of the daily routine in `how-refresh-data.md`. Format `YYYY-MM-DD-revN`; increment `revN` for same-day re-edits.
|
||||
|
||||
### Stats screen — planning (2026-06-14)
|
||||
- **Plano completo em `.agents/stats-screen-plan.md`** (pós-Copa "tela de estatísticas finais"). Gerado via workflow de 5 sub-agentes (Times/Individuais/Partidas/Curiosidades/UX). **Nada implementado ainda** — só planejamento, aprovado pelo usuário.
|
||||
- **Escopo aprovado: as 4 camadas** — ✅ dados existentes · 🟡🧩 acréscimos baratos (attendance, cartões y/r, ranking/wcDebut/confederation em teams.json, coords em stadiums.json, backfill do `stats`) · 🔴 dados de jogadores (`players.json` + log de eventos + `awards.json`) · 📝 editorial (`curiosities.json`).
|
||||
- **REQUISITO DE 1ª ORDEM — degradação graciosa:** quando um dado não existir, a UI **não pode quebrar nem deixar visível ao usuário final que falta algo**. Regra: dado/seção só renderiza com cobertura completa; senão é **removido do DOM** (não placeholder/"—"/"em breve"); chips de sub-nav de seções vazias também somem; `loadData()` deve tolerar fetch de arquivo novo ausente (default vazio, não exceção).
|
||||
- **Decisões técnicas planejadas:** novo 6º tab `stats` → `#panel-stats` + `assets/js/stats.js` + `assets/css/stats.css` (segue padrão por-view + import circular com app.js); sub-nav = `<nav>` de âncoras com scrollspy (NÃO um 2º tablist); **sem chart-lib** (SVG/CSS inline, respeita budget <300KB); render preguiçoso por seção; modelo de stats memoizado, re-render só de labels no `langchange`. Roadmap A–J com portões de aprovação (A–F entregam a tela só com dados de hoje; G/H/I acendem camadas 2/3/4).
|
||||
|
||||
### Stats tab — PARCIAL durante a copa (2026-06-14, Etapa A+B)
|
||||
- **Nova 6ª aba `stats`** implementada de forma incremental (plano em `C:\Users\Lucas\.claude\plans\contexto-o-planejamento-foamy-brook.md`). É a **fundação evolutiva** do plano pós-copa (`.agents/stats-screen-plan.md`) — mesmo tab/módulo; seções pós-copa "acendem" depois.
|
||||
- **Arquivos novos:** `assets/js/stats.js` + `assets/css/stats.css`. Integração: `stats.css` linkado no `<head>`; botão+painel `panel-stats`/`#stats-root` no `index.html`; `'stats'` adicionado a `TABS` em `app.js`; `initStats()` chamado no `try` de `init()`; chaves `nav.stats` + `stats.*` (EN/PT) em `i18n.js`.
|
||||
- **Filosofia (decidida via /grill-me):** agregados correntes "até agora", **só `status==='finished'`** (consistente com `computeStandings`); "X de 104" é moldura, não lacuna. `stats` opcional (posse/chutes/cartões) entra com **gating por-jogo** (`aggregateTeams` ignora jogo sem o campo). Sem polling — recomputa no load + re-render de labels no `langchange` (modelo memoizado em `let model`).
|
||||
- **Implementado nesta etapa (A+B):** `aggregateTeams()` (agregação torneio-wide, grupo+mata-mata, própria — `computeStandings` é só por-grupo); hero "pulso" com tiles count-up (IntersectionObserver dispara a animação quando o painel abre, reduced-motion-safe); Overview (jogos/decididas/empates) + chart "gols por fase" (só fases com ≥1 jogo — sem barras zeradas de R32+); link "ver partidas" → `navigateTo('matches')`. Verificado: 27 gols/3.00 média/margem 6/3 clean sheets, EN↔PT, mobile 2×2, console limpo.
|
||||
- **Seção "Estatísticas por time" (2026-06-14, +/grill-me):** tabela paginada das **48 seleções, 6 páginas fixas de 8** (`PAGE_SIZE`/`COLUMNS` no topo de `stats.js`). 10 colunas ordenáveis (J/V/E/D/GP/GC/SG/Pts/G·J/CS; rótulos curtos reusam `standings.*`, os 2 novos têm `title`). Clique no header faz **toggle desc↔asc** (seta + `aria-sort`), default **GP desc** ao abrir; desempate fixo SG→GP→nome; trocar coluna ou re-ordenar volta à pág. 1. Coluna # = **rank global** 1–48. Times sem jogo = zeros reais (não lacuna), caem ao fim. Faixa **top-3 leaders** (melhor ataque=GP / defesa=GC entre quem jogou / mais clean sheets) acima — `computeLeaders` só considera `played>0`. Estado `sortKey`/`sortDir`/`teamPage` é módulo-level e **sobrevive ao langchange** (como o zoom do bracket); ordenação/paginação re-renderizam só `#stats-teams-table` (não re-disparam count-ups). Mobile: `.stats-table-wrap` scroll-x com `#`+`Seleção` `position:sticky` (left 0 / 2.5rem). Verificado: sort/toggle/troca-de-coluna/paginação/EN↔PT/mobile-sticky, console limpo.
|
||||
- **Falta (etapas D–G, aguardando aprovação):** líderes posse/chutes/disciplina; recordes auto (maior goleada→modal); comparador time-vs-time; polimento. **Sem** arquivo de resultados (linka p/ Matches). `penalties` ainda não existe em results.json → card de pênaltis só renderiza quando ≥1 disputa ocorrer.
|
||||
|
||||
### Tooltips de header + legenda (2026-06-14, +/grill-me)
|
||||
- **Tooltips nas siglas das tabelas** (Stats "Estatísticas por time" **e** as 12 tabelas de Grupos). Decidido via /grill-me: tooltip custom glass (não `title` nativo), **nome + definição onde ajuda**, e **legenda compacta só no mobile**.
|
||||
- **`initTooltips()` em `app.js`** (chamado no `init()`): um único balão `.app-tooltip` `position:fixed`, via **delegação de eventos** em `document` (`mouseover`/`focusin` mostram, `mouseout`/`focusout`/`scroll` escondem). Sobrevive a re-renders das tabelas e **nunca é cortado** por overflow/stacking (motivo de não usar `::after` dentro do `.stats-table-wrap` que tem `overflow-x:auto`). Centraliza sobre o elemento, faz clamp na viewport e **flip pra baixo** se não couber acima.
|
||||
- **Como dar tooltip a um header:** adicionar classe `has-tip` + `data-tip="<texto>"` + `aria-label="<sigla> — <texto>"` (o aria-label cobre leitor de tela, já que o balão é visual). Textos em `i18n.js` no namespace **`tip.*`** (EN/PT), reusados pelas duas tabelas (`tip.played/won/drawn/lost/gf/ga/gd/pts/gpg/cs`).
|
||||
- **Legenda mobile:** `<p class="stats-legend">` com pares `<b>sigla</b> = texto` — `display:none` no desktop, `flex` em `≤600px` (estilo em `stats.css`). Uma por tabela: `legendHTML()` em `stats.js` (usa `COLUMNS`) e em `groups.js` (lista fixa das 8 colunas). Cobre o touch, onde o hover não dispara.
|
||||
- CSS do tooltip/legenda vive em `stats.css` (carregado global, então `.has-tip`/`.app-tooltip`/`.stats-legend` valem também na aba Grupos).
|
||||
|
||||
### 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.
|
||||
|
|
|
|||
264
.agents/stats-screen-plan.md
Normal file
264
.agents/stats-screen-plan.md
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# Plano de Implementação — Tela de Estatísticas Finais (World Cup 2026 Hub)
|
||||
|
||||
> Documento de planejamento (NÃO é implementação). Gerado em 2026-06-14 após workflow multi-agente
|
||||
> (5 perspectivas) + aprovação da lista de dados pelo usuário. Escopo aprovado: **as 4 camadas**
|
||||
> (✅ existentes · 🟡🧩 acréscimos baratos · 🔴 dados de jogadores · 📝 editorial).
|
||||
>
|
||||
> **PRINCÍPIO REITOR (requisito explícito do usuário):** *degradação graciosa*. Quando um dado não
|
||||
> puder ser obtido, o tratamento deve ser elegante — **não pode quebrar a UI nem deixar visível para o
|
||||
> usuário final que algo está faltando**. Sem `—`, sem cards vazios, sem "dados em breve". A regra é:
|
||||
> **um dado/seção só aparece quando está completo o bastante para ser apresentado como autoritativo;
|
||||
> caso contrário é removido do DOM** (não escondido com placeholder, *removido*). Ver §0 e §6.
|
||||
|
||||
---
|
||||
|
||||
## 0 · Camada de degradação graciosa (a espinha dorsal de todo o resto)
|
||||
|
||||
Esta é a peça arquitetural mais importante do plano. Tudo abaixo se apoia nela.
|
||||
|
||||
### 0.1 Contrato de disponibilidade
|
||||
Cada estatística, card, linha de tabela e sub-seção declara um predicado `isAvailable(model)`:
|
||||
- **Sub-seção inteira sem dados** → a seção **não é renderizada** e seu chip no sub-nav **é removido**
|
||||
(navegação nunca aponta para o vazio).
|
||||
- **Card/recorde individual sem dados** → o card não é inserido; o grid reflui naturalmente.
|
||||
- **Célula opcional numa linha** (ex.: xG de um time sem feed) → a coluna some para todos OU a célula
|
||||
cai para um valor neutro só se isso não denunciar ausência. Preferência: **a coluna inteira só existe
|
||||
se todos os times tiverem o dado.**
|
||||
- **Agregado sobre `stats` esparso** (posse/chutes/cartões) → só renderiza com **cobertura completa**
|
||||
(todos os jogos da amostra relevante preenchidos). Sem cobertura → **escondido, sem disclaimer**.
|
||||
(Decisão: o usuário NÃO quer "baseado em N jogos" visível. Ou faz backfill total, ou não mostra.)
|
||||
|
||||
### 0.2 Carga de dados tolerante a falha
|
||||
`loadData()` em `app.js` passa a buscar os novos JSON; cada fetch novo é **opcional**:
|
||||
arquivo ausente / 404 / JSON inválido → `console.warn` (dev) + **default vazio** (`[]`/`{}`),
|
||||
**nunca** exceção que derrube o app. Os 6 arquivos atuais continuam obrigatórios.
|
||||
|
||||
### 0.3 Mídia com fallback
|
||||
Fotos de jogadores / bandeiras quebradas → `onerror` cai para iniciais (monograma) ou silhueta
|
||||
genérica. Nunca ícone de imagem quebrada.
|
||||
|
||||
### 0.4 Camadas se auto-desligam
|
||||
- Sem `players.json`/eventos → **toda** a seção Jogadores + Prêmios + comparador de jogadores +
|
||||
recordes de tempo de gol somem (chips inclusive). A tela continua completa só com dados de time.
|
||||
- Sem `curiosities.json` → Recordes mostra só os auto-deriváveis; nada de "em breve".
|
||||
- Sem `attendance` → recordes de público somem; o resto do Overview fica intacto.
|
||||
|
||||
Resultado: a mesma base de código entrega uma tela coerente e "cheia" com **só os dados existentes
|
||||
hoje**, e vai "acendendo" seções conforme os dados das camadas 2/3/4 forem entrando — sem nenhuma
|
||||
data de corte nem buraco visível.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Arquitetura da tela
|
||||
|
||||
### 1.1 Estrutura de componentes (segue o padrão por-view existente)
|
||||
```
|
||||
index.html
|
||||
├─ nav.tabs + <button data-tab="stats" ...> (6º tab, após Stadiums)
|
||||
└─ <section id="panel-stats" class="panel" role="tabpanel" hidden>
|
||||
<h2 class="section-title" data-i18n="nav.stats">…</h2>
|
||||
<div id="stats-root"><p class="placeholder glass" data-i18n="app.comingSoon"></p></div>
|
||||
|
||||
assets/js/stats.js (NOVO módulo — espelha schedule.js/groups.js/bracket.js)
|
||||
- initStats() chamado por app.js (mesmo padrão de import circular intencional)
|
||||
- buildStatsModel(data) deriva TODO o modelo computável 1x, memoizado
|
||||
- renderStats() / renderSection(id) render preguiçoso por seção (IntersectionObserver)
|
||||
- escuta langchange (re-render só de labels) / favchange / timemodechange
|
||||
|
||||
assets/css/stats.css (NOVO — como bracket.css; componentes da tela de stats)
|
||||
```
|
||||
|
||||
### 1.2 Rotas
|
||||
- Hash route `#stats` + persistência `wc2026_prefs.lastTab` → **de graça** (roteamento por hash já existe).
|
||||
- Sub-navegação interna **não** é rota nem segundo `tablist`: é uma `<nav>` de âncoras (`#stats-teams`…)
|
||||
com *scrollspy* via IntersectionObserver (evita conflito de setas com o `tablist` do topo — ver §3).
|
||||
- Opcional (nice-to-have): deep-link `#stats=players` que faz scroll até a seção ao carregar.
|
||||
|
||||
### 1.3 Estado global
|
||||
- **Nenhum estado persistente novo é obrigatório.** O modelo de stats é derivado de `getData()` e
|
||||
**memoizado em memória** no módulo (`let statsModel`); recalcula só se os dados mudarem (não mudam
|
||||
em runtime). `langchange` re-renderiza labels, **não** recomputa valores (nomes não se traduzem).
|
||||
- Reusa estado existente: `wc2026_favorites` (destaque dourado em linhas/cards do time favorito),
|
||||
`wc2026_prefs.timeMode` (arquivo de resultados + modal).
|
||||
- Opcional: `wc2026_prefs.statsSort` p/ lembrar a última ordenação de tabela (baixa prioridade).
|
||||
|
||||
### 1.4 Integração com o existente (reuso, não reinvenção)
|
||||
- `openMatchModal(matchId)` → cards de recorde, linhas do arquivo e qualquer linha ligada a um jogo.
|
||||
- `resolveBracketTeams`, `computeStandings`, `getBracketTree`, `calculateChallengeScore` → reusados
|
||||
para ranking, caminho do campeão, fase alcançada.
|
||||
- `t(key)`, eventos `langchange`/`favchange`/`timemodechange`, `.glass`, `.slide-up`, `.container`.
|
||||
- `DATA_VERSION` (cache-busting) **deve ser bumpado** quando os novos `data/*.json` entrarem no
|
||||
`Promise.all` de `loadData()`.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Fontes de dados
|
||||
|
||||
### 2.1 Já existe (Camada 1 — ~70% da tela, zero coleta)
|
||||
`teams.json`, `groups.json`, `matches.json`, `results.json` (placar/status/penalties + `stats`
|
||||
opcional), `stadiums.json` (capacidade, timezone), `bracket-config.json`. Computados: standings,
|
||||
bracket, challenge.
|
||||
|
||||
### 2.2 Precisa coletar/calcular
|
||||
|
||||
| Camada | Arquivo / campo novo | Schema | Destrava | Esforço |
|
||||
|---|---|---|---|---|
|
||||
| 🟡 2 | `results.json` → `attendance` | inteiro por jogo | público total/médio/ocupação, maior público | dado público FIFA |
|
||||
| 🟡 2 | `results.json` → `cards: {home:{y,r}, away:{y,r}}` | split amarelo/vermelho | disciplina completa, fair-play | reformatar campo atual |
|
||||
| 🟡 2 | `results.json` → `decidedIn` | `"regulation"\|"ET"\|"penalties"` | separar prorrogação de pênaltis | trivial |
|
||||
| 🟡 2 | `results.json` → backfill `stats` nos 104 | posse/chutes existentes | posse/chutes/conversão como agregado confiável | médio (104 jogos) |
|
||||
| 🟡 2 | `results.json` → `shotsOnTarget`, `passes`, `passAccuracy` (opc.) | nível time | chutes no alvo, passes | opcional |
|
||||
| 🧩 2 | `teams.json` → `ranking`/`seed`, `wcDebut`, `confederation` | por time | zebras, Cinderela, estreantes, desempenho por confederação | trivial 1x |
|
||||
| 🧩 2 | `stadiums.json` → `lat`,`lng` | por estádio | distância total percorrida | trivial 1x |
|
||||
| 🔴 3 | `players.json` | roster mínimo `{id,name,team,position,birthDate,shirt?}` — só envolvidos | base de todos os individuais | médio |
|
||||
| 🔴 3 | `player-events.json` | log append-only `{type:goal\|card\|ownGoal, player, team, matchId, minute, goalType?, assist?, card?}` | artilharia, hat-tricks, tempo de gol, disciplina | **alto, ~400+ linhas** |
|
||||
| 🔴 3 | `awards.json` | `{goldenBall, goldenBoot, goldenGlove, bestYoung, squadOfTournament[], goalOfTournament}` | prêmios FIFA + Seleção do Torneio | trivial (1x pós-final) |
|
||||
| 🔴 3 | (opc.) `keeper-stats.json` | defesas/clean-sheets por goleiro | Luva de Ouro detalhada | opcional |
|
||||
| 📝 4 | `curiosities.json` | array de cards `{id,type,priority,titleEN/PT,bodyEN/PT,matchId?,teamId?,mediaRef?,statRef?}` | gol mais bonito, VAR, histórias | redação bilíngue |
|
||||
| 📝 4 | `all-time-baselines.json` (ou embutido) | recordes históricos fixos p/ comparação | painel "esta Copa vs história" | trivial 1x |
|
||||
|
||||
> **Decisão de modelagem (jogadores):** usar **roster + log de eventos** (não agregados pré-somados).
|
||||
> O log deriva tudo (artilharia, assistências, hat-tricks, gol mais rápido/jovem/tardio) com o mesmo
|
||||
> padrão do projeto (results.json é por jogo; o código computa o resto). Minutos jogados e defesas de
|
||||
> goleiro — caros por evento — ficam como **agregado opcional** (`keeper-stats.json`) ou são omitidos.
|
||||
> Entrada **incremental por rodada** (encaixa no fluxo diário `/update-worldcup`), não num lote único.
|
||||
|
||||
### 2.3 Fora de escopo v1
|
||||
- **xG**: não existe feed; exige provedor externo. Slot de UI previsto, **escondido** até haver dado.
|
||||
- Distância percorrida por jogador, dribles, sprints: custo de manutenção alto demais para site estático.
|
||||
|
||||
---
|
||||
|
||||
## 3 · UI/UX — Proposta de navegação (wireframe textual)
|
||||
|
||||
**Colocação:** novo 6º tab `Stats` / `Estatísticas`, último (é o epílogo do torneio). Reusa o
|
||||
`tablist` WAI-ARIA existente (roving tabindex, Setas/Home/End) — zero paradigma novo.
|
||||
|
||||
**Sub-navegação:** barra de chips *sticky* (scrollspy) abaixo do hero — **`<nav>` de âncoras, não um
|
||||
segundo tablist** (evita conflito de Setas com o tab do topo; é também o gatilho do render preguiçoso).
|
||||
Chips: **Overview · Times · Jogadores · Recordes · Comparador · Arquivo** (chips de seções vazias somem).
|
||||
|
||||
```
|
||||
┌─ #panel-stats ───────────────────────────────────────────────────────────┐
|
||||
│ ╔═ HERO "o veredito" (glass, slide-up) ════════════════════════════════╗ │
|
||||
│ ║ 🏆 CAMPEÃO [bandeira] vice · 3º · Chuteira de Ouro · Seleção ║ │
|
||||
│ ║ [ 172 ] [ 2.68 ] [ 31 ] [ 3.1M ] ← tiles count-up ║ │
|
||||
│ ║ Gols Gols/jogo Pênaltis Público ║ │
|
||||
│ ╚═══════════════════════════════════════════════════════════════════════╝ │
|
||||
│ ┌─ SUB-NAV sticky (scrollspy) ● Overview Times Jogadores … Arquivo ───┐ │
|
||||
│ ═══ #stats-overview ═══ cards (Partidas/Média/Cartões/Clean sheets) + │
|
||||
│ CHART "gols por fase" (barras SVG, reveal) + "gols por rodada" │
|
||||
│ ═══ #stats-teams ═══ filtros[confederação▾ fase▾ ⌕] + │
|
||||
│ LEADERBOARD ordenável (#, time, P W E D, GF GA GD, Pts, [xG]) + │
|
||||
│ cards: maior goleada, caminho do campeão, forma V/E/D │
|
||||
│ ═══ #stats-players ═══ PÓDIO top-3 (Chuteira de Ouro, count-up) + │
|
||||
│ chips[Artilheiros·Assist·Cartões·Defesas] → troca corpo da tabela +│
|
||||
│ bloco PRÊMIOS + SELEÇÃO DO TORNEIO (gráfico de formação) │
|
||||
│ ═══ #stats-records ═══ grid de record-cards (auto) + faixa "ESTREIAS DO │
|
||||
│ FORMATO 48" (destaque) + cards editoriais (curiosities.json) │
|
||||
│ ═══ #stats-comparator ═══ [A▾] vs [B▾] toggle Times/Jogadores + │
|
||||
│ barras divergentes espelhadas (anima na escolha) │
|
||||
│ ═══ #stats-archive ═══ 104 resultados, accordion por fase, filtros/sort, │
|
||||
│ linha → openMatchModal() (preguiçoso, por último) │
|
||||
└───────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
Acima da dobra (desktop): hero veredito + 4 tiles + sub-nav. O resto é recompensa de scroll.
|
||||
|
||||
**Componentes reutilizáveis:** `stat-card` (tile count-up), `leaderboard-table` (ordenável, a11y,
|
||||
linha favorita dourada), `podium` (top-3), `record-card` (clica → modal), `chart-panel` (SVG/CSS +
|
||||
`<details>` tabela alternativa), `comparator`, `filter-bar`, `section-nav` (scrollspy),
|
||||
`results-archive`, `chip-tabs` (toggle intra-seção via `aria-pressed`, não tablist).
|
||||
|
||||
**Interações/animações** (todas atrás de `prefers-reduced-motion`):
|
||||
count-up dos números (IntersectionObserver, ~900ms ease-out), barras crescendo da base,
|
||||
linha com `stroke-dashoffset`, FLIP no re-sort, duelo de barras no comparador, reveal por seção.
|
||||
|
||||
**Responsividade:** ≤767 hero empilha + tiles 2×2, sub-nav scroll horizontal com snap, tabelas com
|
||||
rank+nome congelados e scroll-x numa região focável; 768–1439 grids 2-col; 1440+ `.container`
|
||||
(`min(1200px,100%-2rem)`), grid 4-col, hero numa linha.
|
||||
|
||||
**Acessibilidade:** `<table>` real com `<caption>`/`scope`/`aria-sort` (header ordenável = `<button>`);
|
||||
sub-nav é `<nav>` (não tablist); charts com alternativa textual (`<details>` tabela + `aria-label`);
|
||||
count-up: valor final é o DOM real, tween só visual (`aria-live="off"`); foco visível via
|
||||
`:focus-visible` existente.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Stack e dependências
|
||||
|
||||
**Princípio:** zero dependência nova de runtime. Mantém o mandato do projeto (vanilla ES Modules, sem
|
||||
framework/bundler/CDN) e o orçamento de **JS < 300KB (hoje ~74KB)**.
|
||||
|
||||
- **Gráficos: SEM biblioteca.** Barras/linhas/donut feitos à mão em **SVG inline + CSS** (casa com a
|
||||
estética e com o orçamento). Se um gráfico realmente exigir mais, `import()` dinâmico de micro-lib
|
||||
(<poucos KB) só quando a seção entra na viewport — **postura padrão: peso zero de chart-lib.**
|
||||
- **Animação:** reusa `animations.css` + IntersectionObserver; helper `countUp()` em vanilla
|
||||
(`requestAnimationFrame`). Tudo desligado em `prefers-reduced-motion`.
|
||||
- **i18n:** novo namespace `stats.*` em `i18n.js` (dicts EN **e** PT). Nomes de dados não se traduzem.
|
||||
- **CSS:** novo `assets/css/stats.css` (linkado no `<head>` como `bracket.css`), usando os tokens
|
||||
existentes (`--accent-gold`, `--glass-bg`, `--radius`, etc.).
|
||||
- **Tooling opcional (não-shippado):** script Node sem deps para gerar/validar agregados de jogadores
|
||||
a partir do log de eventos — facilita a entrada manual. Fora do bundle do site.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Roadmap de implementação (incremental, com portões de aprovação)
|
||||
|
||||
> Convenção do projeto: 1 etapa por vez, resumo + aprovação antes da próxima. Esforço: **S** ≈ meia
|
||||
> sessão · **M** ≈ 1 sessão · **L** ≈ ~2 sessões. Etapas A–F entregam uma tela completa só com dados
|
||||
> existentes; G–I acendem as camadas 2/3/4; J é polimento.
|
||||
|
||||
| # | Etapa | Entrega | Camada | Esforço |
|
||||
|---|---|---|---|---|
|
||||
| **A** | **Scaffolding + motor de degradação** | 6º tab, `#panel-stats`, `stats.js`, `stats.css`, namespace `stats.*`, render preguiçoso, scrollspy, `loadData()` tolerante a falha, contrato `isAvailable` (§0) | infra | **M** |
|
||||
| **B** | **Overview + Hero** | veredito (campeão/vice/3º/4º), 4 tiles count-up, gráfico gols-por-fase e gols-por-rodada | ✅1 | **M** |
|
||||
| **C** | **Estatísticas de times** | `leaderboard-table` ordenável+a11y, **ranking final 1–48 (cadeia de desempate)**, cards (maior goleada, caminho do campeão, forma, splits, clean sheets, sequências) | ✅1 | **L** |
|
||||
| **D** | **Recordes + Estreias do formato 48** | record-cards auto-deriváveis + faixa de destaque "104 jogos / maior caminho à final / Round of 32 / melhor 3º / 1º campeão da nova era" | ✅1 | **M** |
|
||||
| **E** | **Arquivo de resultados** | 104 jogos navegáveis, filtros/sort, linha → modal (reusa padrões de `schedule.js`) | ✅1 | **M** |
|
||||
| **F** | **Comparador de times** | seletor A vs B + barras divergentes animadas | ✅1 | **M** |
|
||||
| **G** | **Camada 2 — dados baratos** | estende `results.json` (attendance, cards y/r, decidedIn), `teams.json` (ranking/wcDebut/confederation), `stadiums.json` (coords); backfill `stats`; liga recordes de público, disciplina, zebras, distância | 🟡🧩2 | **M** + entrada de dados |
|
||||
| **H** | **Camada 3 — jogadores** | `players.json`+`player-events.json`+`awards.json`; pódio artilharia, chips (assist/cartões/defesas), bloco de prêmios, **Seleção do Torneio** (formação), comparador de jogadores, recordes de tempo de gol | 🔴3 | **L** (maior) + entrada contínua |
|
||||
| **I** | **Camada 4 — editorial** | `curiosities.json` + `all-time-baselines.json`; render de cards editoriais + painel "esta Copa vs história" | 📝4 | **M** + redação |
|
||||
| **J** | **Polimento** | auditoria responsiva/a11y, performance (lazy, sem blur em cards repetidos), Lighthouse, bump `DATA_VERSION`, README + i18n review | todas | **M** |
|
||||
|
||||
Caminho mínimo para uma tela publicável e bonita: **A→B→C→D→E→F** (tudo com dados de hoje).
|
||||
G/H/I são aditivos e podem entrar em qualquer ordem conforme os dados aparecem (graças a §0).
|
||||
|
||||
---
|
||||
|
||||
## 6 · Pontos de atenção (edge cases, dados incompletos, fallbacks)
|
||||
|
||||
1. **Torneio ainda não acabou (hoje 2026-06-14).** A tela é pós-Copa. Campeão/ranking só existem após
|
||||
a final → o **hero veredito não renderiza** enquanto `FINAL` não estiver `finished`. Opção: manter o
|
||||
tab **oculto** até a final terminar, ou deixá-lo "acender" progressivamente. Decidir antes da Etapa B.
|
||||
2. **`stats` esparso (hoje ~9/104).** Agregados de posse/chutes/cartões só aparecem com **cobertura
|
||||
completa**; senão ficam escondidos (sem disclaimer — requisito do usuário). Backfill é a Etapa G.
|
||||
3. **`cards` é só amarelo hoje.** Índice de disciplina/fair-play e "time mais faltoso" ficam
|
||||
incompletos até o split y/r (Etapa G). Antes disso: rotular como "amarelos" ou esconder.
|
||||
4. **Convenção de V/E/D no mata-mata.** Empate decidido nos pênaltis conta como empate (para gols) mas
|
||||
vitória/derrota (para avanço). Definir e documentar na camada de dados antes de somar retrospectos.
|
||||
5. **Desempate do ranking 1–48.** Cadeia determinística explícita: fase alcançada → pontos → SG → GP →
|
||||
(fallback id). Documentar; sem isso o ranking não é reproduzível.
|
||||
6. **Burden e confiabilidade dos dados de jogador.** ~400+ linhas manuais; risco de typo/evento
|
||||
perdido. Usar **uma fonte autoritativa** (site oficial FIFA), entrada incremental por rodada. UI
|
||||
esconde seções/linhas sem dado (§0) — nunca mostra lacuna.
|
||||
7. **Editorial bilíngue.** Todo card de `curiosities.json` precisa EN **e** PT; se faltar um idioma,
|
||||
*fallback* para o outro (não renderiza em branco).
|
||||
8. **Performance.** Render preguiçoso por seção; **sem `backdrop-filter` em cards repetidos** (custo de
|
||||
paint — regra já vigente no projeto); charts em SVG/CSS sem lib; memoizar o modelo.
|
||||
9. **Cache-busting & deploy.** Bumpar `DATA_VERSION` quando novos `data/*.json` entrarem; novos
|
||||
arquivos em `data/` **são** deployados (bom); `.agents/` (este plano) é excluído (bom). Paths
|
||||
relativos para fotos de jogador (gotcha #7 — subpath Hostinger/Pages).
|
||||
10. **Não quebrar o existente.** Manter o padrão de import circular com `app.js`; novo tab não pode
|
||||
alterar o contrato do `tablist`/roteamento atual.
|
||||
11. **Mídia ausente.** Fotos de jogador/bandeiras → `onerror` para monograma/silhueta; nunca imagem
|
||||
quebrada.
|
||||
12. **xG sem feed.** Coluna/seção xG só existe se todos os times tiverem o dado; senão, removida (não
|
||||
mostrar coluna pela metade).
|
||||
|
||||
---
|
||||
|
||||
## Apêndice — proveniência
|
||||
Consolidado de 5 sub-agentes (Times, Individuais, Partidas, Curiosidades, UX). Lista completa de
|
||||
métricas com tags de disponibilidade está no histórico da sessão de 2026-06-14 (Fase 1). Escopo
|
||||
aprovado: 4 camadas + degradação graciosa como requisito de primeira ordem.
|
||||
Loading…
Add table
Add a link
Reference in a new issue