feat(stats): degradation engine + scrollspy sub-nav (Stage A)

Scaffold the post-Cup stats screen (.agents/stats-screen-plan.md) on the
feature/stats-final-screen branch.

- loadData(): fault-tolerant optional data layers (players, player-events,
  awards, keeper-stats, curiosities, all-time-baselines) via loadOptional() —
  an absent/404 file defaults to empty SILENTLY (graceful degradation, keeps
  the console clean), warning only on a present-but-malformed file. The 6 core
  files still throw on failure.
- stats.js SECTIONS registry: a section and its sub-nav chip render only when
  available(model) holds, else they are omitted from the DOM entirely (no
  placeholder / no coming-soon). Overview/Teams live; the 4 future sections
  stay dark until later stages.
- Sticky scrollspy sub-nav: hash-safe anchor chips (preventDefault +
  scrollIntoView, never touch location.hash so the tab router does not bounce
  to Home); position-based scrollspy with an explicit page-bottom -> last
  section rule (robust on short pages). --header-h kept live via a
  ResizeObserver so the nav sticks correctly under the variable-height header.
- flagImg() monogram fallback: a broken flag SVG becomes a 3-letter code span,
  never a broken-image icon.
- i18n stats.nav* keys (EN/PT); stats.css for sub-nav / section / fallback.

No DATA_VERSION bump (no deployed data changed). No index.html change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lucas Kalil 2026-06-16 19:36:32 -03:00
parent 79e746e4d4
commit 387fab3c8b
4 changed files with 253 additions and 4 deletions

View file

@ -119,6 +119,13 @@ const dicts = {
'stats.tileAvg': 'Goals / match',
'stats.tileBiggestMargin': 'Biggest margin',
'stats.tileCleanSheets': 'Clean sheets',
'stats.sectionsNav': 'Statistics sections',
'stats.navOverview': 'Overview',
'stats.navTeams': 'Teams',
'stats.navPlayers': 'Players',
'stats.navRecords': 'Records',
'stats.navComparator': 'Comparator',
'stats.navArchive': 'Archive',
'stats.overviewTitle': 'Overview',
'stats.played': 'Matches played',
'stats.decisive': 'Decisive',
@ -259,6 +266,13 @@ const dicts = {
'stats.tileAvg': 'Gols por jogo',
'stats.tileBiggestMargin': 'Maior margem',
'stats.tileCleanSheets': 'Sem sofrer gols',
'stats.sectionsNav': 'Seções de estatísticas',
'stats.navOverview': 'Visão geral',
'stats.navTeams': 'Seleções',
'stats.navPlayers': 'Jogadores',
'stats.navRecords': 'Recordes',
'stats.navComparator': 'Comparador',
'stats.navArchive': 'Arquivo',
'stats.overviewTitle': 'Visão geral',
'stats.played': 'Jogos disputados',
'stats.decisive': 'Decididas',