## v1.99.7 — 4 Jun 2026
- **Full-stats cache log — Fetch column** (`full-stats-cache-log.php`, `full-stats-cache.php`): stop defaulting missing `pagesFetched` / `apiRequests` to `0` on `persist_only` (show `—` = not reported). PHP logs those fields only when the browser POST includes them. `app.js` no longer forces `0` when passing delta fetch stats.

## v1.99.6 — 4 Jun 2026
- **Full-stats cache log — `new` column** (`full-stats-cache.php`, `full-stats-cache-log.php`): `persist_only` always logs `newMatches` as `matchCount − previous cache matchCount` (not client override). Viewer infers the same delta from consecutive `total` values when older log lines lack `newMatches` (fixes `new: —` on legacy rows).

## v1.99.5 — 4 Jun 2026
- **Full-stats `persist_only` client fetch stats** (`app.js`, `multiple.js`, `full-stats-cache.php`): browser passes `pagesFetched` / `apiRequests` (and `newMatches` when known) on `scheduleFullStatsCachePersistBackground` — from `loadFullStats` client flow, profile cache delta sync, and Multiple Accounts delta fetch (`fetchRecentApiUntilOverlap` tracks pages).
- **Version bump**: `app.js?v=1.99.5`, `multiple.js?v=1.99.5`.

## v1.99.4 — 4 Jun 2026
- **Full-stats cache log** (`full-stats-cache.php`, `full-stats-cache-log.php`): `persist_only` writes now log `pagesFetched`, `apiRequests`, and `newMatches` (delta vs previous cache `matchCount`; optional client overrides via POST body). Log viewer uses numeric parsing for these fields and shows `syncMode` under Fetch (fixes rows that only had `matchCount` / `total` populated).

## v1.99 — 29 May 2026
- **Recent Matches — Ranked only** (`app.js`): without full match history in memory, enabling the filter fetches ranked matches from Eleven (`ranked=true`, 100 per page); **Load 100 more** requests the next API page and appends results. With full-stats cache / hybrid history, filtering stays client-side only (no extra API calls).
- **Recent Matches — table paint** (`app.js`, `style.css`): rows built with `DocumentFragment` + `replaceChildren`; `scheduleMatchesListPaintFix` after render and after the profile loading overlay hides; `resolveMatchParticipants` supports `attributes.players` (team 0/1) when `home-team` / `away-team` are absent. CSS layer hints on `.match-row` / `.matches-list` to reduce Chrome grid paint gaps.
- **Profile loading overlay** (`app.js`, `style.css`): `repaintRecentMatchesAfterProfileLoad`; removed `backdrop-filter` on `#profileLoadingScreen` (v1.98.2, included in this release).
- **Recent Matches header** (`style.css`): **Ranked only** checkbox aligned to the right; section title stays left.
- **HOF Biggest Comeback** (`app.js` — `computeHofStats`): only ranked **completed** matches (≥2 completed sets won by the player, first completed set lost); set scores in card/modal exclude in-progress sets (e.g. 3-3).
- **Version bumps**: footer `v1.99`; `app.js?v=1.99.0`, `multiple.js?v=1.99.0`, `style.css?v=1.99.0`; `CHANGELOG_FALLBACK` + `public-changelog.md`.

## v1.98.7 — 29 May 2026
- **HOF Biggest Comeback** (`app.js` — `computeHofStats`): eligible matches must be ranked, completed (`isCompletedMatch`), not forfeited 0-0, and won with at least two **completed** sets; first-set deficit uses the first completed set only; set list excludes in-progress scores (e.g. 3-3). Aligns with Signature Victory / Fast & Furious / Nemesis gates.
- **Version bump**: `app.js?v=1.98.7`.

## v1.98.6 — 29 May 2026
- **Recent Matches row paint** (`app.js`, `style.css`): `renderMatches` builds rows via `DocumentFragment` + `replaceChildren`; `scheduleMatchesListPaintFix` after render and after profile loading overlay hides; `resolveMatchParticipants` falls back to `attributes.players` (team 0/1) or user-id stubs when `home-team`/`away-team` are missing (ranked API payloads). CSS: `translateZ(0)`, `isolation`, `contain` on list/rows to reduce Chrome grid paint gaps.
- **Version bump**: `app.js?v=1.98.6`, `style.css?v=1.98.6`.

## v1.98.5 — 29 May 2026
- **Recent Matches — Ranked only API pagination** (`app.js`): ranked-only mode without full history now tracks `apiPage` / `apiTotalPages` from match list `links.last`; **Load 100 more** fetches `page[number]=N+1` with `ranked=true` and merges into `rankedOnlyApiContext` (`loadMoreRankedOnlyApiMatches`, `fetchRankedOnlyMatchesPage`).
- **Version bump**: `app.js?v=1.98.5`.

## v1.98.4 — 29 May 2026
- **Recent Matches — Ranked only without full history** (`app.js`): when the profile has no in-memory full match history (`profileFullStatsCacheServedPlayerId` / `matchHybridContext` from server full-stats file), enabling **Ranked only** triggers GET `/accounts/{id}/matches?...&ranked=true` (`applyRankedOnlyApiFetch`, `rankedOnlyApiContext`); disabling clears that context and restores the normal unranked sample. With complete history, behavior unchanged — client filter via `isMatchRankedForList` on the full pool.
- **Version bump**: `app.js?v=1.98.4`.

## v1.98.2 — 29 May 2026
- **Recent Matches paint** (`app.js`, `style.css`): re-render match table after profile loading overlay fully hides (`repaintRecentMatchesAfterProfileLoad`); removed `backdrop-filter` on `#profileLoadingScreen` to avoid Chrome compositor gaps until hover.

## v1.98.1 — 20 May 2026
- **Multiple → opponent profile** (`app.js`, `multiple.js`): context menu “Open in new tab” uses clean `buildIndexProfileUrl` (no `back_to`; original tab unchanged); same-tab H2H link keeps `back_to` + `matchup_opp` for BACK; hash `loadPlayer` deferred on `/multiple?main=` to avoid racing combined boot.

## v1.98 — 20 May 2026
- **HOF opponent ELO — no live account lookup** (`app.js`): removed `hydrateHofOpponentCurrentElo`, `fetchAccountEloByUserIds`, `collectHofOpponentIds`, `patchHofStatsOpponentElo`, and `liveEloByUserId`; Insights cards and HOF modals show opponent `lastElo` from the **most recent completed meeting** in the loaded match set (`home-elo` / `away-elo` on newest-first rows). Eliminates batch `/accounts/users?userIdList=` on profile/HOF render and language refresh.
- **Matchup modal** (`app.js` — `resolveEloFromMatchHistory`): hero ELO for both players comes from newest H2H row in merged matchup data; no `/accounts/users` prefetch.
- **Multiple Accounts** (`multiple.js`): dropped `hydrateHofOpponentCurrentElo` bridge calls; `renderHofScopeStats` is synchronous again.
- **Version bumps**: footer `v1.98`; `app.js?v=1.98.0`, `multiple.js?v=1.98.0`; `CHANGELOG_FALLBACK` + `public-changelog.md`.

## v1.97 — 17 May 2026
- **Waived match detection** (`app.js` — `isWaivedByRoundsOrRaw`): central helper classifies ranked voided sessions using round data when `included` rounds exist (zero completed points/sets); raw `0-0` fallback only when both `home-score` and `away-score` are explicitly present in the payload (avoids false positives on legacy rows missing scores).
- **Waived timeline for ELO chart** (`app.js`): `readFullStatsCacheForProfile` builds `waivedTimelineByPlayer[playerId]` with exact ISO timestamps and minute buckets; `loadEloHistory` / ranked timeline filters drop waived points (and casual) using exact + minute keys; marker priming path re-scans marker/snapshot matches with page-level `included` rounds map.
- **Recent Matches UI** (`app.js`, `style.css`, `i18n.js` — `tip_waived`): ranked waived rows render **WAIVED** badge (cyan) instead of a misleading `0-0` score line; ELO column shows `0` with green/red context (opponent vs profile player left); tooltips localized in all 9 languages; `setLang` rehydrates waived tooltip HTML.
- **HOF opponent stats alignment** (`app.js` — `computeHofStats` / `oppCount`): Nemesis, Favorite Victim, and Longest Rivalry now count only **completed** ranked meetings (`isCompletedMatch` + `isRankedFull` + not forfeited 0-0), matching Most Played Opponent ranked denominator — fixes inflated rivalry totals vs ranked-only subtitle on Most Played card.
- **HOF live opponent ELO** (`app.js` — `hydrateHofOpponentCurrentElo`, `renderHofWithLiveOpponentElo`, `collectHofOpponentIds`): after `computeHofStats`, batch `/accounts/users?userIdList=` (chunks of 100) updates `lastElo` on displayed opponent entities; wired into profile quick/full paths, cached full-stats render, language refresh, and `multiple.js` combined scope toggles via `__appBridge`.
- **Multiple Accounts — HOF scope parity** (`multiple.js`, `multiple.html`): combined profile always renders Insights on aggregated **last ~100** matches first (`sliceCombinedHofQuick` when full history already loaded); **Show full statistics** / **Back to last 100 matches** toggle (`hofQuickBtn`, `applyCombinedHofScope`, `state.combinedHof`); `hof_show_quick_btn` i18n renamed to Back to last 100 matches (all locales).
- **Internal tool** (`decript_names.html`, `decript-names-cache.php`): paste access-log rows, resolve account IDs to names; server-side history list (not user-facing).
- **Version bumps**: footer `v1.97`; `app.js?v=1.97.0`, `multiple.js?v=1.97.0`, `style.css?v=1.97.0`; `CHANGELOG_FALLBACK` + `public-changelog.md`.

## v1.96 — 13 May 2026
- **ElevenVR load reduction** (profile / multiple): cache-first full-stats read, match delta cooldown (`PROFILE_MATCH_DELTA_COOLDOWN_MS`), `fromMatchId` incremental sync, batched `/accounts/users` where already used.
- **Loading screens** (`index.html`, `multiple.html`, `app.js`, `style.css`): profile/multi loading screens with `ettlogo_round_192.png` until profile data ready.
- **HOF quick vs full on cache hit** (`applyProfileFromFullStatsCacheBundle`): render quick HOF first; Show full statistics / quick back toggle when eligible.
- **Files**: `app.js?v=1.96.5`, `multiple.js?v=1.96.6`, `style.css?v=1.96.3`.

## v1.95 � 12 May 2026
- **Profile ELO prefetch** (`app.js`, `config.js`, `index.html`, `multiple.html`): `scheduleProfileEloHistory` after `loadHofQuick` runs `loadEloHistory` on profile open so `elo-history-csv` prefetches chart data; `ELO_CSV_LOOKBACK_COUNT` 500; hero MAX from CSV window without full-stats cache, exact peak from full ranked timeline when server cache exists; `elo-history-max` cache for both tiers.
- **Combined profile** (`multiple.js`): `MULTI_ELO_MAX_POINTS` 500 for CSV lookback; CSV aggregate peak when not all accounts have full cache; otherwise `peakFromFullStatsMatches`.
- **Full stats (first run)** (`app.js`): after all match pages are fetched, HOF / ELO / unique-opponents update from in-memory rows; `full-stats-cache.php` runs in the background without blocking on its response.
- **Full stats progress bar** (`app.js`): bar advances on each completed match-page fetch (from known `exactTotal` / `MATCH_PAGE_SIZE`), not only after each `HOF_FULL_BATCH` chunk.
- **Hero stats mobile portrait** (`index.html`, `multiple.html`, `style.css`): ELO+Rank, Wins+Losses, Ranked matches+Ranked % in two-column rows; Unique opponents title stacked; `style.css?v=1.93.0`.
- **HOF grid mobile landscape** (`style.css`, `app.js`): portrait under 768px stays single-column; phone landscape uses a fixed pairing layout (Signature+Underdog, streaks, Longest+Fast, Prime Time full width, three forfeit cards on one row, etc.) via `#hofGrid[data-hof-grid-variant]`; `style.css?v=1.93.2`.
- **Combined profile cache sync** (`multiple.js`, `app.js` `__appBridge`): per-account full-stats cache + Eleven delta; full HOF when all accounts sync; `persistOnly` in background; `app.js?v=1.95.8`, `multiple.js?v=1.93.5`.

## v1.92 � 12 May 2026
- **Mobile layout** (`index.html`, `style.css`): viewport `viewport-fit=cover`; `html { overflow-x: clip }`; touch phone landscape overrides for profile rails (`main`, `.hero`, `.section-elo`, `.hof-inner`, `.matches-wrap`, header/search); compact `match-row` grid for narrow landscape widths.
- **Recent matches** (`app.js`): `isTouchPhoneLandscape()`; aborted-match column shows `NAME LEFT` instead of `NAME LEFT THE GAME` only on touch phones in landscape (<600px short side).

## v1.91 � 11 May 2026
- **ELO chart data pipeline refactor** (`app.js`): replaced elo-history-csv dependency with ranked-match timeline construction from primeEloMarkerMatches, including computed current-elo points and match-id propagation in chart rows.
- **Chart stability hardening** (`app.js`): added load-sequence guards and watchdog/failsafe paths to avoid persistent Loading chart... states during slow or stale async flows.
- **Ranked recency consistency** (`app.js`): marker priming now refreshes with latest ranked page before finalizing chart data, reducing stale-cache drift versus recent matches table.
- **Waived/ranked filtering consistency fixes** (`app.js`): corrected waived fallback behavior when raw scores are missing and ensured page-level rounds context is used while filtering ranked rows from API pages.
- **Match-to-point alignment fixes** (`app.js`): for match-id timelines, removed legacy synthetic tail-point and legacy +1 marker shift; latest ranked match now maps to the final chart point with correct marker/tooltip/upset context.

## v1.90 � 8 May 2026
- **Unique Opponents hero card** (`index.html`, `multiple.html`, `app.js`, `i18n.js`, `multiple.js`): added new hero metric with localized two-line label (`UNIQUE` / `OPPONENTS`) and full translation coverage for tooltip copy in all supported languages.
- **Unique Opponents data correctness** (`app.js`, `multiple.js`): unified counting logic to include all encountered opponents (no completion/ranked gating), fixed stale-value paths, and aligned single-profile + combined-profile flows to read from full-stats datasets consistently.
- **Full-stats cache source selection hardening** (`full-stats-cache.php`, `full-stats-cache-read.php`, `full-stats-cache-has.php`): when multiple cache files exist for the same player, selection now prefers the freshest/highest-quality entry (`updatedAt`, `matchCount`, `mtime`), preventing old partial files from driving UI metrics.
- **Incremental sync fallback for incomplete cache** (`full-stats-cache.php`): cache-first incremental mode now auto-falls back to full scan when cached `matchCount` is below known exact total; increased hard page cap for very large histories.
- **ELO chart waived exclusion** (`app.js`): chart timeline and marker mapping now explicitly ignore waived 0-0 matches (including API payload variants where `elo-change` may still be present), while preserving all matches with real ELO impact.

## v1.89 � 8 May 2026
- **Profile hero external buttons** (`index.html`, `multiple.html`, `app.js`, `style.css`): added two share-sized actions near profile share (`openCurrentProfileOnElevenVR`, `openCurrentProfileOnClubhouse`) with localized tooltips and ID-validity gating.
- **Opponent context menu expansion** (`app.js`, `i18n.js`): added dynamic `Copy Player ID: <id>` label + copy toast, plus external-open actions for ElevenVR and 11'ClubHouse.
- **Unified tooltip/menu UX** (`app.js`, `style.css`): context-menu open now suppresses conflicting hover tooltips (including HOF card-level tips) and restores them on close.
- **Pointer affordance fix** (`style.css`): removed tooltip-level cursor overrides and enforced pointer cursor on interactive controls to avoid arrow cursor on clickable UI.

## v1.88 � 8 May 2026
- **Prime Time card + tooltip localization** (`app.js`, `i18n.js`): added compact timezone rendering (`GMT+H` / `GMT+H:MM`) on-card and richer localized timezone tooltip copy; fixed tooltip precedence so card-level and context-menu interactions no longer overlap.
- **HOF local rivals i18n + availability UX** (`app.js`, `i18n.js`): introduced `hof_local_rivals` title key in all locales and explicit unavailable-state messaging for international profiles.
- **Unified opponent context menu** (`app.js`, `i18n.js`): centralized right-click menu for opponent links across matches and HOF (`.opp-link` + `.hof-link` via delegated handler), with actions for H2H, open profile in new tab, copy player ID, open on ElevenVR, and open on 11'ClubHouse.
- **Hero total note language-switch fix** (`app.js`): preserved `statTotalNote` numeric payload while translating only the trailing label, preventing total-match count loss on `setLang()`.

## v1.87 � 7 May 2026
- **Tooltip i18n hardening** (`app.js`, `i18n.js`): localized previously hardcoded tooltip strings (`has left the game`, `Share this card`, `More details`) via new keys (`tip_left_game_simple`, `tip_share_card`, `tip_more_details`) for all 9 languages.
- **Match table language refresh** (`setLang`): tooltip payloads now rehydrate on language switch for match id, H2H trigger, profile link, waived badges, ABND markers, score-extra messages, and opponent flag country names (`.opp-flag-wrap[data-country-code]`).
- **Locale completeness** (`i18n.js`): standardized `tip_unranked` wording in all locales and removed remaining English fallback fragments in non-EN language blocks.

## v1.86 � 4 May 2026
- **Local Rivals modal** (`openLocalRivalsModal`): **pagination** (`PAGE_SIZE_LOCAL_RIVALS=10`), pager UI + CSS; **`ensureLastOnlineForRows`** fetches `/accounts/users?userIdList=` only for the current page; removed global prefetch of all missing IDs; **`onlyActive`** filter uses `localRivalsLastOnlineCache` + row `lastOnline`.
- **Local Rivals last-online persistence**: `localStorage` key **`ettcache:local_rivals:last_online:v1`** with **12h TTL** (`hydrateLocalRivalsLastOnlineCache`, `persistLocalRivalsLastOnlineEntries`).
- **`loadFullStats`**: **`updateRankedPct`** uses **`exactTotal` / `totalNum`** instead of `cached.totalMatches` / `allMatches.length` to avoid incorrect 100% Ranked %.
- **`ranked-total.php`**: append **`cache/ranked_total_history.jsonl`** on successful fresh `/leaderboards` fetch (`appendRankedTotalHistory`).

## v1.84 � 3 May 2026
- **HoF modals** (`Signature Victory`, `Underdog Sting`): added **ELO change** column (match delta); CSS column widths/alignment; Underdog uses **`formatModalMatchEloChangeHtml(..., { asLoss: true })`** so API magnitude maps to negative player delta on losses.
- **`LAST_ONLINE_RELATIVE_MAX_DAYS`** (`config.js`): profile **`timeAgo`** switches to **month + year** via **`last_online_month_year`** i18n when full days inactive exceed threshold; **`paintHeroLastOnline`** + **`applyTranslations`** keep hero line + badges in sync on language change.
- **Profile activity badges**: **`inactivityBadge`** refactored to **`inactivityMeta` + `inactivityTooltip`** + `data-tooltip` (parity with Local Rivals table).

## v1.83 � 3 May 2026
- **�First active� on profile hero**: localized label + date of the **oldest recorded match** � from `full-stats-cache-read.php` when cache exists, otherwise **`matches` page 1 (`links.last`) + last page** (single extra API round-trip). Tooltip explains the source.
- **Day separator aggregates**: `renderMatches` / `renderMatchesAppend` use full merged lists (`bannerMatches`, `matchBannerAggregate`, hybrid cache, `__combinedRecentBanner`) so per-day match count, duration, and ELO banner stats stay correct when a calendar day spans pagination.

## v1.82 � 3 May 2026
- **Incremental match sync (`fromMatchId`)**: `app.js` / `multiple.js` hybrid recent-fetch and `full-stats-cache.php` server sync skip already-cached tail when the newest cached match id is known; full-stats event log gains `syncMode` / `fromMatchId` for diagnostics.
- **Dropped redundant `page[size]=1` count request**: `loadHofQuick` and `loadAccountBundle` derive approximate totals from `links.last` on the first quick HOF page (same page size); max rounding error 24 matches vs exact count.
- **Batched profile reads**: Local Rivals �last online� prefetch uses `/accounts/users?userIdList=` (chunks of 100); Multiple Accounts �Full stats� boot uses the same endpoint with per-id fallback.
- **`ranked=true` for ELO marker priming** (`primeEloMarkerMatches`): smaller match payloads when the chart needs extra ranked rows from the API.
- **Matchup modal**: in-session `matchupPairCache` plus `fromMatchId` on repeat opens to avoid re-downloading full H2H history every time.

## v1.81 � 2 May 2026
- ELO chart requests now pass **`lookbackCount`** aligned to `ELO_CHART_MAX_POINTS` (and Multiple Accounts to `MULTI_ELO_MAX_POINTS`) so api3 returns only the recent slice needed for rendering; first-load **max ELO** path still fetches full history when `elo-history-max` cache is cold.

## v1.80 � 2 May 2026
- ELO history: migrated from deprecated `elo-history` JSON endpoint to `elo-history-csv` (per upstream API guidance).
- `api-proxy.php`: routes `elo-history-csv` through raw HTTP fetch; normalizes api3 **tabular JSON** (`columns` + row arrays) into the JSON:API-like shape consumed by the chart; retries once with `/Accounts/...` casing when parsed output quality is low.
- `lib/eleven-elo-history-csv.php`: parser supports that tabular JSON format, legacy JSON:API `data[]` objects, and CSV-style text with delimiter heuristics.
- `lib/eleven-api-auth.php`: added `elevenApiFetchRaw` plus shared request composition / `curl` execution for non-JSON bodies.
- `app.js` / `multiple.js`: all ELO history fetches now target `/accounts/{id}/elo-history-csv`.

## v1.79 � 30 Apr 2026
- API access hardening and credential management:
  - introduced server-side credential pool config (`config/eleven-api-keys.php`) with ordered fallback credentials
  - added reusable auth/rotation helper (`lib/eleven-api-auth.php`) with active-key tracking, cooldown on auth/rate-limit failures, and persistent state (`cache/eleven_api_key_state.json`)
  - migrated server endpoints (`full-stats-cache.php`, `ranked-total.php`, `profile-view.php`, `combined-profile-view.php`) to centralized credential-rotation flow
  - added local API gateway endpoint (`api-proxy.php`) and moved frontend API traffic to server-side proxy path for localhost reliability
- Fallback behavior improvements:
  - profile loading now falls back to server-side full-stats cache when upstream profile endpoint fails
  - HOF quick flow now falls back to server-side cache datasets when live API quick requests fail
  - clearer warning states for API-unavailable cases (profile/search), including explicit no-cache failure messaging
- ELO chart UX refinements:
  - improved layering/visibility of outcome markers on top of line rendering
  - stabilized tooltip line ordering (`player ELO` first, opponent-result row second)
  - improved X-axis date readability near right edge and compacted tick format for constrained width
  - redesigned MIN/MAX signaling using anchored tags plus in-canvas directional arrows, with better alignment to resulting ELO points and tooltip compatibility
  - improved MIN/MAX marker readability while avoiding overlap with opponent ELO marker cloud
- Multiple Accounts reliability:
  - migrated multi-account aggregation API flow to local proxy path, fixing `Combine failed: Failed to fetch` when direct upstream auth was unavailable on localhost
  - aligned multi-page search/profile/match/elo fetches with server-side API management flow

## v1.78 � 29 Apr 2026
- Cross-page profile navigation polish:
  - from combined-profile H2H, opponent profile now opens on `index.html` with a persistent external back target
  - `BACK` button on `index.html` now supports return to originating combined-profile URL when no local stack entries remain
  - returning to `multiple.html` can restore H2H modal context (opponent id + opponent name)
- Multiple page UX cleanup:
  - removed redundant profile-level BACK button on combined profile page
- Most Viewed combined-card insertion refined:
  - deterministic trigger stored via `ettmv:lastCombined`
  - when missing from visible top list, combined card can replace slot #6 (while keeping section capped to 6 cards)
  - added robustness fields (`createdAt`, `lastViewedAt`) for ordering fallback
- Changelog markdown parsing improvements:
  - support for relative links (`multiple`) in addition to absolute/host-root links
  - environment-safe changelog links for local subfolder deployments

## v1.77 � 29 Apr 2026
- Data-loading optimization for both `index.html` and `multiple.html`: recent-match history now uses hybrid fetch logic (fresh API head + overlap detection + server-side history completion) to reduce unnecessary upstream calls
- Added read-only cache endpoint `full-stats-cache-read.php` for fast historical retrieval from server-side full-stats storage
- Added combined-profile usage tracking/registry:
  - `combined_profiles.json` cache registry with `created_at`, `last_visit`, `views`, and account association metadata
  - `combined-profiles-track.php` endpoint for safe atomic updates
  - `combined-profiles-log.php` user-friendly viewer (HTML + JSON mode)
- Multiple Accounts recent matches now follow standard paging behavior:
  - initial render capped to first 100 combined matches
  - `Load 100 more` restored with local aggregated paging
- Changelog markdown rendering now supports clickable `[text](url)` links in modal content

## v1.76 � 29 Apr 2026
- New major feature: `Multiple Accounts` combined profile (`/multiple`) that aggregates data from one main account plus secondary accounts into a single unified profile view
- Combined profile now includes full hero aggregation, merged recent matches, merged insights/HOF logic, synchronized dual-line ELO evolution, and account-owner context in relevant match lists/modals
- Added dedicated combined-profile sharing route (`/m?main=...&secondary=...`) with social preview metadata support
- Added polished loading UX on combined profile boot (dynamic loading overlay + smooth fade transition)
- Main app match table improvement: each recent match now shows a compact clickable Match ID tag that copies `Match ID: #...` to clipboard

## v1.75 � 28 Apr 2026
- Mobile modal refinements for readability and compactness across HOF dialogs
- Longest Match score column updated: bold overall score, per-set scores stacked, tighter row spacing
- Best Day Ever mobile table header now shows WINS, and mobile date two-line rendering uses equal font size

## v1.74 � 28 Apr 2026
- Mobile UI optimization across all HOF modals: compact column headers (Pts/Dur/T/R/U/Rate/Time/W), smaller fonts and padding
- Date columns in all modals now display day+month on first line and year below on mobile screens
- Fixed truncated column headers in MOST PLAYED OPPONENT, MOST ACTIVE DAY, BEST DAY EVER, LONGEST MATCH, FAST & FURIOUS
- Fixed "Unranked" text bleeding into Score column in SIGNATURE VICTORY and UNDERDOG STING on mobile

## v1.73 � 28 Apr 2026
- Main matches table now opens head-to-head history directly from opponent name click (same pattern as other interfaces)
- Added profile-link tooltip on opponent name in H2H modal and fixed its visibility/clipping behavior
- Additional UX consistency refinements for profile navigation between matches list, H2H modal, and player profile

## v1.72 � 28 Apr 2026
- Completed i18n coverage for newly added modal/table labels and tooltips across all supported languages
- Replaced remaining language-specific hardcoded UI fragments with translation keys
- Improved ranked-scope subtitle localization consistency in all modal contexts

## v1.70 � 27 Apr 2026
- Meaningful insights rebrand:
  - replaced legacy naming in interface and changelog references with `MEANINGFUL INSIGHTS` / `insights cards`
  - updated related FAQ/SEO copy to keep wording consistent
- Full-stats cache-first UX and performance:
  - added `full-stats-cache-has.php` lightweight cache probe endpoint
  - `loadFullStats()` now chooses UX path dynamically:
    - cache exists -> no progress bar, rotating funny messages while server-side full stats completes
    - no cache -> classic progress bar flow
  - robust fallback to classic client flow when cache-first path fails
- Full-stats observability:
  - added `apiRequests` counting in `full-stats-cache.php` (counts all upstream attempts, including retries/fallback transports)
  - exposed `apiRequests` in endpoint response and `_events.log`
  - surfaced `apiRequests` in `full-stats-cache-log.php` under Fetch diagnostics

## v1.69 � 26 Apr 2026
- Server-side full stats storage hardening and reliability:
  - added incremental checkpoint writes during long fetch runs to reduce timeout risk
  - improved JSON parsing resilience (BOM handling, double-encoded JSON, gzip decode fallback)
  - extended transport fallback chain for upstream fetches (strict/relaxed cURL + strict/relaxed stream)
  - improved atomic cache writes and event logging details for troubleshooting
- Added operational controls:
  - introduced `FULL_STATS_STORAGE` toggle in `config.js` to enable/disable server-side full stats storage
  - added read-only monitoring page `full-stats-cache-log.php` with filters and size insights

## v1.68 � 24 Apr 2026
- Profile share and social preview improvements:
  - finalized profile share link flow with dynamic OG preview support and cleaner share URLs
  - improved generated profile share image layout and readability using app typography
  - streamlined profile share image naming and storage in `shares/`
- Match and insight exploration UX refinements:
  - improved mobile interactions around insights cards and profile navigation safety
  - expanded H2H entry points from match context and insight context

## v1.67 � 23 Apr 2026
- Head-to-head (H2H) reliability and consistency:
  - fixed H2H totals to use the full available history instead of being capped by the rendered row window
  - fixed H2H match list to render all available meetings (not only the first ~25)
  - improved H2H data sourcing by combining local profile snapshots with `/matchup` responses and deduplicating by match ID
  - aligned main match-table ranked detection with H2H logic (`ranked !== false` OR non-zero ELO impact)
- H2H mobile UX refresh:
  - popup now uses full mobile width with a denser row layout for small screens
  - compact labels on mobile (`W/L`, `R/U`, shorter status tags, ELO suffix removed in compact mode)
  - improved close button visibility/stacking and fixed overlap issues with sticky header layering (`z-index`)
- Insights cards/HOF controls:
  - added H2H button tooltip behavior parity with share button (card-level tooltip is suppressed while hovering the H2H button)
  - share button tooltip now uses the same custom in-app tooltip system (no browser-native tooltip mismatch)
- Profile navigation:
  - added profile-level `? BACK` control to return to the previously viewed profile
  - refactored hero top-row structure (`PLAYER` + `BACK`) for stable alignment across desktop/mobile without layout side effects
- Player activity status badge:
  - added inactivity badges next to `Last online` with 3 levels:
    - `Inactive lately` (15�89 days)
    - `Inactive account` (90�364 days)
    - `Dormant account` (365+ days)
  - added distinct color coding per inactivity level, including enhanced blue emphasis for `Dormant`
- Hero rank display:
  - compacted unranked value in rank card from `Unranked` to `-` to avoid unnecessary card width expansion
- Most Played Opponent accuracy:
  - now counts only completed meetings (finished matches) instead of including abandoned/forfeited attempts
- Minimum-threshold logic updates:
  - increased minimum threshold for `Longest Rivalry`, `Die Hard`, and `Favorite Victim` from 3 to 5 ranked matches
  - updated related tooltips in all languages to match the new threshold
  - threshold calculations now consistently exclude forfeited 0-0 matches while keeping abandoned matches with points played
- Match table waived status:
  - for ranked forfeited 0-0 matches, score column now shows `WAIVED` badge (cyan/neon style) instead of `0-0 (0-0)`
  - ELO impact column for `WAIVED` now shows `0` with contextual color:
    - green when opponent waived
    - red when current profile player waived
- Share-card wording and clarity:
  - standardized footer wording in generated share images:
    - `Selected from last 100 ...` for quick scope
    - `Selected from all ...` for full scope
  - made scope line typography more discreet (lighter weight, softer color)
- Share-card historical ELO correctness:
  - for date/match-based cards, player ELO now uses the historical ELO from the relevant match/day/streak context
  - replaced current-ELO usage with context ELO snapshots (with safe fallback when historical value is missing)

## v1.66 � 22 Apr 2026
- Mobile header/search UX:
  - deep-search button in the compact header now shows icon-only (text hidden) to free horizontal space
  - language selector compacted on mobile (reduced horizontal padding, removed dropdown arrow)
  - tapping `Start typing a name above` now forces `search-active` full-width mode on mobile (search + full deep-search button)
  - fixed event propagation conflict where landing-hint tap immediately triggered `closeSearch()` and reverted the header state
- Landing naming update:
  - replaced `Eleven Table Tennis` with `ElevenVR` in all `landing_unofficial` translations (all 9 languages)
- Search result ranking:
  - kept existing relevance ordering (exact / startsWith / contains)
  - added tie-breaker: if two usernames differ only by letter casing, order by ELO descending
- Insights cards mobile interaction safety:
  - disabled opponent profile links inside insights cards on mobile to prevent accidental navigation while tapping for tooltips
- Profile navigation behavior:
  - each `loadPlayer()` now resets page scroll to top on both mobile and desktop
- Most Viewed (landing, mobile):
  - reduced excessive vertical whitespace between disclaimer and Most Viewed
  - reworked card layout to strict two equal columns (`repeat(2, minmax(0, 1fr))`) using full viewport width
  - kept section header alignment intact while applying width changes to card grid only
  - added uniform small grid gap between columns and rows
  - reduced card vertical height (padding and internal spacing tuned for denser display)
  - long usernames remain truncated with ellipsis; card width no longer expands based on name length
- Most Viewed rank display fix:
  - rank rendering now maps non-positive/invalid ranks to `Unranked` instead of displaying values like `#-1`
  - rank cache update logic now accepts explicit numeric values (including non-truthy values) via `null/undefined` checks instead of truthy checks

## v1.65 � 22 Apr 2026
- New feature: first release with per-card sharing for `Meaningful insights` � each HOF card can now generate its own personalized image for social sharing
- Share cards: major visual refresh across multiple HOF templates to improve readability in small previews (larger header/footer typography, rebalanced content blocks, stronger hierarchy for key numbers)
- Share cards: Signature Victory and Underdog Sting redesigned for better storytelling � clearer `vs` row, larger player ELO values, centered ELO difference emphasis, larger date row, and ranked/unranked + ELO gain messaging moved under set scores
- Share cards: Biggest Comeback updated to prioritize set-by-set score visibility (significantly larger set score rendering) while keeping the rest of the layout stable
- Share cards: Longest Match now emphasizes duration as the dominant center element and uses minute+second formatting (`Xm YYs`); opponent ELO now included in both in-app HOF card and generated share image
- Share cards: Most Played Opponent / Die Hard / Favorite Victim / Longest Rivalry typography and spacing tuned for preview readability; context footer text standardized to `Based on all ${XX} ranked matches` where applicable, and `Most Played Opponent` uses the all-matches total (ranked + unranked)
- Share cards: Most Active Day and Best Day Ever refreshed with clearer labels (`MATCHES`, `ACTUAL PLAYING TIME`) and stronger date emphasis
- Share cards: Win/Loss streak and forfeit-related cards received header/footer scaling and proportional typography updates for improved legibility
- Share icons: `Most Played Opponent` now uses dynamic semantics � `??` when unranked meetings dominate, `????` when ranked meetings dominate; `Longest Rivalry` now uses `??`
- Insights cards share button UX: per-card share button now uses tooltip text `Share this card`; while hovering/focusing that button, the card-level tooltip is temporarily suppressed to avoid overlap
- Hero stat cards: reduced horizontal padding to make cards visually narrower and denser without changing card structure (`14px 10px` desktop, `8px 10px` mobile)
- i18n consistency: corrected `label_rank` language mapping inversions (`sv` now `RANKNING`, `ro` now `POZI?IE`) and verified `unranked` mapping consistency across all 9 languages

## v1.63 � 20 Apr 2026
- Landing page: new collapsible "ABOUT / FAQ" section added below Most Viewed � improves SEO with structured content and FAQ schema markup (JSON-LD)
- SEO: updated page title and meta description across title, Open Graph and Twitter Card
- Match table: score tooltip now shows player names in bold on the first line, followed by the existing content

## v1.62 � 20 Apr 2026
- Added 6 new interface languages: Portuguese (BR), Spanish (ES), Swedish (SE), Polish (PL), Italian (IT), Romanian (RO)
- All UI labels, tooltips and HOF cards fully translated in every new language
- Language selector dropdown updated with flags and codes for all 9 supported languages

## v1.61 � 19 Apr 2026
- Match table: ELO diff pill now shows a specific tooltip with actual player names and exact point difference (e.g. "At match time, PlayerX had 164 more ELO points than PlayerY") � replaces the previous generic explanation
- Match table: tooltip for unranked matches simplified � no longer lists both players; now shows "Unranked match � no ELO change"
- Match table: tooltip for mid-game abandoned matches (? badge) now identifies who abandoned � "PlayerX abandoned the match after points were played" � instead of a generic "X vs Y � not completed"
- Mobile: tapping the set score (e.g. "2-1") now shows a tooltip with the full per-set breakdown (e.g. "11-7 / 7-11 / 13-11")
- Fix: ELO diff pill tooltip was not visible on desktop � pill changed to inline-block and generic cell tooltip removed to avoid conflict

## v1.60 � 19 Apr 2026
- Mobile: ELO Evolution chart now stretches edge-to-edge � both axes hidden on small screens, chart fills full width
- Mobile: ELO chart shows first & last date at bottom edge; MAX/MIN badges include a compact date line (e.g. "Apr '26")
- Mobile: match table overhauled � arrow column removed, proportional grid (55% name / flexible score / fixed ELO), opponent name truncates gracefully while always preserving the ELO diff pill
- Mobile: HOF section title abbreviated for compact layout
- Match table: win/loss arrow replaced with neutral grey bar for all matches with no ELO impact (unranked, not ranked, forfeited) � previously only abandoned matches showed the bar
- Mobile: FORFEITED label now distinguishes who quit � "QUIT" in orange (current player) vs "FORFEIT" in red (opponent)
- Mobile: abandoned mid-game matches show a ? badge before the ELO value to signal the match was not played to completion

## v1.59 � 18 Apr 2026
- ELO Evolution chart: interactive crosshair � vertical line, hover dot and tooltip (date + ELO) follow the cursor across the chart
- Fix: Signature Victory & Underdog Sting now correctly exclude unranked matches from ranked detection (matches with `ranked: false` no longer shown as "Ranked" on the card)
- HOF abandon cards: sub-line now shows `N of X ranked matches` instead of just `N ranked matches`, making the percentage immediately verifiable
- Fix: tooltips for Player Forfeits, Early Exits and Opp. Forfeits updated � removed incorrect mention of ELO gained/lost

## v1.58 � 18 Apr 2026
- HOF section now open by default when a profile loads
- Fix: match table no longer shows "-0" for matches with 0 ELO change
- Ranked % hero card: now calculated from HOF stats (accurate ranked count, including forfeited matches); overridden with full-history count after full stats calculation
- Fix: 0-0 abandoned ranked matches with `elo-change: 0` now correctly shown as FORFEITED instead of NOT RANKED
- Match table: added NOT RANKED label for matches with `elo-change: 0` on a completed score (ambiguous API data � may be unranked or a ranked match with no ELO applied)

## v1.57 � 18 Apr 2026
- HOF: Signature Victory & Underdog Sting � added ranked/unranked indicator and ELO points gained/lost
- HOF: Biggest Comeback � added ranked indicator, ELO gained, and opponent's current ELO
- HOF: Win Streak & Loss Streak � clarified as ranked-only in sub-line
- HOF: Most Played Opponent � sub-line now shows breakdown: `X ranked / Y unranked`
- HOF: Longest Match � added set scores
- HOF: Best Day Ever � added victory count
- HOF: Die Hard, Favorite Victim, Longest Rivalry � opponent's current ELO displayed next to name
- HOF: Player Forfeits, Early Exits, Opp. Forfeits � removed ELO totals; replaced with ranked match count

## v1.56 � 18 Apr 2026
- Hero: added 7th stat card showing percentage of ranked matches out of total matches played
- HOF full stats: added 3 new cards (Player Forfeits, Early Exits, Opp. Forfeits), all calculated relative to total ranked matches
- HOF grows from 12 to 15 cards after full stats calculation

## v1.55 � 08 Apr 2026
- Search: added deep search button � paginates through API results until a matching player is found or all pages are exhausted
- Search: results sorted by relevance (exact match ? starts with ? contains)
- Search: purely numeric input triggers a direct player ID lookup in parallel
- Search: query sent to API in uppercase for more consistent results
- Search: result pool accumulated across successive requests for the same query (dedup by ID)

## v1.54 � 08 Apr 2026
- Signature Victory & Underdog Sting: abandoned matches now filtered out � only counts matches where at least 2 complete sets were won
- GA4: corrected tracking tag ID; added `view_player` event on every profile view

## v1.53 � 29 Mar 2026
- Language selector replaced with a dropdown (flag + code + arrow) � reduces header clutter, especially on mobile

## v1.52 � 29 Mar 2026
- Eliminated two redundant network requests on profile load (`loadVersion`, `updateTotalCount`) � total match count now extracted from existing HOF response
- Unofficial disclaimer added to title, meta tags, OG/Twitter, landing page and footer

## v1.50 � 29 Mar 2026
- Longest Match: duration now shown in MM'SS'' format instead of minutes only
- Die Hard / Favourite Victim: minimum match threshold lowered from 5 to 3 ranked matches

## v1.49 � 29 Mar 2026
- Fix: hero layout broken � player name and stat boxes were overlapping
- Fix: HOF card tooltips not appearing due to incorrect `overflow: hidden` placement

## v1.46 � 29 Mar 2026
- Fix: Most Played Opponent was incorrectly filtered to ranked-only; now counts all matches (ranked + unranked)
- Fix: Biggest Comeback was not filtered to ranked matches only
- Biggest Comeback: displays all set scores instead of only the first set

## v1.45 � 29 Mar 2026
- Fix: Win Streak and Loss Streak now calculated from ranked matches only (unranked matches no longer break a streak)
- Fix: Win/Loss Streak date now shows the full interval (e.g. "13�17 Oct 2022") instead of only the start date
- Die Hard, Favourite Victim, Longest Rivalry: recalculated from ranked matches only
- Most Active Day: criterion changed from most matches played to longest total play time; 0-0 matches excluded
- Day separator: 0-0 matches excluded from displayed match count

## v1.44 � 27 Mar 2026
- Landing: "Start typing a name above" hint is now clickable and focuses the search box

## v1.41�v1.43 � 27 Mar 2026
- Fix i18n: multiple labels (HOF toggle, WIN RATE, CLEAR HISTORY, contact form errors) were not translating on language change

## v1.40 � 27 Mar 2026
- Fix: duplicate i18n key `matches_loading` causing incorrect button label during load

## v1.39 � 27 Mar 2026
- ETTSTATS logo is now clickable � returns to the landing page
- Landing: added MOST VIEWED section showing the 6 most visited profiles with flag, name, ELO, rank and visit count

## v1.38 � 27 Mar 2026
- Full stats calculation: page size increased from 25 to 100 � 4� fewer API requests, significantly faster

## v1.37 � 27 Mar 2026
- ELO Evolution: chart now loads lazily on first open (no request on profile load if section is closed)
- ELO Evolution: added ~5 evenly spaced date labels on X axis; max/min pills; limit extended to 150 points

## v1.36 � 27 Mar 2026
- ELO Evolution section reactivated and moved before the HOF section

## v1.33 � 26 Mar 2026
- Fix: "Calculate full statistics" button not reappearing correctly when navigating back to a profile
- HOF full stats loaded from cache automatically, without requiring button click

## v1.32 � 26 Mar 2026
- Fix: ELO history cache was storing the full array (~600 KB per player); now stores only the max ELO value

## v1.31 � 26 Mar 2026
- Fix: cache was storing full-stats and paginated match pages; per-player cache reduced from ~5 MB to ~120 KB

## v1.30 � 26 Mar 2026
- localStorage cache implemented: profile + matches 60 min, ELO history 2h, HOF full stats 12h
- Expired entries deleted automatically on app start

## v1.25 � 26 Mar 2026
- New HOF card: Most Active Day (total play time + match count)
- International flag shown for players without a country code
- Fix: ELO values rounded to integer throughout (hero, peak ELO, HOF cards, search results)

## v1.21�v1.23 � 26 Mar 2026
- Country flags added next to opponent names in HOF cards
- Fix: flags not updating on repeated occurrences of the same opponent

## v1.19�v1.20 � 26 Mar 2026
- Fix: search dropdown was appearing behind other page sections (z-index)

## v1.17 � 26 Mar 2026
- Opponent country flag displayed next to name in the match table

## v1.14�v1.16 � 26 Mar 2026
- Custom tooltips added to hero stat boxes, match table columns and HOF cards � translated in EN/DE/FR, update on language change
- Fix: hero tooltips were appearing behind the header

## v1.07 � 26 Mar 2026
- ELO difference column: added tooltip explaining green/red colour (translated EN/DE/FR)
- Desktop: score displayed as `2-1 (11-7 / 7-11 / 13-11)` with overall result in bold

## v1.05 � 26 Mar 2026
- Custom tooltips with 0.5s delay implemented across the entire interface

## v1.04 � 26 Mar 2026
- ELO diff now uses `>` / `<` symbols instead of `+` / `-` to avoid confusion with gain/loss
- Fix: ELO diff column alignment on mobile

## v1.02�v1.03 � 26 Mar 2026
- Mobile: match table shows ELO diff and overall score (2-1) instead of per-set scores
- Mobile: ELO diff displayed inline after opponent name on small screens

## v1.01 � 25 Mar 2026
- Mobile responsive layout: compact header, hero stats on 3�2 grid, HOF in single column

## v1.0 � 25 Mar 2026
- Public launch on ettstats.eu
- Player search, profile view, ELO history chart, Hall of Fame, match history with pagination
- localStorage caching, EN/DE/FR localisation
- SEO meta tags, Open Graph, Twitter Card, Google Analytics
- Contact modal (EmailJS)
