Nitro Type - Enhanced Stats

Race and racer data analytics: session tracking, hidden stats, XP breakdown, WPM curves, enhanced racelog, league calculators.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Nitro Type - Enhanced Stats
// @namespace    https://nitrotype.info
// @version      2.1.2
// @description  Race and racer data analytics: session tracking, hidden stats, XP breakdown, WPM curves, enhanced racelog, league calculators.
// @author       Captain.Loveridge
// @match        https://www.nitrotype.com/*
// @match        *://*.nitrotype.com/settings/mods*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        unsafeWindow
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ─── Singleton Guard ─────────────────────────────────────────────────────────
    const pageWindow = (typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : window;
    const SCRIPT_SINGLETON_KEY = '__ntEnhancedStatsSingleton';
    if (pageWindow[SCRIPT_SINGLETON_KEY]) {
        try { console.info('[ESTATS] Duplicate instance detected; skipping.'); } catch (e) { }
        return;
    }
    pageWindow[SCRIPT_SINGLETON_KEY] = true;

    // ─── Constants ───────────────────────────────────────────────────────────────
    const LOG_PREFIX = '[ESTATS]';
    const NTCFG_MANIFEST_ID = 'enhanced-stats';
    const NTCFG_MANIFEST_KEY = `ntcfg:manifest:${NTCFG_MANIFEST_ID}`;
    const NTCFG_VALUE_PREFIX = `ntcfg:${NTCFG_MANIFEST_ID}:`;
    const NTCFG_BRIDGE_VERSION = '1.0.0-bridge.1';
    const SETTINGS_STORAGE_VERSION = 1;
    const STORAGE_VERSION_KEY = `${NTCFG_VALUE_PREFIX}__storage_version`;
    const STORAGE_MIGRATED_AT_KEY = `${NTCFG_VALUE_PREFIX}__migrated_at`;
    const STORAGE_CLEANUP_AFTER_KEY = `${NTCFG_VALUE_PREFIX}__cleanup_after`;
    const LEGACY_CLEANUP_GRACE_MS = 30 * 24 * 60 * 60 * 1000;

    // ─── Settings Definition ─────────────────────────────────────────────────────
    const ESTATS_SETTINGS = {
        // ── Racer Profile ────────────────────────────────────────────────────
        ENABLE_SESSION_COUNTER: {
            type: 'boolean',
            label: 'Session Race Counter',
            default: false,
            group: 'Racer',
            description: 'Show a persistent race counter next to your profile dropdown.'
        },
        SESSION_INACTIVITY_MINUTES: {
            type: 'number',
            label: 'Inactivity Reset (minutes)',
            default: 30,
            group: 'Racer',
            min: 5,
            max: 120,
            step: 5,
            description: 'Auto-reset session counter after this many minutes of inactivity.',
            visibleWhen: { key: 'ENABLE_SESSION_COUNTER', eq: true }
        },
        ENABLE_HIDDEN_STATS: {
            type: 'boolean',
            label: 'Hidden Racer Stats',
            default: true,
            group: 'Racer',
            description: 'Show hidden stats (Nitros Used, Cars Owned, Longest Session) on racer profiles.'
        },

        // ── Race ─────────────────────────────────────────────────────────────
        ENABLE_RACE_ENHANCEMENTS: {
            type: 'boolean',
            label: 'Race Result Enhancements',
            default: true,
            group: 'Race',
            description: 'Show season points and skipped characters on the post-race scoreboard.'
        },
        ENABLE_WPM_CURVE: {
            type: 'boolean',
            label: 'Post-Race Graph',
            default: true,
            group: 'Race',
            description: 'Show a Graph button on race results to view WPM, accuracy, and nitro usage over time.'
        },

        // ── Stats Page ──────────────────────────────────────────────────────
        ENABLE_ENHANCED_STATS_PAGE: {
            type: 'boolean',
            label: 'Enhanced Stats Overview',
            default: true,
            group: 'Stats',
            description: 'Inject additional statistics from your account data into the stats page overview boxes and summary table.'
        },

        // ── Racelog ──────────────────────────────────────────────────────────
        ENABLE_ENHANCED_RACELOG: {
            type: 'boolean',
            label: 'Enhanced Racelog',
            default: true,
            group: 'Stats',
            description: 'Add extra columns, corrected stats, and session time estimates to the racelog.'
        },
        // ── Leagues ──────────────────────────────────────────────────────────
        ENABLE_LEAGUE_CALCULATOR: {
            type: 'boolean',
            label: 'XP-Races Calculator',
            default: true,
            group: 'Leagues',
            description: 'Show how many races needed to beat each league player.'
        },
        ENABLE_TOURNAMENT_WINS: {
            type: 'boolean',
            label: 'Tournament Wins',
            default: true,
            group: 'Leagues',
            description: 'Show personal and team tournament win counts on the leagues page.'
        },

        // ── Garage ─────────────────────────────────────────────────────────
        ENABLE_GARAGE_CAR_COUNT: {
            type: 'boolean',
            label: 'Garage Car Count',
            default: true,
            group: 'Racer',
            description: 'Show total car count next to the "Cars" heading on the garage page.'
        }
    };

    // ─── Settings Read/Write Utilities ────────────────────────────────────────────
    const getStorageKey = (settingKey) => `${NTCFG_VALUE_PREFIX}${settingKey}`;

    const canUseGMStorage = () => typeof GM_getValue === 'function' && typeof GM_setValue === 'function';

    const readCanonicalValue = (storageKey) => {
        if (!canUseGMStorage()) return undefined;
        try {
            return GM_getValue(storageKey);
        } catch {
            return undefined;
        }
    };

    const writeCanonicalValue = (storageKey, value) => {
        if (!canUseGMStorage()) return;
        try {
            GM_setValue(storageKey, value);
        } catch { /* ignore */ }
    };

    const readStorageMetaNumber = (storageKey, fallback = 0) => {
        const raw = readCanonicalValue(storageKey);
        const parsed = Number(raw);
        return Number.isFinite(parsed) ? parsed : fallback;
    };

    const dispatchActionResult = (requestId, status, error = '') => {
        if (!requestId) return;
        try {
            document.dispatchEvent(new CustomEvent('ntcfg:action-result', {
                detail: {
                    requestId,
                    script: NTCFG_MANIFEST_ID,
                    status,
                    error
                }
            }));
        } catch { /* ignore */ }
    };

    const readSetting = (settingKey) => {
        const meta = ESTATS_SETTINGS[settingKey];
        if (!meta) return undefined;
        const storageKey = getStorageKey(settingKey);
        try {
            const canonical = readCanonicalValue(storageKey);
            if (canonical !== undefined) {
                return meta.type === 'boolean' ? !!canonical : canonical;
            }
            const raw = localStorage.getItem(storageKey);
            if (raw == null) return meta.default;
            const parsed = JSON.parse(raw);
            if (meta.type === 'boolean') return !!parsed;
            return parsed;
        } catch {
            return meta.default;
        }
    };

    const writeSetting = (settingKey, value) => {
        const meta = ESTATS_SETTINGS[settingKey];
        if (!meta) return;
        const normalized = meta.type === 'boolean' ? !!value : value;
        const storageKey = getStorageKey(settingKey);
        try {
            writeCanonicalValue(storageKey, normalized);
            const serialized = JSON.stringify(normalized);
            if (localStorage.getItem(storageKey) !== serialized) {
                localStorage.setItem(storageKey, serialized);
            }
        } catch { /* ignore storage failures */ }
    };

    const applySetting = (settingKey, value) => {
        const meta = ESTATS_SETTINGS[settingKey];
        if (!meta) return;
        writeSetting(settingKey, meta.type === 'boolean' ? !!value : value);
        applySettingSideEffects(settingKey);
    };

    const isFeatureEnabled = (settingKey) => readSetting(settingKey) !== false;

    const applySettingSideEffects = (settingKey) => {
        switch (settingKey) {
            case 'ENABLE_SESSION_COUNTER':
                handleSessionCounter();
                break;
            case 'ENABLE_HIDDEN_STATS':
                cleanupHiddenStats();
                if (readSetting(settingKey)) handleHiddenStats();
                break;
            case 'ENABLE_ENHANCED_STATS_PAGE':
                cleanupEnhancedStatsPage();
                if (readSetting(settingKey)) void handleEnhancedStatsPage();
                break;
            case 'ENABLE_ENHANCED_RACELOG':
                cleanupEnhancedRacelog();
                if (readSetting(settingKey)) void handleEnhancedRacelog();
                break;
            case 'ENABLE_LEAGUE_CALCULATOR':
                cleanupLeagueCalculator();
                if (readSetting(settingKey)) handleLeagueCalculator();
                break;
            case 'ENABLE_TOURNAMENT_WINS':
                cleanupTournamentWins();
                if (readSetting(settingKey)) void handleTournamentWins();
                break;
            case 'ENABLE_GARAGE_CAR_COUNT':
                cleanupGarageCarCount();
                if (readSetting(settingKey)) handleGarageCarCount();
                break;
            default:
                break;
        }
    };

    const applyAllLiveSettingSideEffects = () => {
        handleSessionCounter();

        cleanupHiddenStats();
        if (isFeatureEnabled('ENABLE_HIDDEN_STATS')) handleHiddenStats();

        cleanupEnhancedStatsPage();
        if (isFeatureEnabled('ENABLE_ENHANCED_STATS_PAGE')) void handleEnhancedStatsPage();

        cleanupEnhancedRacelog();
        if (isFeatureEnabled('ENABLE_ENHANCED_RACELOG')) void handleEnhancedRacelog();

        cleanupLeagueCalculator();
        if (isFeatureEnabled('ENABLE_LEAGUE_CALCULATOR')) handleLeagueCalculator();

        cleanupTournamentWins();
        if (isFeatureEnabled('ENABLE_TOURNAMENT_WINS')) void handleTournamentWins();

        cleanupGarageCarCount();
        if (isFeatureEnabled('ENABLE_GARAGE_CAR_COUNT')) handleGarageCarCount();
    };

    // ─── Manifest Registration ────────────────────────────────────────────────────
    const registerManifest = () => {
        try {
            const manifest = {
                id: NTCFG_MANIFEST_ID,
                name: 'Stats',
                version: NTCFG_BRIDGE_VERSION,
                scriptVersion: typeof GM_info !== 'undefined' ? GM_info.script.version : '',
                storageVersion: SETTINGS_STORAGE_VERSION,
                supportsGlobalReset: true,
                description: 'Race and racer data analytics: session tracking, hidden stats, XP breakdown, WPM curves, enhanced racelog, league calculators.',
                icon: '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>',
                sections: [
                    { id: 'racer', title: 'Racer Profile', subtitle: 'Session tracking, hidden stats, and garage info.', resetButton: 'Reset Racer Profile to Defaults' },
                    { id: 'race', title: 'Race', subtitle: 'In-race analytics and post-race breakdowns.', resetButton: 'Reset Race to Defaults' },
                    { id: 'stats', title: 'Stats Page', subtitle: 'Enhanced overview, racelog, and hidden account data.', resetButton: 'Reset Stats Page to Defaults' },
                    { id: 'leagues', title: 'Leagues', subtitle: 'League calculators and tournament stats.', resetButton: 'Reset Leagues to Defaults' }
                ],
                settings: ESTATS_SETTINGS
            };
            const serialized = JSON.stringify(manifest);
            if (localStorage.getItem(NTCFG_MANIFEST_KEY) !== serialized) {
                localStorage.setItem(NTCFG_MANIFEST_KEY, serialized);
            }
        } catch { /* ignore */ }
    };

    const syncAllSettings = () => {
        Object.keys(ESTATS_SETTINGS).forEach((key) => {
            writeSetting(key, readSetting(key));
        });
    };

    const ensureEnhancedStatsStorageMigration = () => {
        const currentVersion = readStorageMetaNumber(STORAGE_VERSION_KEY, 0);
        const migratedAt = readStorageMetaNumber(STORAGE_MIGRATED_AT_KEY, 0);
        const now = Date.now();

        if (currentVersion < SETTINGS_STORAGE_VERSION) {
            syncAllSettings();
            writeCanonicalValue(STORAGE_VERSION_KEY, SETTINGS_STORAGE_VERSION);
            if (!migratedAt) {
                writeCanonicalValue(STORAGE_MIGRATED_AT_KEY, now);
                writeCanonicalValue(STORAGE_CLEANUP_AFTER_KEY, now + LEGACY_CLEANUP_GRACE_MS);
            }
        }
    };

    const resetEnhancedStatsSettingsToDefaults = () => {
        Object.entries(ESTATS_SETTINGS).forEach(([settingKey, meta]) => {
            if (meta.type === 'note' || meta.type === 'action') return;
            writeSetting(settingKey, meta.default);
        });
    };

    // Listen for mod menu changes (same tab)
    document.addEventListener('ntcfg:change', (event) => {
        if (event?.detail?.script !== NTCFG_MANIFEST_ID) return;
        applySetting(event.detail.key, event.detail.value);
    });

    document.addEventListener('ntcfg:action', (event) => {
        const detail = event?.detail || {};
        if (detail.script !== '*') return;
        if (detail.key !== 'clear-settings' || detail.scope !== 'prefs+caches') return;
        try {
            resetEnhancedStatsSettingsToDefaults();
            writeCanonicalValue(STORAGE_VERSION_KEY, SETTINGS_STORAGE_VERSION);
            writeCanonicalValue(STORAGE_MIGRATED_AT_KEY, Date.now());
            writeCanonicalValue(STORAGE_CLEANUP_AFTER_KEY, Date.now() + LEGACY_CLEANUP_GRACE_MS);
            registerManifest();
            syncAllSettings();
            applyAllLiveSettingSideEffects();
            document.dispatchEvent(new CustomEvent('ntcfg:manifest-updated', {
                detail: { script: NTCFG_MANIFEST_ID }
            }));
            dispatchActionResult(detail.requestId, 'success');
        } catch (error) {
            dispatchActionResult(detail.requestId, 'error', error?.message || String(error));
        }
    });

    // Listen for cross-tab changes
    window.addEventListener('storage', (event) => {
        const key = String(event?.key || '');
        if (!key.startsWith(NTCFG_VALUE_PREFIX) || event.newValue == null) return;
        const settingKey = key.slice(NTCFG_VALUE_PREFIX.length);
        if (!ESTATS_SETTINGS[settingKey]) return;
        try { applySetting(settingKey, JSON.parse(event.newValue)); } catch { /* ignore */ }
    });

    ensureEnhancedStatsStorageMigration();
    registerManifest();
    syncAllSettings();

    // Alive signal — write BEFORE dispatching manifest-updated so the mod menu sees this script as alive when it re-renders.
    try { localStorage.setItem('ntcfg:alive:' + NTCFG_MANIFEST_ID, String(Date.now())); } catch { /* ignore */ }

    try {
        document.dispatchEvent(new CustomEvent('ntcfg:manifest-updated', {
            detail: { script: NTCFG_MANIFEST_ID }
        }));
    } catch { /* ignore */ }

    // ─── Shared Utilities ─────────────────────────────────────────────────────────

    /** Traverse React fiber tree to find component instance. */
    const findReact = (dom, traverseUp = 0) => {
        if (!dom) return null;
        const key = Object.keys(dom).find((k) => k.startsWith('__reactFiber$'));
        const domFiber = dom[key];
        if (domFiber == null) return null;
        const getCompFiber = (fiber) => {
            let parentFiber = fiber?.return;
            while (typeof parentFiber?.type === 'string') {
                parentFiber = parentFiber?.return;
            }
            return parentFiber;
        };
        let compFiber = getCompFiber(domFiber);
        for (let i = 0; i < traverseUp && compFiber; i++) {
            compFiber = getCompFiber(compFiber);
        }
        return compFiber?.stateNode;
    };

    /** Get React fiber from a DOM node. */
    const getReactFiber = (dom) => {
        if (!dom) return null;
        const key = Object.keys(dom).find((k) =>
            k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')
        );
        return key ? dom[key] : null;
    };

    /** Walk up fiber tree looking for props containing a given key. */
    const findReactProp = (dom, propName, maxSteps = 30) => {
        let fiber = getReactFiber(dom);
        let steps = 0;
        while (fiber && steps++ < maxSteps) {
            const props = fiber.memoizedProps || fiber.pendingProps || null;
            if (props && propName in props) return props[propName];
            const stateNode = fiber.stateNode;
            if (stateNode?.props && propName in stateNode.props) return stateNode.props[propName];
            fiber = fiber.return;
        }
        return undefined;
    };

    /** Escape HTML entities for safe injection. */
    const escapeHtml = (value) => {
        return String(value ?? '')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    };

    /** Normalize a pathname by stripping trailing slashes. */
    const normalizePath = (pathname) => {
        if (!pathname || pathname === '/') return '/';
        return pathname.replace(/\/+$/, '') || '/';
    };

    /** Get current user data from persist:nt localStorage. */
    const getCurrentUser = () => {
        try {
            const persist = JSON.parse(localStorage.getItem('persist:nt'));
            return JSON.parse(persist.user);
        } catch {
            return null;
        }
    };

    /** Get player auth token for API calls. */
    const getAuthToken = () => {
        return localStorage.getItem('player_token');
    };

    /** Authenticated fetch helper. */
    const apiFetch = async (endpoint) => {
        const token = getAuthToken();
        if (!token) throw new Error('No auth token');
        const res = await fetch(endpoint, {
            headers: { 'Authorization': `Bearer ${token}` }
        });
        if (!res.ok) throw new Error(`API ${res.status}`);
        return res.json();
    };

    // ─── SPA Navigation Hooks ─────────────────────────────────────────────────────
    const originalPushState = history.pushState;
    history.pushState = function () {
        const result = originalPushState.apply(this, arguments);
        onRouteChange();
        return result;
    };

    const originalReplaceState = history.replaceState;
    history.replaceState = function () {
        const result = originalReplaceState.apply(this, arguments);
        onRouteChange();
        return result;
    };

    window.addEventListener('popstate', onRouteChange);

    // ─── Race Page Cleanup Registry ───────────────────────────────────────────────
    const cleanupFns = [];
    const registerCleanup = (fn) => cleanupFns.push(fn);
    const runCleanup = () => {
        while (cleanupFns.length) {
            try { cleanupFns.pop()(); } catch (e) {
                console.error(LOG_PREFIX, 'Cleanup error:', e);
            }
        }
    };

    const rememberOriginalStyle = (element) => {
        if (!element || element.hasAttribute('data-estats-original-style')) return;
        const original = element.getAttribute('style');
        element.setAttribute('data-estats-original-style', original == null ? '__none__' : original);
    };

    const restoreOriginalStyle = (element) => {
        if (!element || !element.hasAttribute('data-estats-original-style')) return;
        const original = element.getAttribute('data-estats-original-style');
        if (original === '__none__') {
            element.removeAttribute('style');
        } else {
            element.setAttribute('style', original);
        }
        element.removeAttribute('data-estats-original-style');
    };

    const rememberOriginalText = (element) => {
        if (!element || element.hasAttribute('data-estats-original-text')) return;
        element.setAttribute('data-estats-original-text', element.textContent ?? '');
    };

    const restoreOriginalText = (element) => {
        if (!element || !element.hasAttribute('data-estats-original-text')) return;
        element.textContent = element.getAttribute('data-estats-original-text') || '';
        element.removeAttribute('data-estats-original-text');
    };

    const rememberOriginalHtml = (element, attrName = 'data-estats-original-html') => {
        if (!element || element.hasAttribute(attrName)) return;
        element.setAttribute(attrName, element.innerHTML);
    };

    const restoreOriginalHtml = (element, attrName = 'data-estats-original-html') => {
        if (!element || !element.hasAttribute(attrName)) return;
        element.innerHTML = element.getAttribute(attrName) || '';
        element.removeAttribute(attrName);
    };

    function onRouteChange() {
        runCleanup();
        raceHooked = false;
        wpmSamples = {};
    }

    // ─── Observer Manager Integration ─────────────────────────────────────────────
    function initObserverManager() {
        const existing = window.NTObserverManager || {};
        if (existing.version !== '1.0.0' && existing.observer && typeof existing.observer.disconnect === 'function') {
            try { existing.observer.disconnect(); } catch (e) { }
            existing.observer = null;
        }
        if (existing.debounceTimer) {
            clearTimeout(existing.debounceTimer);
            existing.debounceTimer = null;
        }
        existing.callbacks = existing.callbacks || {};
        existing.version = '1.0.0';
        existing.register = function (scriptName, callback) {
            this.callbacks[scriptName] = callback;
            if (!this.observer) {
                this.observer = new MutationObserver(() => {
                    clearTimeout(this.debounceTimer);
                    this.debounceTimer = setTimeout(() => {
                        Object.values(this.callbacks).forEach((cb) => {
                            try { cb(); } catch (e) { console.error('[Observer Error]', e); }
                        });
                    }, 250);
                });
                this.observer.observe(document.body, {
                    childList: true,
                    subtree: true
                });
            }
            try {
                callback();
            } catch (e) {
                console.error('[Observer Error]', e);
            }
        };
        window.NTObserverManager = existing;
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 1: Session Race Counter (Global)
    // ─────────────────────────────────────────────────────────────────────────────
    const SESSION_COUNTER_ATTR = 'data-estats-session-counter';
    const SESSION_STORAGE_KEY = 'estats:session';

    function getSessionData() {
        try {
            const raw = localStorage.getItem(SESSION_STORAGE_KEY);
            if (!raw) return null;
            return JSON.parse(raw);
        } catch { return null; }
    }

    function setSessionData(data) {
        try {
            localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(data));
        } catch { /* ignore */ }
    }

    function handleSessionCounter() {
        if (!isFeatureEnabled('ENABLE_SESSION_COUNTER')) {
            // Remove counter if feature was disabled
            const existing = document.querySelector(`[${SESSION_COUNTER_ATTR}]`);
            if (existing) existing.remove();
            return;
        }

        const user = getCurrentUser();
        if (!user) return;

        const ntSessionRaces = user.sessionRaces || 0;
        const inactivityMs = (readSetting('SESSION_INACTIVITY_MINUTES') || 30) * 60 * 1000;
        const now = Date.now();

        let session = getSessionData();
        if (!session) {
            session = { count: ntSessionRaces, lastNtCount: ntSessionRaces, lastActive: now };
        }

        // Reset if NT's session dropped (NT reset its session)
        if (ntSessionRaces < session.lastNtCount) {
            session = { count: ntSessionRaces, lastNtCount: ntSessionRaces, lastActive: now };
        }

        // Reset if inactivity timeout exceeded
        if (now - session.lastActive > inactivityMs) {
            session = { count: ntSessionRaces, lastNtCount: ntSessionRaces, lastActive: now };
        }

        // Detect new races: if NT count increased since our last check
        const delta = ntSessionRaces - session.lastNtCount;
        if (delta > 0) {
            session.count += delta;
            session.lastNtCount = ntSessionRaces;
            session.lastActive = now;
        }

        setSessionData(session);

        // Inject counter into the profile dropdown trigger area
        const dropdownTrigger = document.querySelector('.dropdown-trigger');
        if (!dropdownTrigger) return;

        let counter = dropdownTrigger.querySelector(`[${SESSION_COUNTER_ATTR}]`);
        if (!counter) {
            counter = document.createElement('span');
            counter.setAttribute(SESSION_COUNTER_ATTR, '');
            counter.style.cssText = 'display:block;font-size:11px;font-weight:600;color:#a6aac1;margin-top:4px;';
            dropdownTrigger.appendChild(counter);
        }
        counter.textContent = 'Current Session: ' + session.count + (session.count === 1 ? ' Race' : ' Races');
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 2: Hidden Racer Stats (/racer/*)
    // ─────────────────────────────────────────────────────────────────────────────
    const HIDDEN_STATS_ATTR = 'data-estats-hidden-stats';

    function cleanupHiddenStats() {
        const levelContainer = document.querySelector('.profile--grid--level');
        if (levelContainer && levelContainer.hasAttribute(HIDDEN_STATS_ATTR)) {
            restoreOriginalHtml(levelContainer, 'data-estats-hidden-level-html');
            restoreOriginalStyle(levelContainer);
            levelContainer.removeAttribute(HIDDEN_STATS_ATTR);
        }

        const playerStats = document.querySelector('.profile-playerStats');
        if (playerStats && playerStats.hasAttribute(HIDDEN_STATS_ATTR)) {
            restoreOriginalHtml(playerStats, 'data-estats-hidden-player-html');
            restoreOriginalStyle(playerStats);
            playerStats.removeAttribute(HIDDEN_STATS_ATTR);
        }

        document.querySelectorAll('[data-estats-carcount]').forEach((el) => el.remove());
    }

    /** Format seconds into a human-readable duration. */
    const formatDuration = (totalSeconds) => {
        if (!totalSeconds || totalSeconds <= 0) return '—';
        const days = Math.floor(totalSeconds / 86400);
        const hours = Math.floor((totalSeconds % 86400) / 3600);
        const mins = Math.floor((totalSeconds % 3600) / 60);
        const secs = Math.floor(totalSeconds % 60);
        if (days > 0) return `${days}d ${hours}h ${mins}m`;
        if (hours > 0) return `${hours}h ${mins}m ${secs}s`;
        if (mins > 0) return `${mins}m ${secs}s`;
        return `${secs}s`;
    };

    /** Format a unix timestamp to a readable date. */
    const formatTimestamp = (ts) => {
        if (!ts) return '—';
        return new Date(ts * 1000).toLocaleDateString('en-US', {
            year: 'numeric', month: 'short', day: 'numeric'
        });
    };

    /** Build a stat well HTML block. */
    const statWell = (label, value, color = '#d8dcf2') => `
        <div class="well tac" style="padding:10px 8px;">
            <div class="tsxs ttu tc-ts" style="margin-bottom:4px;">${escapeHtml(label)}</div>
            <div class="tss" style="font-weight:700;color:${color};">${escapeHtml(String(value))}</div>
        </div>`;

    /** Build a section header. */
    const sectionHeader = (title) =>
        `<h5 class="tsxs ttu" style="margin:16px 0 8px;color:#99a4c5;letter-spacing:0.5px;">${escapeHtml(title)}</h5>`;

    /**
     * Match Nitro's season infinity-tier logic:
     * infinity starts after the final finite reward level (totalRewards + 1).
     */
    function formatSeasonLevel(level) {
        if (level == null || level <= 0) return null;
        try {
            const season = pageWindow.NTGLOBALS?.ACTIVE_SEASONS?.[0];
            const totalRewards = Number(season?.totalRewards);
            if (Number.isFinite(totalRewards) && level > (totalRewards + 1)) {
                return `∞${level - totalRewards - 1}`;
            }
        } catch { /* ignore */ }
        return String(level);
    }

    function getPlayerXpThreshold(level) {
        if (!Number.isFinite(level) || level <= 0) return 0;
        const plCfg = pageWindow.NTGLOBALS?.PLAYER_LEVELS;
        const plBase = (plCfg && Number.isFinite(plCfg.basePoints)) ? plCfg.basePoints : 500;
        const plMult = (plCfg && Number.isFinite(plCfg.multiple)) ? plCfg.multiple : 2;
        return Math.floor((plBase * plMult * Math.pow(level, 2)) + (plBase * level));
    }

    function handleHiddenStats() {
        if (!isFeatureEnabled('ENABLE_HIDDEN_STATS')) return;

        const path = normalizePath(window.location.pathname);
        if (!path.startsWith('/racer/')) return;

        if (document.querySelector(`[${HIDDEN_STATS_ATTR}]`)) return;

        const info = pageWindow.NTGLOBALS?.RACER_INFO;
        if (!info || !info.username) return;

        // Find the stats section on the profile page
        const profileGrid = document.querySelector('.profile--grid--level');
        if (!profileGrid) return;

        const statsParent = profileGrid.closest('.card') || profileGrid.closest('section') || profileGrid.parentElement;
        if (!statsParent) return;

        // ── Inject Season Level into .profile--grid--level ──
        const levelContainer = document.querySelector('.profile--grid--level');
        if (levelContainer && !levelContainer.querySelector('[data-estats-season-level]')) {
            const seasonLevel = info.level;
            const seasonLevelStr = formatSeasonLevel(seasonLevel);
            if (seasonLevelStr) {
                rememberOriginalHtml(levelContainer, 'data-estats-hidden-level-html');
                rememberOriginalStyle(levelContainer);
                levelContainer.setAttribute(HIDDEN_STATS_ATTR, '');
                levelContainer.style.cssText = 'display:flex;align-items:center;justify-content:flex-end;padding:12px 16px;';
                const levelInner = document.createElement('div');
                levelInner.setAttribute('data-estats-season-level', '');
                levelInner.style.textAlign = 'right';
                levelInner.innerHTML = `
                    <div class="tsxl twb" style="line-height:1;"><span class="tc-i">LVL</span> <span class="tc-fuel">${seasonLevelStr}</span></div>
                `;
                levelContainer.appendChild(levelInner);
            }
        }

        // ── Inject extra stats into .profile-playerStats ──
        const playerStats = document.querySelector('.profile-playerStats');
        if (playerStats && !playerStats.querySelector('[data-estats-inline]')) {
            const hasExtra = info.longestSession != null || info.experience != null || info.nitrosUsed != null || info.nitros != null;
            if (hasExtra) {
                rememberOriginalHtml(playerStats, 'data-estats-hidden-player-html');
                rememberOriginalStyle(playerStats);
                playerStats.setAttribute(HIDDEN_STATS_ATTR, '');
                playerStats.style.cssText = 'display:inline-grid;grid-template-columns:repeat(3,auto);gap:0 32px;';

                // Wrap existing native stats in column 1
                const nativeCol = document.createElement('div');
                nativeCol.setAttribute('data-estats-inline', '');
                while (playerStats.firstChild) {
                    nativeCol.appendChild(playerStats.firstChild);
                }
                // Force native rows to simple flex layout (split--inline spreads them)
                nativeCol.querySelectorAll('.split').forEach(el => {
                    el.style.cssText = 'display:flex !important;align-items:baseline;gap:6px;';
                    el.className = '';
                });
                nativeCol.querySelectorAll('.split-cell').forEach(el => {
                    el.style.cssText = 'flex:none;';
                    el.className = '';
                });
                playerStats.appendChild(nativeCol);

                const makeRow = (label, value) => `<div style="display:flex;align-items:baseline;gap:6px;"><div class="tsxxs ttu">${label}</div><div class="tss">${value}</div></div>`;

                // Column 2: Longest Session + Season XP
                const col2 = document.createElement('div');
                if (info.longestSession != null) col2.innerHTML += makeRow('Longest Session', info.longestSession.toLocaleString() + ' races');
                if (info.experience != null) col2.innerHTML += makeRow('Season XP', info.experience.toLocaleString());
                if (col2.children.length) playerStats.appendChild(col2);

                // Column 3: Nitros Used + Nitros Owned
                const col3 = document.createElement('div');
                if (info.nitrosUsed != null) col3.innerHTML += makeRow('Nitros Used', info.nitrosUsed.toLocaleString());
                if (info.nitros != null) col3.innerHTML += makeRow('Nitros Owned', info.nitros.toLocaleString());
                if (col3.children.length) playerStats.appendChild(col3);
            }
        }

        // ── Inject total cars count next to "Cars" heading ──
        const carsHeading = document.querySelector('.card-cap h1');
        if (carsHeading && carsHeading.textContent.trim() === 'Cars' && !carsHeading.querySelector('[data-estats-carcount]')) {
            const totalCars = info.cars ? info.cars.length :
                (info.garage ? new Set(info.garage.filter(id => id && id !== '')).size : 0);
            if (totalCars) {
                const countSpan = document.createElement('span');
                countSpan.setAttribute('data-estats-carcount', '');
                countSpan.className = 'tbs';
                countSpan.style.cssText = 'margin-left:8px;';
                countSpan.textContent = `| ${totalCars}`;
                carsHeading.appendChild(countSpan);
            }
        }

    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 3: Race Result Enhancements (/race)
    // ─────────────────────────────────────────────────────────────────────────────
    const RACE_ENHANCE_ATTR = 'data-estats-race-enhanced';
    let raceHooked = false;

    function applyRaceResultStatsListLayout(cell, statsList) {
        if (!cell || !statsList) return;

        const splitReverse = cell.querySelector('.split.split--reverse');
        if (!splitReverse) return;

        const nameWrapper = cell.querySelector('.raceResults-playerName');
        const titleCell = splitReverse.querySelector('.split-cell > .tsxs.tc-fuel');
        if (titleCell && nameWrapper && titleCell.parentElement !== nameWrapper) {
            titleCell.style.cssText = 'margin-top:2px;';
            nameWrapper.appendChild(titleCell);
        }

        splitReverse.style.cssText = 'display:block;';
        const statsCell = statsList.closest('.split-cell');
        if (statsCell) {
            statsCell.style.cssText = 'width:100%;max-width:100%;';
        }
        statsList.style.cssText = 'flex-wrap:wrap;margin-top:2px;';
    }

    function handleRaceEnhancements() {
        if (!isFeatureEnabled('ENABLE_RACE_ENHANCEMENTS')) return;

        const path = normalizePath(window.location.pathname);
        if (path !== '/race' && !path.startsWith('/race/')) return;

        const raceContainer = document.getElementById('raceContainer');
        if (!raceContainer) return;

        const results = raceContainer.querySelector('.race-results');
        if (!results) return;
        if (results.hasAttribute(RACE_ENHANCE_ATTR)) return;

        const raceObj = findReact(raceContainer);
        if (!raceObj) return;

        const racers = raceObj.state?.racers;
        const user = raceObj.props?.user;
        if (!racers || !user) return;

        results.setAttribute(RACE_ENHANCE_ATTR, '');

        // Calculate season points for each racer
        const racerStats = {};
        racers.forEach(racer => {
            const progress = racer.progress || {};
            const typed = progress.typed || 0;
            const skipped = progress.skipped || 0;
            const errors = progress.errors || 0;
            const startStamp = progress.startStamp || 0;
            const completeStamp = progress.completeStamp || 0;

            if (completeStamp <= 0 || startStamp <= 0) {
                racerStats[racer.userID] = { points: 0, skipped, errors };
                return;
            }

            const timeSeconds = (completeStamp - startStamp) / 1000;
            const wpm = timeSeconds > 0 ? ((typed - skipped) / 5) / (timeSeconds / 60) : 0;
            const totalTyped = typed - skipped;
            const accuracy = totalTyped > 0 ? (totalTyped - errors) / totalTyped : 0;
            const points = Math.max(0, Math.round(1 * accuracy * (100 + wpm / 2)));

            racerStats[racer.userID] = { points, skipped, errors };
        });

        // Inject directly into each racer's existing result row
        // NT structure: .gridTable-cell > .split.split--flag > .split-cell > .list.list--inline
        const resultCells = results.querySelectorAll('.gridTable-cell');

        resultCells.forEach(cell => {
            // Find the player name to match with racer data
            const nameContainer = cell.querySelector('.player-name--container');
            if (!nameContainer) return;

            // Find the existing stats list (WPM, Acc, secs)
            const statsList = cell.querySelector('.list.list--inline.list--flag');
            if (!statsList) return;
            if (statsList.hasAttribute('data-estats-injected')) return;
            statsList.setAttribute('data-estats-injected', '');

            // Match this cell to a racer by display name
            const displayName = nameContainer.getAttribute('title') || '';
            const racer = racers.find(r => {
                const rName = r.profile?.displayName || r.profile?.username || '';
                return rName === displayName;
            });

            if (!racer || !racerStats[racer.userID]) return;
            const stats = racerStats[racer.userID];

            // Inject season points into the existing list
            const ptsItem = document.createElement('div');
            ptsItem.className = 'list-item';
            ptsItem.innerHTML = `${stats.points} <span class="tc-ts">Points</span>`;
            statsList.appendChild(ptsItem);

            // Inject skipped count if > 0
            if (stats.skipped > 0) {
                const skipItem = document.createElement('div');
                skipItem.className = 'list-item';
                skipItem.innerHTML = `${stats.skipped} <span class="tc-ts">Skipped</span>`;
                statsList.appendChild(skipItem);
            }

            // Inject error count if > 0
            if (stats.errors > 0) {
                const errItem = document.createElement('div');
                errItem.className = 'list-item';
                errItem.innerHTML = `${stats.errors} <span class="tc-ts">Errors</span>`;
                statsList.appendChild(errItem);
            }

            applyRaceResultStatsListLayout(cell, statsList);
        });
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 4: Post-Race WPM Curve (/race)
    // ─────────────────────────────────────────────────────────────────────────────
    const WPM_CURVE_ATTR = 'data-estats-wpm-curve';
    let wpmSamples = {}; // { userID: [{time, typed, skipped}] }

    function getNearestCurvePoint(points, targetElapsed) {
        if (!Array.isArray(points) || points.length === 0 || !Number.isFinite(targetElapsed)) return null;
        let nearest = points[0];
        let nearestDiff = Math.abs(points[0].elapsed - targetElapsed);
        for (let i = 1; i < points.length; i++) {
            const diff = Math.abs(points[i].elapsed - targetElapsed);
            if (diff < nearestDiff) {
                nearest = points[i];
                nearestDiff = diff;
            }
        }
        return nearest;
    }

    function hookRaceServer() {
        if (raceHooked) return;

        const path = normalizePath(window.location.pathname);
        if (path !== '/race' && !path.startsWith('/race/')) return;

        const raceContainer = document.getElementById('raceContainer');
        if (!raceContainer) return;

        const raceObj = findReact(raceContainer);
        if (!raceObj?.server) return;

        const server = raceObj.server;
        const user = raceObj.props?.user;
        if (!user) return;

        raceHooked = true;
        wpmSamples = {};

        // Hook into update events for WPM sampling
        const onUpdate = (e) => {
            if (!e?.racers) return;
            const now = Date.now();

            e.racers.forEach(racer => {
                if (!racer.userID) return;
                if (!wpmSamples[racer.userID]) {
                    wpmSamples[racer.userID] = [];
                }
                wpmSamples[racer.userID].push({
                    time: now,
                    typed: racer.progress?.typed || 0,
                    skipped: racer.progress?.skipped || 0,
                    errors: racer.progress?.errors || 0,
                    complete: racer.progress?.completeStamp > 0
                });
            });

        };

        server.on('update', onUpdate);

        // Register cleanup to remove listener on navigation
        registerCleanup(() => {
            try { server.off('update', onUpdate); } catch { /* ignore */ }
        });
    }

    function handleWPMCurve() {
        if (!isFeatureEnabled('ENABLE_WPM_CURVE')) return;

        const path = normalizePath(window.location.pathname);
        if (path !== '/race' && !path.startsWith('/race/')) return;

        // Try to hook the race server if not already done
        hookRaceServer();

        const raceContainer = document.getElementById('raceContainer');
        if (!raceContainer) return;

        const results = raceContainer.querySelector('.race-results');
        if (!results) return;
        if (document.querySelector(`[${WPM_CURVE_ATTR}]`)) return;

        // Need at least some samples to draw
        const sampleKeys = Object.keys(wpmSamples);
        if (sampleKeys.length === 0) return;

        // Check all racers have completed
        const raceObj = findReact(raceContainer);
        const user = raceObj?.props?.user;

        // Build WPM data series for each racer
        const series = [];
        const MY_RACE_GRAPH_COLOR = '#f3a81b';
        const OPPONENT_GRAPH_COLORS = ['#d62f3a', '#4ade80', '#1c99f4', '#a855f7', '#f97316', '#14b8a6', '#f43f5e', '#94a3b8'];
        let opponentColorIndex = 0;

        sampleKeys.forEach((uid, idx) => {
            const samples = wpmSamples[uid];
            if (samples.length < 2) return;

            const startTime = samples[0].time;
            const dataPoints = [];
            const nitroMarkers = []; // timestamps when nitros were used
            const accuracyPoints = []; // running accuracy over time

            for (let i = 1; i < samples.length; i++) {
                const prev = samples[i - 1];
                const curr = samples[i];
                const dt = (curr.time - prev.time) / 1000;
                if (dt <= 0) continue;

                const charsDelta = (curr.typed - curr.skipped) - (prev.typed - prev.skipped);
                if (charsDelta < 0) continue;

                const wpm = (charsDelta / 5) / (dt / 60);
                const elapsed = (curr.time - startTime) / 1000;

                dataPoints.push({ elapsed, wpm: Math.round(wpm) });

                // Detect nitro usage (skipped increased)
                if (curr.skipped > prev.skipped) {
                    nitroMarkers.push(elapsed);
                }

                // Running accuracy: (typed - skipped - errors) / (typed - skipped)
                const netTyped = curr.typed - curr.skipped;
                if (netTyped > 0) {
                    const acc = Math.max(0, (netTyped - curr.errors) / netTyped) * 100;
                    accuracyPoints.push({ elapsed, accuracy: Math.round(acc * 100) / 100 });
                }
            }

            if (dataPoints.length < 2) return;

            // Smooth WPM with a moving average (window of 3)
            const trendWindow = 3;
            const smoothed = [];
            for (let i = 0; i < dataPoints.length; i++) {
                const start = Math.max(0, i - 1);
                const end = Math.min(dataPoints.length - 1, i + 1);
                let sum = 0;
                let count = 0;
                for (let j = start; j <= end; j++) {
                    sum += dataPoints[j].wpm;
                    count++;
                }
                smoothed.push({ elapsed: dataPoints[i].elapsed, wpm: Math.round(sum / count) });
            }

            const isMe = user && String(uid) === String(user.userID);
            const racer = raceObj?.state?.racers?.find(r => String(r.userID) === String(uid));
            const name = racer?.profile?.displayName || racer?.profile?.username || 'Racer';
            const lastSample = samples[samples.length - 1];
            const netTyped = lastSample.typed - lastSample.skipped;
            const finalAcc = netTyped > 0 ? Math.round(((netTyped - lastSample.errors) / netTyped) * 10000) / 100 : 0;
            const avgWpm = dataPoints.length > 0 ? Math.round(dataPoints.reduce((s, d) => s + d.wpm, 0) / dataPoints.length) : 0;
            const color = isMe
                ? MY_RACE_GRAPH_COLOR
                : OPPONENT_GRAPH_COLORS[(opponentColorIndex++) % OPPONENT_GRAPH_COLORS.length];

            series.push({
                uid,
                name,
                isMe,
                data: smoothed,
                rawData: dataPoints,
                nitroMarkers,
                accuracyPoints,
                finalAcc,
                avgWpm,
                totalNitros: nitroMarkers.length,
                trendWindow,
                color
            });
        });

        if (series.length === 0) return;

        // Add graph button directly left of the minimize button
        const minimizeBtn = results.querySelector('.raceResults-close.raceResults-close--minimizer');
        if (!minimizeBtn || minimizeBtn.parentNode.querySelector('[data-estats-graph-btn]')) return;

        // Make the parent a flex container so both buttons sit side by side
        const btnParent = minimizeBtn.parentNode;
        rememberOriginalStyle(btnParent);
        btnParent.style.display = 'flex';
        btnParent.style.alignItems = 'center';
        btnParent.style.gap = '8px';

        const graphBtn = document.createElement('button');
        graphBtn.setAttribute('data-estats-graph-btn', '');
        graphBtn.setAttribute(WPM_CURVE_ATTR, '');
        graphBtn.className = 'raceResults-close raceResults-close--minimizer';
        graphBtn.title = 'WPM Graph';
        graphBtn.innerHTML = `<span style="display:flex;align-items:center;gap:4px;"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
        </svg><span style="color:#fff;font-size:11px;font-weight:700;letter-spacing:0.5px;">Graph</span></span>`;
        // Match minimize button height, override absolute positioning
        const minBtnHeight = minimizeBtn.offsetHeight || minimizeBtn.getBoundingClientRect().height;
        graphBtn.style.position = 'relative';
        graphBtn.style.height = `${minBtnHeight}px`;
        graphBtn.style.display = 'flex';
        graphBtn.style.alignItems = 'center';
        graphBtn.style.justifyContent = 'center';
        rememberOriginalStyle(minimizeBtn);
        minimizeBtn.style.position = 'relative';
        btnParent.insertBefore(graphBtn, minimizeBtn);

        graphBtn.addEventListener('click', () => {
            // Toggle state
            const chartState = {
                showSpeed: true,
                showAccuracy: true,
                showNitros: true,
                hiddenRacers: new Set(),
                hoverMouse: null
            };

            // Create full-screen modal overlay
            const overlay = document.createElement('div');
            overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;display:flex;align-items:center;justify-content:center;';

            const modal = document.createElement('div');
            modal.style.cssText = 'background:#1d2030;border-radius:12px;padding:24px 32px;width:90vw;max-width:1200px;position:relative;border:1px solid rgba(255,255,255,0.1);';

            // Close button
            const closeBtn = document.createElement('button');
            closeBtn.style.cssText = 'position:absolute;top:12px;right:16px;background:none;border:none;color:#a6aac1;font-size:24px;cursor:pointer;line-height:1;';
            closeBtn.innerHTML = '&times;';
            modal.appendChild(closeBtn);

            // Title
            const title = document.createElement('h4');
            title.className = 'tss ttu tc-ts';
            title.style.cssText = 'margin:0 0 16px;';
            title.textContent = 'Race Performance Over Time';
            modal.appendChild(title);

            // Canvas
            const canvasFrame = document.createElement('div');
            canvasFrame.style.cssText = 'width:100%;max-width:100%;overflow:hidden;border-radius:8px;';
            const canvas = document.createElement('canvas');
            canvas.width = 1100;
            canvas.height = 400;
            canvas.style.cssText = 'display:block;width:100%;height:auto;max-width:100%;border-radius:8px;cursor:crosshair;';
            canvasFrame.appendChild(canvas);
            modal.appendChild(canvasFrame);

            const redraw = () => drawWPMChart(canvas, series, chartState);

            const syncHoverMouse = (event) => {
                const rect = canvas.getBoundingClientRect();
                if (!rect.width || !rect.height) return;
                chartState.hoverMouse = {
                    x: ((event.clientX - rect.left) / rect.width) * (canvas._origW || canvas.width),
                    y: ((event.clientY - rect.top) / rect.height) * (canvas._origH || canvas.height)
                };
                redraw();
            };
            canvas.addEventListener('mousemove', syncHoverMouse);
            canvas.addEventListener('mouseleave', () => {
                chartState.hoverMouse = null;
                redraw();
            });

            // Racer legend (clickable to toggle)
            const legend = document.createElement('div');
            legend.style.cssText = 'display:flex;flex-wrap:wrap;gap:10px;margin-top:16px;';
            series.forEach(s => {
                const item = document.createElement('div');
                item.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:12px;color:#a6aac1;background:rgba(255,255,255,0.04);padding:6px 12px;border-radius:6px;cursor:pointer;user-select:none;transition:opacity 0.2s;';
                item.innerHTML = `
                    <span class="estats-legend-dot" style="display:inline-block;width:10px;height:10px;border-radius:2px;background:${s.color};flex-shrink:0;"></span>
                    <span style="font-weight:600;color:#fff;">${escapeHtml(s.name)}${s.isMe ? ' (You)' : ''}</span>
                    <span>${s.avgWpm} <span style="opacity:0.6;">avg WPM</span></span>
                    <span>${s.finalAcc}% <span style="opacity:0.6;">Acc</span></span>
                    ${s.totalNitros > 0 ? `<span style="color:#1c99f4;">${s.totalNitros} <span style="opacity:0.7;">Nitros</span></span>` : ''}
                `;
                item.addEventListener('click', () => {
                    if (chartState.hiddenRacers.has(s.uid)) {
                        chartState.hiddenRacers.delete(s.uid);
                        item.style.opacity = '1';
                        item.querySelector('.estats-legend-dot').style.background = s.color;
                    } else {
                        chartState.hiddenRacers.add(s.uid);
                        item.style.opacity = '0.3';
                        item.querySelector('.estats-legend-dot').style.background = '#555';
                    }
                    redraw();
                });
                legend.appendChild(item);
            });
            modal.appendChild(legend);

            // Filter toggle buttons
            const filters = document.createElement('div');
            filters.style.cssText = 'display:flex;gap:8px;margin-top:12px;';

            const makeToggle = (label, color, key) => {
                const btn = document.createElement('button');
                btn.style.cssText = `background:${color};color:#fff;border:none;border-radius:4px;padding:5px 14px;font-size:11px;font-weight:700;cursor:pointer;letter-spacing:0.5px;text-transform:uppercase;opacity:1;transition:opacity 0.2s;`;
                btn.textContent = label;
                btn.addEventListener('click', () => {
                    chartState[key] = !chartState[key];
                    btn.style.opacity = chartState[key] ? '1' : '0.3';
                    redraw();
                });
                return btn;
            };

            filters.appendChild(makeToggle('Speed', '#f3a81b', 'showSpeed'));
            filters.appendChild(makeToggle('Accuracy', '#4ade80', 'showAccuracy'));
            filters.appendChild(makeToggle('Nitros', '#1c99f4', 'showNitros'));
            modal.appendChild(filters);

            // Chart key
            const chartKey = document.createElement('div');
            chartKey.style.cssText = 'display:flex;gap:20px;margin-top:10px;font-size:11px;color:rgba(255,255,255,0.4);align-items:center;';
            chartKey.innerHTML = `
                <span style="display:flex;align-items:center;gap:6px;"><span style="display:inline-block;width:20px;height:2px;background:#fff;"></span> Solid = WPM Trend</span>
                <span style="display:flex;align-items:center;gap:6px;"><span style="display:inline-block;width:20px;height:0;border-top:2px dashed rgba(255,255,255,0.8);"></span> Dashed = Accuracy (same racer color)</span>
                <span style="display:flex;align-items:center;gap:6px;"><span style="color:#1c99f4;">▼</span> <span style="display:inline-block;width:14px;height:0;border-top:1px dashed #1c99f4;"></span> = Nitro Used</span>
            `;
            modal.appendChild(chartKey);

            overlay.appendChild(modal);

            const closeOverlay = () => {
                overlay.remove();
                document.removeEventListener('keydown', onEsc);
            };

            // Close on overlay click (not modal)
            overlay.addEventListener('click', (e) => {
                if (e.target === overlay) closeOverlay();
            });

            // Close on Escape key
            const onEsc = (e) => { if (e.key === 'Escape') closeOverlay(); };
            document.addEventListener('keydown', onEsc);
            closeBtn.addEventListener('click', closeOverlay);

            document.body.appendChild(overlay);
            redraw();
        });
    }

    function drawWPMChart(canvas, series, chartState = {}) {
        const { showSpeed = true, showAccuracy = true, showNitros = true, hiddenRacers = new Set() } = chartState;
        const visibleSeries = series.filter(s => !hiddenRacers.has(s.uid));

        const ctx = canvas.getContext('2d');
        const dpr = window.devicePixelRatio || 1;
        if (!canvas._baseW) {
            canvas._baseW = canvas.width || 1100;
            canvas._baseH = canvas.height || 400;
            canvas._aspectRatio = canvas._baseW / canvas._baseH;
        }

        const parentWidth = canvas.parentElement?.clientWidth || canvas.getBoundingClientRect().width || canvas._baseW;
        const w = Math.max(320, Math.round(Math.min(canvas._baseW, parentWidth)));
        const h = Math.max(220, Math.round(w / (canvas._aspectRatio || (1100 / 400))));

        canvas._origW = w;
        canvas._origH = h;
        canvas.width = Math.round(w * dpr);
        canvas.height = Math.round(h * dpr);
        canvas.style.width = `${w}px`;
        canvas.style.height = `${h}px`;
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

        const padding = { top: 20, right: showAccuracy ? 50 : 20, bottom: 30, left: 45 };
        const chartW = w - padding.left - padding.right;
        const chartH = h - padding.top - padding.bottom;

        // Calculate data bounds from ALL series (keeps scale stable when toggling)
        let maxTime = 0;
        let maxWpm = 0;
        let minWpm = Infinity;
        let minAcc = Infinity;
        let maxAcc = -Infinity;

        series.forEach(s => {
            s.data.forEach(d => {
                if (d.elapsed > maxTime) maxTime = d.elapsed;
                if (d.wpm > maxWpm) maxWpm = d.wpm;
                if (d.wpm < minWpm) minWpm = d.wpm;
            });
            (s.accuracyPoints || []).forEach((d) => {
                if (!Number.isFinite(d?.accuracy)) return;
                minAcc = Math.min(minAcc, d.accuracy);
                maxAcc = Math.max(maxAcc, d.accuracy);
            });
        });

        if (!Number.isFinite(minWpm)) minWpm = 0;
        if (!Number.isFinite(maxWpm) || maxWpm <= 0) maxWpm = 150;
        minWpm = Math.max(0, Math.floor(minWpm / 10) * 10 - 10);
        maxWpm = Math.max(minWpm + 10, Math.ceil(maxWpm / 10) * 10 + 10);
        maxTime = Math.max(1, Math.ceil(maxTime));

        if (!Number.isFinite(minAcc)) minAcc = 90;
        if (!Number.isFinite(maxAcc) || maxAcc <= 0) maxAcc = 100;
        minAcc = Math.max(0, Math.floor(minAcc) - 1);
        maxAcc = Math.min(100, Math.ceil(maxAcc) + 1);
        if (maxAcc - minAcc < 4) {
            const midAcc = (maxAcc + minAcc) / 2;
            minAcc = Math.max(0, Math.floor(midAcc - 2));
            maxAcc = Math.min(100, Math.ceil(midAcc + 2));
        }
        if (maxAcc <= minAcc) maxAcc = Math.min(100, minAcc + 5);

        const scaleX = (elapsed) => padding.left + (elapsed / maxTime) * chartW;
        const scaleY = (wpm) => padding.top + chartH - ((wpm - minWpm) / ((maxWpm - minWpm) || 1)) * chartH;
        const scaleAccY = (acc) => padding.top + chartH - ((acc - minAcc) / ((maxAcc - minAcc) || 1)) * chartH;
        const invertScaleX = (x) => ((x - padding.left) / chartW) * maxTime;

        // Clear canvas
        ctx.clearRect(0, 0, w, h);

        // Grid lines
        ctx.strokeStyle = 'rgba(255,255,255,0.06)';
        ctx.lineWidth = 1;
        const yTicks = 5;
        for (let i = 0; i <= yTicks; i++) {
            const val = minWpm + (maxWpm - minWpm) * (i / yTicks);
            const y = scaleY(val);
            ctx.beginPath();
            ctx.moveTo(padding.left, y);
            ctx.lineTo(w - padding.right, y);
            ctx.stroke();

            // Y-axis labels (WPM - left)
            if (showSpeed) {
                ctx.fillStyle = 'rgba(255,255,255,0.4)';
                ctx.font = '10px Montserrat, sans-serif';
                ctx.textAlign = 'right';
                ctx.fillText(Math.round(val), padding.left - 6, y + 3);
            }
        }

        // Right Y-axis labels (Accuracy)
        if (showAccuracy) {
            for (let i = 0; i <= yTicks; i++) {
                const accVal = minAcc + ((maxAcc - minAcc) * i / yTicks);
                const y = scaleAccY(accVal);
                ctx.fillStyle = 'rgba(75,192,75,0.4)';
                ctx.font = '10px Montserrat, sans-serif';
                ctx.textAlign = 'left';
                ctx.fillText(accVal.toFixed(0) + '%', w - padding.right + 6, y + 3);
            }
        }

        // X-axis labels
        const xTicks = Math.min(6, Math.ceil(maxTime / 5));
        for (let i = 0; i <= xTicks; i++) {
            const val = (maxTime / xTicks) * i;
            const x = scaleX(val);
            ctx.fillStyle = 'rgba(255,255,255,0.4)';
            ctx.font = '10px Montserrat, sans-serif';
            ctx.textAlign = 'center';
            ctx.fillText(Math.round(val) + 's', x, h - padding.bottom + 16);
        }

        // Axis labels
        if (showSpeed) {
            ctx.fillStyle = 'rgba(255,255,255,0.3)';
            ctx.font = '10px Montserrat, sans-serif';
            ctx.textAlign = 'center';
            ctx.save();
            ctx.translate(12, padding.top + chartH / 2);
            ctx.rotate(-Math.PI / 2);
            ctx.fillText('WPM', 0, 0);
            ctx.restore();
        }

        if (showAccuracy) {
            ctx.fillStyle = 'rgba(75,192,75,0.3)';
            ctx.font = '10px Montserrat, sans-serif';
            ctx.textAlign = 'center';
            ctx.save();
            ctx.translate(w - 8, padding.top + chartH / 2);
            ctx.rotate(Math.PI / 2);
            ctx.fillText('Accuracy', 0, 0);
            ctx.restore();
        }

        // Draw nitro markers (behind lines)
        if (showNitros) {
            visibleSeries.forEach(s => {
                if (!s.nitroMarkers || s.nitroMarkers.length === 0) return;
                s.nitroMarkers.forEach(elapsed => {
                    const x = scaleX(elapsed);

                    // Dashed vertical line
                    ctx.strokeStyle = '#1c99f4';
                    ctx.lineWidth = 1;
                    ctx.globalAlpha = s.isMe ? 0.5 : 0.2;
                    ctx.setLineDash([4, 4]);
                    ctx.beginPath();
                    ctx.moveTo(x, padding.top);
                    ctx.lineTo(x, padding.top + chartH);
                    ctx.stroke();
                    ctx.setLineDash([]);

                    // Triangle marker at top
                    ctx.fillStyle = s.color;
                    ctx.globalAlpha = s.isMe ? 0.8 : 0.3;
                    ctx.beginPath();
                    ctx.moveTo(x, padding.top);
                    ctx.lineTo(x - 4, padding.top - 8);
                    ctx.lineTo(x + 4, padding.top - 8);
                    ctx.closePath();
                    ctx.fill();
                    ctx.globalAlpha = 1;
                });
            });
        }

        // Draw accuracy lines (dashed, all visible racers)
        if (showAccuracy) {
            const sortedAcc = [...visibleSeries].sort((a, b) => (a.isMe ? 1 : 0) - (b.isMe ? 1 : 0));
            sortedAcc.forEach(s => {
                if (!s.accuracyPoints || s.accuracyPoints.length < 2) return;

                ctx.strokeStyle = s.color;
                ctx.lineWidth = s.isMe ? 1.5 : 1;
                ctx.globalAlpha = s.isMe ? 0.4 : 0.15;
                ctx.lineJoin = 'round';
                ctx.lineCap = 'round';
                ctx.setLineDash([4, 4]);

                ctx.beginPath();
                s.accuracyPoints.forEach((d, i) => {
                    const x = scaleX(d.elapsed);
                    const y = scaleAccY(d.accuracy);
                    if (i === 0) ctx.moveTo(x, y);
                    else ctx.lineTo(x, y);
                });
                ctx.stroke();
                ctx.setLineDash([]);
                ctx.globalAlpha = 1;
            });
        }

        // Draw WPM lines (current user on top)
        if (showSpeed) {
            const sorted = [...visibleSeries].sort((a, b) => (a.isMe ? 1 : 0) - (b.isMe ? 1 : 0));

            sorted.forEach(s => {
                if (s.data.length < 2) return;

                ctx.strokeStyle = s.color;
                ctx.lineWidth = s.isMe ? 2.5 : 1.5;
                ctx.globalAlpha = s.isMe ? 1 : 0.5;
                ctx.lineJoin = 'round';
                ctx.lineCap = 'round';

                ctx.beginPath();
                s.data.forEach((d, i) => {
                    const x = scaleX(d.elapsed);
                    const y = scaleY(d.wpm);
                    if (i === 0) ctx.moveTo(x, y);
                    else ctx.lineTo(x, y);
                });
                ctx.stroke();
                ctx.globalAlpha = 1;
            });
        }

        const hoverMouse = chartState.hoverMouse;
        if (hoverMouse && chartW > 0 && chartH > 0 && visibleSeries.length > 0) {
            const withinChart =
                hoverMouse.x >= padding.left &&
                hoverMouse.x <= w - padding.right &&
                hoverMouse.y >= padding.top &&
                hoverMouse.y <= h - padding.bottom;

            if (withinChart) {
                const targetElapsed = Math.max(0, Math.min(maxTime, invertScaleX(hoverMouse.x)));
                let bestHover = null;

                const considerHoverPoint = (seriesEntry, point, x, y, color) => {
                    if (!point || !Number.isFinite(x) || !Number.isFinite(y)) return;
                    const dx = hoverMouse.x - x;
                    const dy = hoverMouse.y - y;
                    const distance = Math.sqrt((dx * dx) + (dy * dy));
                    if (!bestHover || distance < bestHover.distance) {
                        bestHover = { seriesEntry, point, x, y, color, distance };
                    }
                };

                if (showSpeed) {
                    visibleSeries.forEach((s) => {
                        if (!s.data?.length) return;
                        const point = getNearestCurvePoint(s.data, targetElapsed);
                        if (point) {
                            considerHoverPoint(s, point, scaleX(point.elapsed), scaleY(point.wpm), s.color);
                        }
                    });
                } else if (showAccuracy) {
                    visibleSeries.forEach((s) => {
                        if (!s.accuracyPoints?.length) return;
                        const point = getNearestCurvePoint(s.accuracyPoints, targetElapsed);
                        if (point) {
                            considerHoverPoint(s, point, scaleX(point.elapsed), scaleAccY(point.accuracy), s.color);
                        }
                    });
                }

                if (bestHover) {
                    const hoverSpeedPoint = showSpeed
                        ? getNearestCurvePoint(bestHover.seriesEntry.rawData || bestHover.seriesEntry.data, bestHover.point.elapsed)
                        : null;
                    const hoverTrendPoint = showSpeed
                        ? getNearestCurvePoint(bestHover.seriesEntry.data, bestHover.point.elapsed)
                        : null;
                    const hoverAccuracyPoint = showAccuracy
                        ? getNearestCurvePoint(bestHover.seriesEntry.accuracyPoints, bestHover.point.elapsed)
                        : null;

                    ctx.save();

                    ctx.strokeStyle = 'rgba(255,255,255,0.18)';
                    ctx.lineWidth = 1;
                    ctx.setLineDash([4, 4]);
                    ctx.beginPath();
                    ctx.moveTo(bestHover.x, padding.top);
                    ctx.lineTo(bestHover.x, padding.top + chartH);
                    ctx.stroke();
                    ctx.setLineDash([]);

                    ctx.beginPath();
                    ctx.arc(bestHover.x, bestHover.y, 6, 0, Math.PI * 2);
                    ctx.fillStyle = bestHover.color;
                    ctx.fill();
                    ctx.lineWidth = 2;
                    ctx.strokeStyle = '#ffffff';
                    ctx.stroke();

                    const tooltipLines = [
                        { text: `${bestHover.seriesEntry.name}${bestHover.seriesEntry.isMe ? ' (You)' : ''}`, color: '#ffffff' },
                        { text: `${bestHover.point.elapsed.toFixed(2)}s`, color: 'rgba(255,255,255,0.72)' },
                        ...(hoverSpeedPoint ? [{ text: `${hoverSpeedPoint.wpm} WPM`, color: '#f3a81b' }] : []),
                        ...(hoverAccuracyPoint ? [{ text: `${hoverAccuracyPoint.accuracy.toFixed(2)}% Acc`, color: '#4ade80' }] : []),
                        ...(hoverTrendPoint && hoverSpeedPoint && Math.abs(hoverTrendPoint.wpm - hoverSpeedPoint.wpm) >= 1
                            ? [{
                                text: `${bestHover.seriesEntry.trendWindow || 3}-pt trend: ${hoverTrendPoint.wpm} WPM`,
                                color: 'rgba(255,255,255,0.5)'
                            }]
                            : [])
                    ];

                    ctx.font = '12px Montserrat, sans-serif';
                    const lineHeight = 17;
                    const tooltipPaddingX = 10;
                    const tooltipPaddingY = 8;
                    const tooltipWidth = Math.max(...tooltipLines.map((line) => ctx.measureText(line.text).width)) + (tooltipPaddingX * 2);
                    const tooltipHeight = (tooltipLines.length * lineHeight) + (tooltipPaddingY * 2) - 4;
                    let tooltipX = bestHover.x + 14;
                    let tooltipY = bestHover.y - tooltipHeight - 12;

                    if (tooltipX + tooltipWidth > w - 8) {
                        tooltipX = bestHover.x - tooltipWidth - 14;
                    }
                    if (tooltipX < 8) {
                        tooltipX = 8;
                    }
                    if (tooltipY < 8) {
                        tooltipY = bestHover.y + 14;
                    }
                    if (tooltipY + tooltipHeight > h - 8) {
                        tooltipY = h - tooltipHeight - 8;
                    }

                    ctx.fillStyle = 'rgba(9, 14, 24, 0.94)';
                    ctx.strokeStyle = 'rgba(255,255,255,0.14)';
                    ctx.lineWidth = 1;
                    ctx.beginPath();
                    ctx.roundRect(tooltipX, tooltipY, tooltipWidth, tooltipHeight, 8);
                    ctx.fill();
                    ctx.stroke();

                    tooltipLines.forEach((line, index) => {
                        ctx.fillStyle = line.color;
                        ctx.textAlign = 'left';
                        ctx.fillText(line.text, tooltipX + tooltipPaddingX, tooltipY + tooltipPaddingY + 11 + (index * lineHeight));
                    });

                    ctx.restore();
                }
            }
        }
    }


    // ─────────────────────────────────────────────────────────────────────────────
    // Summary Stats Cache (prefetched on every page load)
    // ─────────────────────────────────────────────────────────────────────────────
    const SUMMARY_CACHE_KEY = 'estats-summary-cache';
    const SUMMARY_CACHE_TTL = 20 * 60 * 1000; // 20 minutes, matching NT's own delay
    const SUMMARY_SEASON_MAX_AGE_SECS = 90 * 24 * 60 * 60;
    let _activeSeasonRecordPromise = null;
    let _activeSeasonRecordCache = null;

    function computeSummaryStats(logs) {
        if (logs.length === 0) return null;
        const races = logs.length;
        const totalTyped = logs.reduce((s, r) => s + (r.typed || 0), 0);
        const totalSecs = logs.reduce((s, r) => s + (r.secs || 0), 0);
        const totalErrs = logs.reduce((s, r) => s + (r.errs || 0), 0);
        const totalXp = logs.reduce((s, r) => s + ((r.reward && r.reward.exp) || 0), 0);
        const wpmPerRace = logs.map(r => r.secs > 0 ? (r.typed / 5) / (r.secs / 60) : 0);
        const avgWpm = Math.round(wpmPerRace.reduce((s, v) => s + v, 0) / races);
        const accPerRace = logs.map(r => r.typed > 0 ? (1 - (r.errs || 0) / r.typed) * 100 : 100);
        const avgAcc = Math.round(accPerRace.reduce((s, v) => s + v, 0) / races * 100) / 100;
        return { races, totalTyped, totalSecs, totalErrs, totalXp, avgWpm, avgAcc };
    }

    function getSummaryCache(expectedSeasonKey) {
        try {
            const raw = localStorage.getItem(SUMMARY_CACHE_KEY);
            if (raw) {
                const cached = JSON.parse(raw);
                if (cached.ts && (Date.now() - cached.ts) < SUMMARY_CACHE_TTL) {
                    if (expectedSeasonKey !== undefined && (cached.seasonKey || null) !== (expectedSeasonKey || null)) {
                        return null;
                    }
                    return cached;
                }
            }
        } catch { /* ignore corrupt cache */ }
        return null;
    }

    function parseActiveSeasonsFromBootstrapPayload(bootstrapData) {
        if (!Array.isArray(bootstrapData)) return null;
        const seasonsData = bootstrapData.find(item => Array.isArray(item) && item[0] === 'ACTIVE_SEASONS');
        if (!seasonsData || !Array.isArray(seasonsData[1]) || seasonsData[1].length === 0) return null;
        return seasonsData[1];
    }

    function parseActiveSeasonsFromBootstrapScript(scriptText) {
        if (!scriptText || typeof scriptText !== 'string') return null;
        const match = scriptText.match(/\["ACTIVE_SEASONS",(\[[\s\S]*?\])\],\["ACTIVE_EVENTS"/);
        if (!match || !match[1]) return null;
        try {
            const parsed = JSON.parse(match[1]);
            return Array.isArray(parsed) && parsed.length > 0 ? parsed : null;
        } catch {
            return null;
        }
    }

    function pickActiveSeason(seasons) {
        if (!Array.isArray(seasons) || seasons.length === 0) return null;
        const now = Math.floor(Date.now() / 1000);
        const active = seasons.find(season => now >= Number(season?.startStamp || 0) && now < Number(season?.endStamp || 0));
        if (active) return active;
        return seasons.slice().sort((a, b) => Number(b?.startStamp || 0) - Number(a?.startStamp || 0))[0] || null;
    }

    function getSeasonSummaryKey(seasonRecord) {
        if (!seasonRecord) return null;
        const seasonID = seasonRecord.seasonID ?? seasonRecord.id ?? seasonRecord.name ?? 'season';
        const startStamp = Number(seasonRecord.startStamp || 0);
        const endStamp = Number(seasonRecord.endStamp || 0);
        return `${seasonID}:${startStamp}:${endStamp}`;
    }

    function isSeasonSummaryEligible(seasonRecord) {
        const startStamp = Number(seasonRecord?.startStamp || 0);
        if (!Number.isFinite(startStamp) || startStamp <= 0) return false;
        const now = Math.floor(Date.now() / 1000);
        return (now - startStamp) <= SUMMARY_SEASON_MAX_AGE_SECS;
    }

    async function getActiveSeasonRecord() {
        if (_activeSeasonRecordCache) return _activeSeasonRecordCache;
        if (_activeSeasonRecordPromise) return _activeSeasonRecordPromise;

        _activeSeasonRecordPromise = (async () => {
            try {
                const directSeasons = pageWindow.NTGLOBALS?.ACTIVE_SEASONS;
                const directSeason = pickActiveSeason(directSeasons);
                if (directSeason) {
                    _activeSeasonRecordCache = directSeason;
                    return directSeason;
                }

                if (typeof pageWindow.NTBOOTSTRAP === 'function') {
                    try {
                        const bootstrapData = pageWindow.NTBOOTSTRAP();
                        const bootstrapSeason = pickActiveSeason(parseActiveSeasonsFromBootstrapPayload(bootstrapData));
                        if (bootstrapSeason) {
                            _activeSeasonRecordCache = bootstrapSeason;
                            return bootstrapSeason;
                        }
                    } catch { /* ignore */ }
                }

                const bootstrapSrc = document.querySelector('script[src*="/bootstrap.js"]')?.src;
                if (!bootstrapSrc) return null;

                const response = await fetch(bootstrapSrc, { credentials: 'same-origin' });
                if (!response.ok) return null;

                const scriptText = await response.text();
                const bootstrapSeason = pickActiveSeason(parseActiveSeasonsFromBootstrapScript(scriptText));
                if (bootstrapSeason) {
                    _activeSeasonRecordCache = bootstrapSeason;
                    return bootstrapSeason;
                }
            } catch (error) {
                console.warn(LOG_PREFIX, 'Season metadata lookup failed:', error);
            } finally {
                _activeSeasonRecordPromise = null;
            }
            return null;
        })();

        return _activeSeasonRecordPromise;
    }

    async function prefetchSummaryStats(seasonRecord = null) {
        if (!isFeatureEnabled('ENABLE_ENHANCED_STATS_PAGE')) return;
        const seasonEligible = isSeasonSummaryEligible(seasonRecord);
        const seasonKey = seasonEligible ? getSeasonSummaryKey(seasonRecord) : null;
        // Skip if cache is still fresh
        if (getSummaryCache(seasonKey)) return;

        const nowSecs = Math.floor(Date.now() / 1000);
        const sevenDaysAgo = nowSecs - (7 * 24 * 60 * 60);
        const oneDayAgo = nowSecs - (24 * 60 * 60);
        const rangeStart = seasonEligible
            ? Math.min(sevenDaysAgo, Number(seasonRecord.startStamp || nowSecs))
            : sevenDaysAgo;
        const rangeLogs = [];

        try {
            let page = 0;
            let done = false;

            while (!done) {
                const data = await apiFetch(`/api/v2/stats/data/racelog?page=${page}&limit=30`);
                const logs = data?.results?.logs;
                if (!logs || !Array.isArray(logs) || logs.length === 0) break;

                for (const log of logs) {
                    if (log.stamp >= rangeStart) {
                        rangeLogs.push(log);
                    } else {
                        done = true;
                        break;
                    }
                }
                page++;
            }
        } catch (e) {
            console.error(LOG_PREFIX, 'Summary prefetch error:', e);
            return;
        }

        const weekLogs = rangeLogs.filter(r => r.stamp >= sevenDaysAgo);
        const dayLogs = weekLogs.filter(r => r.stamp >= oneDayAgo);
        const dayStats = computeSummaryStats(dayLogs);
        const weekStats = computeSummaryStats(weekLogs);
        const seasonStats = seasonEligible
            ? computeSummaryStats(rangeLogs.filter(r => {
                const stamp = Number(r?.stamp || 0);
                return stamp >= Number(seasonRecord.startStamp || 0) && stamp < Number(seasonRecord.endStamp || nowSecs + 1);
            }))
            : null;

        try {
            localStorage.setItem(SUMMARY_CACHE_KEY, JSON.stringify({
                ts: Date.now(),
                dayStats,
                weekStats,
                seasonStats,
                seasonKey,
                seasonName: seasonEligible ? String(seasonRecord?.name || 'Season') : null
            }));
        } catch { /* storage full, ignore */ }
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 6: Enhanced Stats Page (/stats)
    // ─────────────────────────────────────────────────────────────────────────────
    const STATS_PAGE_ATTR = 'data-estats-stats-page';
    const STATS_SUMMARY_NOSORT_ATTR = 'data-estats-summary-nosort';
    const STATS_SUMMARY_HEADER_LOCK_ATTR = 'data-estats-summary-header-locked';

    function cleanupEnhancedStatsPage() {
        const statBoxContainer = document.querySelector('.stat-box--container');
        if (!statBoxContainer) return;

        statBoxContainer.querySelectorAll('[data-estats-injected],[data-estats-lifetime],[data-estats-week]').forEach((el) => el.remove());
        statBoxContainer.querySelectorAll('[data-estats-original-text]').forEach((el) => restoreOriginalText(el));
        statBoxContainer.querySelectorAll('[data-estats-original-style]').forEach((el) => restoreOriginalStyle(el));
        statBoxContainer.querySelectorAll('[data-estats-original-colspan]').forEach((el) => {
            const original = el.getAttribute('data-estats-original-colspan');
            if (original === '__none__') {
                el.removeAttribute('colspan');
            } else {
                el.setAttribute('colspan', original);
            }
            el.removeAttribute('data-estats-original-colspan');
        });
        statBoxContainer.removeAttribute(STATS_PAGE_ATTR);
        statBoxContainer.querySelectorAll(`[${STATS_SUMMARY_NOSORT_ATTR}]`).forEach((el) => el.removeAttribute(STATS_SUMMARY_NOSORT_ATTR));
        statBoxContainer.querySelectorAll(`[${STATS_PAGE_ATTR}]`).forEach((el) => el.removeAttribute(STATS_PAGE_ATTR));
    }

    function lockStatsSummaryTableHeaders(table) {
        if (!table) return;

        table.setAttribute(STATS_SUMMARY_NOSORT_ATTR, '');
        table.removeAttribute('data-estats-sortable');
        table.removeAttribute('data-estats-sort-active');
        table.querySelectorAll('[data-estats-sort-arrow]').forEach((el) => el.remove());

        table.querySelectorAll('thead th.table-cell').forEach((th) => {
            if (!th.hasAttribute(STATS_SUMMARY_HEADER_LOCK_ATTR)) {
                th.setAttribute(STATS_SUMMARY_HEADER_LOCK_ATTR, '');
                th.addEventListener('click', (event) => {
                    event.preventDefault();
                    event.stopImmediatePropagation();
                }, true);
            }
            th.style.cursor = 'default';
            th.style.userSelect = '';
            th.title = '';
        });
    }

    /** Create a stat-box--extra element matching NT's native pattern. */
    const createStatExtra = (title, value, label = '') => {
        const extra = document.createElement('div');
        extra.className = 'stat-box--extra';
        extra.setAttribute('data-estats-injected', '');
        extra.innerHTML = `
            <div class="stat-box--extra--title">${escapeHtml(title)}</div>
            <div class="stat-box--extra--value">
                <div class="stat-box--extra--stat">${value}</div>
                ${label ? `<div class="stat-box--extra--label">${escapeHtml(label)}</div>` : ''}
            </div>
        `;
        return extra;
    };

    /** Create a summary table row matching NT's native pattern. */
    const createSummaryRow = (label, races, avgSpeed, avgAcc, extraCols = []) => {
        const tr = document.createElement('tr');
        tr.className = 'table-row';
        tr.setAttribute('data-estats-injected', '');
        let html = `
            <td class="table-cell"><span class="tsm tc-ts">${escapeHtml(label)}</span></td>
            <td class="table-cell tar">${escapeHtml(String(races))}</td>
            <td class="table-cell tar">${escapeHtml(String(avgSpeed))} <span class="tsxxs tc-ts mlxxs">WPM</span></td>
            <td class="table-cell tar">${escapeHtml(String(avgAcc))}<span class="tsxs tc-ts">%</span></td>
        `;
        extraCols.forEach(col => {
            html += `<td class="table-cell tar">${col}</td>`;
        });
        tr.innerHTML = html;
        return tr;
    };

    async function handleEnhancedStatsPage() {
        if (!isFeatureEnabled('ENABLE_ENHANCED_STATS_PAGE')) return;

        const path = normalizePath(window.location.pathname);
        if (path !== '/stats') return;

        const statBoxContainer = document.querySelector('.stat-box--container');
        if (!statBoxContainer) return;
        if (statBoxContainer.hasAttribute(STATS_PAGE_ATTR)) return;
        statBoxContainer.setAttribute(STATS_PAGE_ATTR, '');

        const user = getCurrentUser();
        if (!user) return;

        // ── Fix equal-height gaps: auto-size rows, pack to top, no extras gap ──
        statBoxContainer.querySelectorAll('.stat-box').forEach(box => {
            rememberOriginalStyle(box);
            box.style.setProperty('grid-template-rows', 'auto auto auto', 'important');
            box.style.setProperty('align-content', 'start', 'important');
            const extras = box.querySelector('.stat-box--extras');
            if (extras) {
                rememberOriginalStyle(extras);
                extras.style.setProperty('padding-top', '0', 'important');
                extras.style.setProperty('margin-top', '0', 'important');
            }
            const hr = box.querySelector('hr');
            if (hr) {
                rememberOriginalStyle(hr);
                hr.style.setProperty('margin-bottom', '0', 'important');
                hr.style.setProperty('margin-top', '8px', 'important');
            }
        });

        // ── Rename Speed → Performance + inject stats ─────────────────────
        const speedBoxEl = statBoxContainer.querySelector('.stat-box--speed');
        if (speedBoxEl) {
            const speedTitle = speedBoxEl.querySelector('.stat-box--title');
            rememberOriginalText(speedTitle);
            if (speedTitle) speedTitle.textContent = 'Performance';

            // Add "Speed" label above WPM, "Accuracy" label above accuracy %, rename bottom to "Average"
            if (user.avgAcc != null) {
                const details = speedBoxEl.querySelector('.stat-box--details');
                const statEl = speedBoxEl.querySelector('.stat-box--stat');
                const labelEl = speedBoxEl.querySelector('.stat-box--label');
                if (details && statEl && labelEl) {
                    rememberOriginalText(labelEl);
                    // Insert "Speed" label above the WPM number
                    const speedLabel = document.createElement('div');
                    speedLabel.className = 'stat-box--label';
                    speedLabel.setAttribute('data-estats-injected', '');
                    speedLabel.style.color = '#e8796b';
                    speedLabel.textContent = 'Speed';
                    statEl.before(speedLabel);

                    // Change "Average" to just be the bottom label
                    labelEl.textContent = 'Average';

                    // Insert "Accuracy" label + value after the existing Average label
                    const accTitle = document.createElement('div');
                    accTitle.className = 'stat-box--label';
                    accTitle.setAttribute('data-estats-injected', '');
                    accTitle.style.color = '#e8796b';
                    accTitle.textContent = 'Accuracy';
                    labelEl.after(accTitle);

                    const accStat = document.createElement('div');
                    accStat.className = 'stat-box--stat';
                    accStat.setAttribute('data-estats-injected', '');
                    accStat.innerHTML = `${user.avgAcc}<span>%</span>`;
                    accTitle.after(accStat);

                    const accAvgLabel = document.createElement('div');
                    accAvgLabel.className = 'stat-box--label';
                    accAvgLabel.setAttribute('data-estats-injected', '');
                    accAvgLabel.textContent = 'Average';
                    accStat.after(accAvgLabel);
                }
            }
        }

        // ── Rename Races → Racing + inject stats ─────────────────────────
        const racesBoxEl = statBoxContainer.querySelector('.stat-box--races');
        if (racesBoxEl) {
            const racesTitle = racesBoxEl.querySelector('.stat-box--title');
            rememberOriginalText(racesTitle);
            if (racesTitle) racesTitle.textContent = 'Racing';
        }
        const racesBox = statBoxContainer.querySelector('.stat-box--races .stat-box--extras');
        if (racesBox) {
            if (user.wampusWins != null) {
                racesBox.appendChild(createStatExtra('Wampus Wins', (user.wampusWins || 0).toLocaleString(), 'Wins'));
            }
        }

        // ── Streaks widget next to "Overview" heading ────────────────────
        const flameSvg = '<svg viewBox="0 0 24 24" width="16" height="16" style="vertical-align:-2px;"><path fill="#f3a81b" d="M12 23c-4.97 0-9-3.58-9-8 0-3.07 1.74-6.07 4.5-7.77.37-.23.85.1.75.53-.29 1.2-.04 2.5.69 3.47C9.41 7.73 11.2 4.78 11.6 1.48c.05-.4.53-.54.79-.22C14.54 4.14 17 7.61 17 11c0 1.2-.28 2.37-.82 3.42.44-.47.74-1.05.87-1.69.07-.34.53-.43.73-.14C18.55 13.74 19 15.08 19 16.5c0 3.59-3.13 6.5-7 6.5z"/></svg>';

        // Find the "Overview" heading's parent .row
        const overviewRow = Array.from(document.querySelectorAll('.row')).find(row => {
            const h = row.querySelector('h3');
            return h && h.textContent.trim() === 'Overview';
        });

        if (overviewRow && !overviewRow.querySelector('[data-estats-injected]')) {
            rememberOriginalStyle(overviewRow);
            overviewRow.style.display = 'flex';
            overviewRow.style.alignItems = 'center';
            overviewRow.style.justifyContent = 'space-between';

            const streaksWidget = document.createElement('div');
            streaksWidget.setAttribute('data-estats-injected', '');
            streaksWidget.style.cssText = 'display:flex;gap:20px;align-items:center;';

            const winStreak = user.consecWins || 0;
            const dayStreak = user.consecDaysRaced || 0;

            streaksWidget.innerHTML = `
                <div style="display:flex;align-items:center;gap:6px;">
                    ${flameSvg}
                    <span class="tsm twb" style="color:#f3a81b;">${winStreak}</span>
                    <span class="tsxs tc-ts">Win Streak</span>
                </div>
                <div style="display:flex;align-items:center;gap:6px;">
                    ${flameSvg}
                    <span class="tsm twb" style="color:#f3a81b;">${dayStreak}</span>
                    <span class="tsxs tc-ts">Day Streak</span>
                </div>
            `;

            overviewRow.appendChild(streaksWidget);
        }

        // ── Rename Experience → Progression + inject stats ─────────────────
        const xpBoxEl = statBoxContainer.querySelector('.stat-box--progress');
        if (xpBoxEl) {
            const xpTitle = xpBoxEl.querySelector('.stat-box--title');
            rememberOriginalText(xpTitle);
            if (xpTitle) xpTitle.textContent = 'Progression';
        }
        const xpBox = statBoxContainer.querySelector('.stat-box--progress .stat-box--extras');
        if (xpBox) {
            // Find the NT native "calculated as of" note to insert before it
            const noteEl = xpBox.querySelector('.tsxs.tc-ts');

            // Order: Player Level, Season Level, Season XP, then the native note
            const insertBeforeNote = (el) => {
                if (noteEl) {
                    xpBox.insertBefore(el, noteEl);
                } else {
                    xpBox.appendChild(el);
                }
            };

            // ── Shared progress bar builder ──────────────────────────────
            const createProgressBar = (progress, needed, gradient, tooltipHtml) => {
                const pct = needed > 0 ? Math.min(100, Math.max(0, (progress / needed) * 100)) : 0;
                const widget = document.createElement('div');
                widget.setAttribute('data-estats-injected', '');
                widget.style.cssText = 'margin:-4px 0 10px;padding:0 2px;position:relative;';

                const track = document.createElement('div');
                track.style.cssText = 'height:6px;border-radius:999px;background:rgba(0,0,0,0.22);box-shadow:inset 0 1px 2px rgba(0,0,0,0.35);overflow:hidden;';

                const fill = document.createElement('div');
                fill.style.cssText = `width:${pct.toFixed(1)}%;height:100%;border-radius:999px;background:${gradient};box-shadow:0 1px 0 rgba(255,255,255,0.25) inset,0 2px 6px rgba(0,0,0,0.25);transition:width 0.3s ease;`;
                track.appendChild(fill);

                const label = document.createElement('div');
                label.style.cssText = 'font-size:11px;color:#a6aac1;margin-top:3px;display:inline-flex;align-items:center;gap:2px;';
                label.textContent = `${progress.toLocaleString()} / ${needed.toLocaleString()} XP`;

                if (tooltipHtml) {
                    const infoWrap = document.createElement('span');
                    infoWrap.style.cssText = 'position:relative;display:inline-block;vertical-align:middle;margin-left:2px;cursor:help;';
                    infoWrap.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" fill="#a6aac1" style="display:block;"><path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1Zm0 2.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2ZM6.5 7h2l.5.5V12h-1V7.5H6.5V7Z"/></svg>';

                    const tip = document.createElement('div');
                    tip.style.cssText = 'display:none;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#1d2030;color:#d8dcf2;font-size:11px;line-height:1.4;padding:8px 10px;border-radius:6px;border:1px solid rgba(255,255,255,0.12);box-shadow:0 4px 12px rgba(0,0,0,0.4);white-space:nowrap;z-index:100;pointer-events:none;';
                    tip.innerHTML = tooltipHtml;

                    const arrow = document.createElement('div');
                    arrow.style.cssText = 'position:absolute;bottom:-4px;left:50%;transform:translateX(-50%) rotate(45deg);width:7px;height:7px;background:#1d2030;border-right:1px solid rgba(255,255,255,0.12);border-bottom:1px solid rgba(255,255,255,0.12);';
                    tip.appendChild(arrow);

                    infoWrap.appendChild(tip);
                    infoWrap.addEventListener('mouseenter', () => { tip.style.display = 'block'; });
                    infoWrap.addEventListener('mouseleave', () => { tip.style.display = 'none'; });
                    label.appendChild(infoWrap);
                }

                widget.appendChild(track);
                widget.appendChild(label);
                return widget;
            };

            const GOLD_GRADIENT = 'linear-gradient(90deg,#f7d14a 0%,#f3a81b 55%,#f07d1b 100%)';
            const BLUE_GRADIENT = 'linear-gradient(90deg,#1c99f4 0%,#167ac3 100%)';

            // ── Player Level + progress bar ──────────────────────────────
            if (user.playerLevel != null) {
                insertBeforeNote(createStatExtra('Player Level', (user.playerLevel || 0).toLocaleString(), ''));

                const pXp = user.playerExperience || 0;
                const plCfg = pageWindow.NTGLOBALS?.PLAYER_LEVELS;
                const plBase = (plCfg && Number.isFinite(plCfg.basePoints)) ? plCfg.basePoints : 500;
                const plMult = (plCfg && Number.isFinite(plCfg.multiple)) ? plCfg.multiple : 2;
                const pLvl = Math.max(1, user.playerLevel || 1);
                const xpFloor = getPlayerXpThreshold(pLvl - 1);
                const xpCeil = getPlayerXpThreshold(pLvl);
                const xpNeeded = Math.max(0, xpCeil - xpFloor);
                const xpProgressRaw = Math.max(0, pXp - xpFloor);
                const xpProgress = xpNeeded > 0 ? Math.min(xpNeeded, xpProgressRaw) : 0;
                const xpRemaining = Math.max(0, xpCeil - pXp);

                const plTooltip = `<div style="margin-bottom:4px;font-weight:600;color:#f3a81b;">Player Level Thresholds</div>`
                    + `<div>Threshold(n) = (${plBase.toLocaleString()} × ${plMult.toLocaleString()} × n²) + (${plBase.toLocaleString()} × n)</div>`
                    + `<div style="margin-top:4px;color:#a6aac1;">Level ${pLvl.toLocaleString()} spans ${xpFloor.toLocaleString()} to ${xpCeil.toLocaleString()} XP</div>`
                    + `<div style="margin-top:4px;color:#a6aac1;">Next level at ${xpCeil.toLocaleString()} XP (${xpRemaining.toLocaleString()} to go)</div>`;

                insertBeforeNote(createProgressBar(xpProgress, xpNeeded, GOLD_GRADIENT, plTooltip));
            }

            // ── Season Level + progress bar ──────────────────────────────
            if (user.level != null && user.experience != null) {
                insertBeforeNote(createStatExtra('Season Level', formatSeasonLevel(user.level) || user.level, ''));

                const sLvl = user.level || 1;
                const sXp = user.experience || 0;
                const sl = pageWindow.NTGLOBALS?.SEASON_LEVELS;
                const season = pageWindow.NTGLOBALS?.ACTIVE_SEASONS?.[0];
                const startingLevels = sl?.startingLevels || 3;
                const xpPerStarting = sl?.experiencePerStartingLevel || 10000;
                const xpPerAchievement = sl?.experiencePerAchievementLevel || 20000;
                const xpPerExtra = sl?.experiencePerExtraLevels || 40000;
                const totalRewards = season?.totalRewards || 32;

                // Calculate cumulative XP thresholds for the current and next level
                const getSeasonXpThreshold = (lvl) => {
                    let total = 0;
                    for (let i = 1; i < lvl; i++) {
                        if (i <= startingLevels) {
                            total += xpPerStarting;
                        } else if (i > totalRewards) {
                            total += xpPerExtra;
                        } else {
                            total += xpPerAchievement;
                        }
                    }
                    return total;
                };

                const sFloor = getSeasonXpThreshold(sLvl);
                const sCeil = getSeasonXpThreshold(sLvl + 1);
                const sProgress = Math.max(0, sXp - sFloor);
                const sNeeded = sCeil - sFloor;

                const tierLabel = sLvl <= startingLevels ? `${(xpPerStarting / 1000).toLocaleString()}k per level (starting)`
                    : sLvl > totalRewards ? `${(xpPerExtra / 1000).toLocaleString()}k per level (bonus)`
                    : `${(xpPerAchievement / 1000).toLocaleString()}k per level`;

                const sTooltip = `<div style="margin-bottom:4px;font-weight:600;color:#1c99f4;">Season Level Progression</div>`
                    + `<div>${tierLabel}</div>`
                    + `<div style="margin-top:4px;color:#a6aac1;">Next level at ${sCeil.toLocaleString()} XP (${(sCeil - sXp).toLocaleString()} to go)</div>`;

                insertBeforeNote(createProgressBar(sProgress, sNeeded, GOLD_GRADIENT, sTooltip));
            } else {
                if (user.level != null) {
                    insertBeforeNote(createStatExtra('Season Level', formatSeasonLevel(user.level) || user.level, ''));
                }
                if (user.experience != null) {
                    insertBeforeNote(createStatExtra('Season XP', (user.experience || 0).toLocaleString(), 'XP'));
                }
            }
        }

        // ── Rename Money box to Assets + inject stats ─────────────────────
        const moneyBoxEl = statBoxContainer.querySelector('.stat-box--money');
        if (moneyBoxEl) {
            // Rename the title from "Money" to "Assets"
            const moneyTitle = moneyBoxEl.querySelector('.stat-box--title');
            rememberOriginalText(moneyTitle);
            if (moneyTitle) moneyTitle.textContent = 'Assets';

            const moneyBox = moneyBoxEl.querySelector('.stat-box--extras');
            if (moneyBox) {
                const totalCars = user.totalCars || user.carsOwned || 0;
                if (totalCars > 0) {
                    moneyBox.appendChild(createStatExtra('Cars Owned', totalCars.toLocaleString(), 'Cars'));
                }
                if (user.nitros != null) {
                    moneyBox.appendChild(createStatExtra('Nitros Owned', (user.nitros || 0).toLocaleString(), ''));
                }
            }
        }

        // ── Inject into Membership box ─────────────────────────────────────
        const memberBox = statBoxContainer.querySelector('.stat-box--gold .stat-box--extras');
        if (memberBox) {
            if (user.playTime != null) {
                memberBox.appendChild(createStatExtra('Play Time', formatDuration(user.playTime || 0), ''));
            }
        }

        // ── Enhance Summary Table ──────────────────────────────────────────
        // Add extra column headers: Errors, Total Time
        const summaryTable = document.querySelector('.table--striped');
        if (summaryTable) {
            const headerRow = summaryTable.querySelector('thead .table-row');
            if (headerRow && !headerRow.querySelector('[data-estats-injected]')) {
                // Keep NT's native header styling

                const extraHeaders = [
                    { label: 'Errors', color: '#d62f3a' },
                    { label: 'Experience', color: '#f3a81b' },
                    { label: 'Total Time', color: '#1c99f4' }
                ];
                extraHeaders.forEach(({ label, color }) => {
                    const th = document.createElement('th');
                    th.scope = 'col';
                    th.className = 'table-cell';
                    th.setAttribute('data-estats-injected', '');
                    th.style.color = color;
                    th.textContent = label;
                    headerRow.appendChild(th);
                });
            }

            const tbody = summaryTable.querySelector('tbody');
            if (tbody && !tbody.querySelector('[data-estats-lifetime]')) {
                // Add placeholder cells to existing rows immediately to prevent layout shift
                tbody.querySelectorAll('.table-row').forEach(row => {
                    if (row.querySelector('[data-estats-injected]')) return;
                    for (let i = 0; i < 3; i++) {
                        const td = document.createElement('td');
                        td.className = 'table-cell tar';
                        td.setAttribute('data-estats-injected', '');
                        td.setAttribute('data-estats-placeholder', i === 0 ? 'errors' : i === 1 ? 'xp' : 'time');
                        td.textContent = '…';
                        td.style.opacity = '0.3';
                        row.appendChild(td);
                    }
                });

                // Add Lifetime row with placeholders immediately
                const totalRaces = user.racesPlayed || 0;
                const avgSpeed = user.avgSpeed || 0;
                const avgAcc = user.avgAcc || 0;
                const lifetimeXp = user.playerExperience || user.experience || 0;
                const totalMinutes = (user.playTime || 0) / 60;
                const totalChars = avgSpeed * totalMinutes * 5;
                const lifetimeErrors = avgAcc > 0 ? Math.round(totalChars * (1 - avgAcc / 100)) : 0;

                const lifetimeRow = document.createElement('tr');
                lifetimeRow.className = 'table-row';
                lifetimeRow.setAttribute('data-estats-lifetime', '');
                lifetimeRow.setAttribute('data-estats-injected', '');
                lifetimeRow.innerHTML = `
                    <td class="table-cell"><span class="tsm tc-ts">Lifetime</span></td>
                    <td class="table-cell tar">${totalRaces.toLocaleString()}</td>
                    <td class="table-cell tar">${avgSpeed} <span class="tsxxs tc-ts mlxxs">WPM</span></td>
                    <td class="table-cell tar">${avgAcc}<span class="tsxs tc-ts">%</span></td>
                    <td class="table-cell tar">~${lifetimeErrors.toLocaleString()}</td>
                    <td class="table-cell tar">${lifetimeXp.toLocaleString()} <span class="tsxxs tc-ts mlxxs">XP</span></td>
                    <td class="table-cell tar">${formatDuration(user.playTime || 0)}</td>
                `;
                tbody.appendChild(lifetimeRow);

                // Update the colspan on the footer
                const footerTd = summaryTable.querySelector('tfoot .table-cell[colspan]');
                if (footerTd) {
                    if (!footerTd.hasAttribute('data-estats-original-colspan')) {
                        const originalColspan = footerTd.getAttribute('colspan');
                        footerTd.setAttribute('data-estats-original-colspan', originalColspan == null ? '__none__' : originalColspan);
                    }
                    footerTd.setAttribute('colspan', '8');
                }

                // ── Helper: fill the table from stats ──
                const fillSummaryStats = (dayStats, weekStats, seasonPayload = null) => {
                    const bodyRows = summaryTable.querySelectorAll('tbody .table-row:not([data-estats-lifetime])');
                    bodyRows.forEach(row => {
                        const cells = row.querySelectorAll('.table-cell');
                        if (dayStats && cells.length >= 4) {
                            cells[1].innerHTML = dayStats.races.toLocaleString();
                            cells[2].innerHTML = `${dayStats.avgWpm} <span class="tsxxs tc-ts mlxxs">WPM</span>`;
                            cells[3].innerHTML = `${dayStats.avgAcc}<span class="tsxs tc-ts">%</span>`;
                        }

                        const errCell = row.querySelector('[data-estats-placeholder="errors"]');
                        const xpCell = row.querySelector('[data-estats-placeholder="xp"]');
                        const timeCell = row.querySelector('[data-estats-placeholder="time"]');
                        if (dayStats) {
                            if (errCell) { errCell.textContent = dayStats.totalErrs.toLocaleString(); errCell.style.opacity = ''; }
                            if (xpCell) { xpCell.innerHTML = `${dayStats.totalXp.toLocaleString()} <span class="tsxxs tc-ts mlxxs">XP</span>`; xpCell.style.opacity = ''; }
                            if (timeCell) { timeCell.textContent = formatDuration(dayStats.totalSecs); timeCell.style.opacity = ''; }
                        } else {
                            if (errCell) { errCell.textContent = '—'; errCell.style.opacity = ''; }
                            if (xpCell) { xpCell.textContent = '—'; xpCell.style.opacity = ''; }
                            if (timeCell) { timeCell.textContent = '—'; timeCell.style.opacity = ''; }
                        }
                    });

                    const lifetimeRowEl = tbody.querySelector('[data-estats-lifetime]');
                    let weekRow = tbody.querySelector('[data-estats-week]');
                    let seasonRow = tbody.querySelector('[data-estats-season]');
                    if (weekStats && lifetimeRowEl) {
                        if (!weekRow) {
                            weekRow = document.createElement('tr');
                            weekRow.className = 'table-row';
                            weekRow.setAttribute('data-estats-injected', '');
                            weekRow.setAttribute('data-estats-week', '');
                            tbody.insertBefore(weekRow, lifetimeRowEl);
                        }
                        weekRow.innerHTML = `
                            <td class="table-cell"><span class="tsm tc-ts">Last 7 Days</span></td>
                            <td class="table-cell tar">${weekStats.races.toLocaleString()}</td>
                            <td class="table-cell tar">${weekStats.avgWpm} <span class="tsxxs tc-ts mlxxs">WPM</span></td>
                            <td class="table-cell tar">${weekStats.avgAcc}<span class="tsxs tc-ts">%</span></td>
                            <td class="table-cell tar">${weekStats.totalErrs.toLocaleString()}</td>
                            <td class="table-cell tar">${weekStats.totalXp.toLocaleString()} <span class="tsxxs tc-ts mlxxs">XP</span></td>
                            <td class="table-cell tar">${formatDuration(weekStats.totalSecs)}</td>
                        `;
                    } else if (weekRow) {
                        weekRow.remove();
                    }

                    if (seasonPayload?.stats && lifetimeRowEl) {
                        if (!seasonRow) {
                            seasonRow = document.createElement('tr');
                            seasonRow.className = 'table-row';
                            seasonRow.setAttribute('data-estats-injected', '');
                            seasonRow.setAttribute('data-estats-season', '');
                            tbody.insertBefore(seasonRow, lifetimeRowEl);
                        }
                        seasonRow.title = seasonPayload.name || 'Current Season';
                        seasonRow.innerHTML = `
                            <td class="table-cell"><span class="tsm tc-ts">Season</span></td>
                            <td class="table-cell tar">${seasonPayload.stats.races.toLocaleString()}</td>
                            <td class="table-cell tar">${seasonPayload.stats.avgWpm} <span class="tsxxs tc-ts mlxxs">WPM</span></td>
                            <td class="table-cell tar">${seasonPayload.stats.avgAcc}<span class="tsxs tc-ts">%</span></td>
                            <td class="table-cell tar">${seasonPayload.stats.totalErrs.toLocaleString()}</td>
                            <td class="table-cell tar">${seasonPayload.stats.totalXp.toLocaleString()} <span class="tsxxs tc-ts mlxxs">XP</span></td>
                            <td class="table-cell tar">${formatDuration(seasonPayload.stats.totalSecs)}</td>
                        `;
                    } else if (seasonRow) {
                        seasonRow.remove();
                    }
                };

                const seasonRecord = await getActiveSeasonRecord();
                const seasonSummaryEligible = isSeasonSummaryEligible(seasonRecord);
                const expectedSeasonKey = seasonSummaryEligible ? getSeasonSummaryKey(seasonRecord) : undefined;

                // ── Load from prefetched cache (instant, no shift) ──
                const cached = getSummaryCache(expectedSeasonKey);
                if (cached) {
                    fillSummaryStats(
                        cached.dayStats,
                        cached.weekStats,
                        cached.seasonStats && seasonSummaryEligible
                            ? { stats: cached.seasonStats, name: cached.seasonName || seasonRecord?.name || 'Season' }
                            : null
                    );
                }

                // ── If cache was stale/missing, fetch now and fill ──
                if (!cached) {
                    await prefetchSummaryStats(seasonSummaryEligible ? seasonRecord : null);
                    const fresh = getSummaryCache(expectedSeasonKey);
                    if (fresh) {
                        fillSummaryStats(
                            fresh.dayStats,
                            fresh.weekStats,
                            fresh.seasonStats && seasonSummaryEligible
                                ? { stats: fresh.seasonStats, name: fresh.seasonName || seasonRecord?.name || 'Season' }
                                : null
                        );
                    }
                }

                lockStatsSummaryTableHeaders(summaryTable);
            }
        }
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 7: Enhanced Racelog (/racelog/*)
    // ─────────────────────────────────────────────────────────────────────────────
    const RACELOG_ATTR = 'data-estats-racelog';
    const RACELOG_PAGE_ATTR = 'data-estats-racelog-page';
    const RACELOG_ROW_PAGE_ATTR = 'data-estats-racelog-row-page';
    const RACELOG_HYDRATION_ATTR = 'data-estats-racelog-hydration';
    const RACELOG_MODAL_ATTR = 'data-estats-racelog-modal';
    const RACELOG_MODAL_ITEM_ATTR = 'data-estats-racelog-modal-item';
    const RACELOG_MODAL_ROW_HEIGHT_ATTR = 'data-estats-racelog-original-inline-height';
    const RACELOG_MODAL_ROW_MIN_HEIGHT_ATTR = 'data-estats-racelog-original-inline-min-height';
    const RACELOG_MODAL_SPLIT_DISPLAY_ATTR = 'data-estats-racelog-original-split-display';
    const RACELOG_TREND_BTN_ATTR = 'data-estats-racelog-trend-btn';
    const RACELOG_TREND_OVERLAY_ATTR = 'data-estats-racelog-trend-overlay';
    const PER_PAGE = 30;
    const _dataCache = {};
    const _totalRecords = {};
    const _sortableTables = new Set();
    let _lastEnhancedRacelogWorkKey = '';
    function cleanupEnhancedRacelog() {
        resetAllRacelogTabSortState();
        document.querySelectorAll('[data-estats-sort-pagination]').forEach((el) => el.remove());
        restoreVisibleNativePagination();
        document.querySelectorAll('.tabs.tabs--a.tabs--fourUp[data-estats-tab-reset-bound]').forEach((el) => {
            el.removeAttribute('data-estats-tab-reset-bound');
        });
        document.querySelectorAll(`table[${RACELOG_ATTR}], .table--striped[data-estats-sortable]`).forEach((table) => {
            table.querySelectorAll('[data-estats-injected]').forEach((el) => el.remove());
            table.removeAttribute(RACELOG_ATTR);
            table.removeAttribute(RACELOG_PAGE_ATTR);
            table.removeAttribute(RACELOG_HYDRATION_ATTR);
            table.removeAttribute('data-estats-sortable');
            table.removeAttribute('data-estats-sort-active');
            table.removeAttribute('data-estats-sort-owner-id');
            table.querySelectorAll('[data-estats-sort-arrow]').forEach((el) => el.remove());
        });
        document.querySelectorAll(`[${RACELOG_ROW_PAGE_ATTR}]`).forEach((row) => {
            row.removeAttribute(RACELOG_ROW_PAGE_ATTR);
        });
        document.querySelectorAll(`.modal--raceResults .raceResults[${RACELOG_MODAL_ATTR}]`).forEach((el) => {
            el.removeAttribute(RACELOG_MODAL_ATTR);
        });
        document.querySelectorAll(`.modal--raceResults [${RACELOG_MODAL_ITEM_ATTR}]`).forEach((el) => el.remove());
        document.querySelectorAll(`[${RACELOG_TREND_BTN_ATTR}]`).forEach((el) => {
            const actionWell = el.parentElement;
            el.remove();
            if (actionWell) restoreOriginalStyle(actionWell);
        });
        document.querySelectorAll(`[${RACELOG_TREND_OVERLAY_ATTR}]`).forEach((el) => el.remove());
        _sortableTables.clear();
        _lastEnhancedRacelogWorkKey = '';
    }
    let _sortOwnerCounter = 0;
    let _deferredSortCleanupTimer = null;

    // NT API type mapping: our internal table types → API type parameter values
    const API_TYPE_MAP = { racelog: 'racelog', daily: 'lastdays', monthly: 'bymonth', topSpeed: 'topspeeds' };

    // ── Shared: fetch all records of a given type (cached, batched) ──
    async function fetchAllDataForType(tableType) {
        const apiType = API_TYPE_MAP[tableType] || tableType;
        if (_dataCache[apiType]) return _dataCache[apiType];
        const first = await apiFetch(`/api/v2/stats/data/${apiType}?page=0&limit=${PER_PAGE}`);
        const total = first.results?.totalRecords || 0;
        _totalRecords[apiType] = total;
        const firstLogs = first.results?.logs || [];
        if (!Array.isArray(firstLogs)) return [];
        if (apiType === 'racelog') {
            firstLogs.forEach((r, i) => { r._raceNum = total - i; });
        }
        if (firstLogs.length >= total) { _dataCache[apiType] = firstLogs; return firstLogs; }
        const totalPages = Math.ceil(total / PER_PAGE);
        let allLogs = [...firstLogs];
        for (let batch = 1; batch < totalPages; batch += 10) {
            const promises = [];
            for (let p = batch; p < Math.min(batch + 10, totalPages); p++) {
                promises.push(apiFetch(`/api/v2/stats/data/${apiType}?page=${p}&limit=${PER_PAGE}`).then(res => ({ page: p, res })));
            }
            const results = await Promise.all(promises);
            results.sort((a, b) => a.page - b.page);
            results.forEach(({ page: p, res: r }) => {
                const logs = r.results?.logs || [];
                if (Array.isArray(logs)) {
                    if (apiType === 'racelog') {
                        logs.forEach((race, i) => { race._raceNum = total - (p * PER_PAGE + i); });
                    }
                    allLogs = allLogs.concat(logs);
                }
            });
        }
        _dataCache[apiType] = allLogs;
        return allLogs;
    }

    async function fetchRacelogPage(pageNum) {
        const safePage = Math.max(0, Number(pageNum) || 0);
        const cacheKey = `racelog-page-${safePage}`;
        if (_dataCache[cacheKey]) return _dataCache[cacheKey];
        const data = await apiFetch(`/api/v2/stats/data/racelog?page=${safePage}&limit=${PER_PAGE}`);
        const logs = Array.isArray(data?.results?.logs) ? data.results.logs : [];
        _dataCache[cacheKey] = logs;
        return logs;
    }

    function formatRacelogTrendLabel(stamp) {
        if (!stamp) return '—';
        return new Date(stamp * 1000).toLocaleDateString('en-US', {
            month: 'short',
            day: 'numeric'
        });
    }

    function formatRacelogTrendTooltipTimestamp(stamp) {
        if (!stamp) return '—';
        return new Date(stamp * 1000).toLocaleString('en-US', {
            month: 'short',
            day: 'numeric',
            year: 'numeric',
            hour: 'numeric',
            minute: '2-digit'
        });
    }

    function formatRacelogTrendAxisLabel(stamp) {
        if (!stamp) return { date: '—', time: '' };
        const date = new Date(stamp * 1000);
        return {
            date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
            time: date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
        };
    }

    function formatDatetimeLocalValue(stamp) {
        if (!stamp) return '';
        const date = new Date(stamp * 1000);
        const yyyy = date.getFullYear();
        const mm = String(date.getMonth() + 1).padStart(2, '0');
        const dd = String(date.getDate()).padStart(2, '0');
        const hh = String(date.getHours()).padStart(2, '0');
        const mi = String(date.getMinutes()).padStart(2, '0');
        return `${yyyy}-${mm}-${dd}T${hh}:${mi}`;
    }

    function parseDatetimeLocalValue(value) {
        if (!value) return null;
        const date = new Date(value);
        const time = date.getTime();
        return Number.isFinite(time) ? Math.floor(time / 1000) : null;
    }

    function filterRacelogTrendPoints(points, rangeState) {
        if (!Array.isArray(points) || points.length === 0) return [];
        const mode = rangeState?.mode || 'all';
        let startStamp = null;
        let endStamp = null;
        const nowStamp = Math.floor(Date.now() / 1000);

        if (mode === '24h') {
            endStamp = nowStamp;
            startStamp = nowStamp - (24 * 60 * 60);
        } else if (mode === '7d') {
            endStamp = nowStamp;
            startStamp = nowStamp - (7 * 24 * 60 * 60);
        } else if (mode === '30d') {
            endStamp = nowStamp;
            startStamp = nowStamp - (30 * 24 * 60 * 60);
        } else if (mode === 'custom') {
            startStamp = Number.isFinite(rangeState?.startStamp) ? rangeState.startStamp : null;
            endStamp = Number.isFinite(rangeState?.endStamp) ? rangeState.endStamp : null;
        }

        return points.filter((point) => {
            if (startStamp != null && point.stamp < startStamp) return false;
            if (endStamp != null && point.stamp > endStamp) return false;
            return true;
        });
    }

    function getNearestIndexedPoint(points, targetIndex) {
        if (!Array.isArray(points) || points.length === 0 || !Number.isFinite(targetIndex)) return null;
        const index = Math.max(0, Math.min(points.length - 1, Math.round(targetIndex)));
        return points[index] || null;
    }

    function buildRacelogTrendPoints(logs) {
        const points = (Array.isArray(logs) ? logs : [])
            .filter((race) => Number.isFinite(Number(race?.stamp)) && Number(race.stamp) > 0 && Number.isFinite(Number(race?.secs)) && Number(race.secs) > 0 && Number.isFinite(Number(race?.typed)) && Number(race.typed) > 0)
            .sort((a, b) => {
                const stampDiff = Number(a.stamp) - Number(b.stamp);
                if (stampDiff !== 0) return stampDiff;
                return Number(a._raceNum || 0) - Number(b._raceNum || 0);
            })
            .map((race, index) => ({
                stamp: Number(race.stamp),
                raceNum: Number(race._raceNum) || index + 1,
                wpm: calcWPM(race),
                accuracy: Math.round(calcAcc(race) * 100) / 100,
                typed: Number(race.typed) || 0,
                errors: Number(race.errs) || 0,
                secs: Number(race.secs) || 0
            }));

        const smoothingWindow = points.length >= 500 ? 21
            : points.length >= 250 ? 15
                : points.length >= 120 ? 9
                    : points.length >= 50 ? 5
                        : 3;

        return points.map((point, index) => {
            const halfWindow = Math.floor(smoothingWindow / 2);
            const start = Math.max(0, index - halfWindow);
            const end = Math.min(points.length - 1, index + halfWindow);
            let sumWpm = 0;
            let sumAcc = 0;
            let count = 0;
            for (let i = start; i <= end; i++) {
                sumWpm += points[i].wpm;
                sumAcc += points[i].accuracy;
                count++;
            }
            return {
                ...point,
                smoothingWindow,
                trendWpm: Math.round(sumWpm / Math.max(1, count)),
                trendAccuracy: Math.round((sumAcc / Math.max(1, count)) * 100) / 100
            };
        });
    }

    function drawRacelogTrendChart(canvas, points, chartState = {}) {
        const ctx = canvas.getContext('2d');
        const dpr = window.devicePixelRatio || 1;
        if (!canvas._origW) { canvas._origW = canvas.width; canvas._origH = canvas.height; }
        const w = canvas._origW;
        const h = canvas._origH;
        const showSpeed = chartState.showSpeed !== false;
        const showAccuracy = chartState.showAccuracy !== false;
        const useSmoothing = chartState.useSmoothing !== false;
        const hoverMouse = chartState.hoverMouse || null;

        canvas.width = w * dpr;
        canvas.height = h * dpr;
        canvas.style.width = `${w}px`;
        canvas.style.height = `${h}px`;
        ctx.scale(dpr, dpr);
        ctx.clearRect(0, 0, w, h);

        if (!Array.isArray(points) || points.length === 0) return;

        const padding = { top: 22, right: showAccuracy ? 54 : 24, bottom: 52, left: 48 };
        const chartW = w - padding.left - padding.right;
        const chartH = h - padding.top - padding.bottom;
        const lastIndex = Math.max(1, points.length - 1);

        let minWpm = Infinity;
        let maxWpm = 0;
        let minAcc = Infinity;
        let maxAcc = 0;

        points.forEach((point) => {
            const wpmValue = useSmoothing ? point.trendWpm : point.wpm;
            const accValue = useSmoothing ? point.trendAccuracy : point.accuracy;
            if (Number.isFinite(wpmValue)) {
                minWpm = Math.min(minWpm, wpmValue);
                maxWpm = Math.max(maxWpm, wpmValue);
            }
            if (Number.isFinite(accValue)) {
                minAcc = Math.min(minAcc, accValue);
                maxAcc = Math.max(maxAcc, accValue);
            }
        });

        if (!Number.isFinite(minWpm)) minWpm = 0;
        if (!Number.isFinite(maxWpm) || maxWpm <= 0) maxWpm = 150;
        if (!Number.isFinite(minAcc)) minAcc = 90;
        if (!Number.isFinite(maxAcc) || maxAcc <= 0) maxAcc = 100;

        minWpm = Math.max(0, Math.floor(minWpm / 10) * 10 - 10);
        maxWpm = Math.max(minWpm + 10, Math.ceil(maxWpm / 10) * 10 + 10);

        minAcc = Math.max(80, Math.floor(minAcc) - 2);
        maxAcc = Math.min(100, Math.ceil(maxAcc) + 1);
        if (maxAcc - minAcc < 4) {
            minAcc = Math.max(80, minAcc - 2);
            maxAcc = Math.min(100, maxAcc + 2);
        }
        if (maxAcc <= minAcc) maxAcc = Math.min(100, minAcc + 5);

        const scaleX = (index) => padding.left + ((index / lastIndex) * chartW);
        const scaleSpeedY = (wpm) => padding.top + chartH - (((wpm - minWpm) / (maxWpm - minWpm || 1)) * chartH);
        const scaleAccY = (acc) => padding.top + chartH - (((acc - minAcc) / (maxAcc - minAcc || 1)) * chartH);
        const invertScaleX = (x) => ((x - padding.left) / chartW) * lastIndex;

        ctx.strokeStyle = 'rgba(255,255,255,0.06)';
        ctx.lineWidth = 1;
        const yTicks = 5;
        for (let i = 0; i <= yTicks; i++) {
            const y = padding.top + ((chartH / yTicks) * i);
            ctx.beginPath();
            ctx.moveTo(padding.left, y);
            ctx.lineTo(w - padding.right, y);
            ctx.stroke();

            if (showSpeed) {
                const speedValue = maxWpm - (((maxWpm - minWpm) / yTicks) * i);
                ctx.fillStyle = 'rgba(255,255,255,0.42)';
                ctx.font = '10px Montserrat, sans-serif';
                ctx.textAlign = 'right';
                ctx.fillText(String(Math.round(speedValue)), padding.left - 6, y + 3);
            }

            if (showAccuracy) {
                const accValue = maxAcc - (((maxAcc - minAcc) / yTicks) * i);
                ctx.fillStyle = 'rgba(74,222,128,0.45)';
                ctx.font = '10px Montserrat, sans-serif';
                ctx.textAlign = 'left';
                ctx.fillText(`${accValue.toFixed(0)}%`, w - padding.right + 6, y + 3);
            }
        }

        const xTicks = Math.min(6, Math.max(2, Math.floor(points.length / 120) + 2));
        for (let i = 0; i <= xTicks; i++) {
            const tickIndex = Math.max(0, Math.min(points.length - 1, Math.round((lastIndex / xTicks) * i)));
            const tickPoint = points[tickIndex];
            if (!tickPoint) continue;
            const x = scaleX(tickIndex);
            const label = formatRacelogTrendAxisLabel(tickPoint.stamp);
            ctx.fillStyle = 'rgba(255,255,255,0.42)';
            ctx.font = '10px Montserrat, sans-serif';
            ctx.textAlign = 'center';
            ctx.fillText(label.date, x, h - padding.bottom + 18);
            ctx.fillStyle = 'rgba(255,255,255,0.28)';
            ctx.font = '9px Montserrat, sans-serif';
            ctx.fillText(label.time, x, h - padding.bottom + 30);
        }

        if (showSpeed) {
            ctx.fillStyle = 'rgba(255,255,255,0.28)';
            ctx.font = '10px Montserrat, sans-serif';
            ctx.textAlign = 'center';
            ctx.save();
            ctx.translate(12, padding.top + (chartH / 2));
            ctx.rotate(-Math.PI / 2);
            ctx.fillText('WPM', 0, 0);
            ctx.restore();
        }

        if (showAccuracy) {
            ctx.fillStyle = 'rgba(74,222,128,0.32)';
            ctx.font = '10px Montserrat, sans-serif';
            ctx.textAlign = 'center';
            ctx.save();
            ctx.translate(w - 8, padding.top + (chartH / 2));
            ctx.rotate(Math.PI / 2);
            ctx.fillText('Accuracy', 0, 0);
            ctx.restore();
        }

        if (showAccuracy) {
            ctx.strokeStyle = '#4ade80';
            ctx.lineWidth = 1.5;
            ctx.globalAlpha = 0.75;
            ctx.setLineDash([5, 5]);
            ctx.lineJoin = 'round';
            ctx.lineCap = 'round';
            ctx.beginPath();
            points.forEach((point, index) => {
                const x = scaleX(index);
                const y = scaleAccY(useSmoothing ? point.trendAccuracy : point.accuracy);
                if (index === 0) ctx.moveTo(x, y);
                else ctx.lineTo(x, y);
            });
            ctx.stroke();
            ctx.setLineDash([]);
            ctx.globalAlpha = 1;
        }

        if (showSpeed) {
            ctx.strokeStyle = '#f3a81b';
            ctx.lineWidth = 2;
            ctx.lineJoin = 'round';
            ctx.lineCap = 'round';
            ctx.beginPath();
            points.forEach((point, index) => {
                const x = scaleX(index);
                const y = scaleSpeedY(useSmoothing ? point.trendWpm : point.wpm);
                if (index === 0) ctx.moveTo(x, y);
                else ctx.lineTo(x, y);
            });
            ctx.stroke();
        }

        if (!hoverMouse || chartW <= 0 || chartH <= 0) return;

        const withinChart = hoverMouse.x >= padding.left
            && hoverMouse.x <= (w - padding.right)
            && hoverMouse.y >= padding.top
            && hoverMouse.y <= (h - padding.bottom);
        if (!withinChart) return;

        const hoverPoint = getNearestIndexedPoint(points, invertScaleX(hoverMouse.x));
        if (!hoverPoint) return;

        const hoverIndex = Math.max(0, points.indexOf(hoverPoint));
        const hoverX = scaleX(hoverIndex);
        const speedY = scaleSpeedY(useSmoothing ? hoverPoint.trendWpm : hoverPoint.wpm);
        const accY = scaleAccY(useSmoothing ? hoverPoint.trendAccuracy : hoverPoint.accuracy);

        ctx.save();
        ctx.strokeStyle = 'rgba(255,255,255,0.18)';
        ctx.lineWidth = 1;
        ctx.setLineDash([4, 4]);
        ctx.beginPath();
        ctx.moveTo(hoverX, padding.top);
        ctx.lineTo(hoverX, padding.top + chartH);
        ctx.stroke();
        ctx.setLineDash([]);

        if (showSpeed) {
            ctx.beginPath();
            ctx.arc(hoverX, speedY, 5, 0, Math.PI * 2);
            ctx.fillStyle = '#f3a81b';
            ctx.fill();
            ctx.lineWidth = 2;
            ctx.strokeStyle = '#ffffff';
            ctx.stroke();
        }

        if (showAccuracy) {
            ctx.beginPath();
            ctx.arc(hoverX, accY, 4.5, 0, Math.PI * 2);
            ctx.fillStyle = '#4ade80';
            ctx.fill();
            ctx.lineWidth = 2;
            ctx.strokeStyle = '#ffffff';
            ctx.stroke();
        }

        const tooltipLines = [
            { text: `Race #${hoverPoint.raceNum}`, color: '#ffffff' },
            { text: formatRacelogTrendTooltipTimestamp(hoverPoint.stamp), color: 'rgba(255,255,255,0.72)' },
            ...(showSpeed ? [{ text: `${hoverPoint.wpm} WPM`, color: '#f3a81b' }] : []),
            ...(showAccuracy ? [{ text: `${hoverPoint.accuracy.toFixed(2)}% Acc`, color: '#4ade80' }] : []),
            { text: `${hoverPoint.errors} Errors`, color: '#f87171' },
            { text: `${hoverPoint.typed.toLocaleString()} Length`, color: '#a6aac1' }
        ];

        ctx.font = '12px Montserrat, sans-serif';
        const lineHeight = 17;
        const tooltipPaddingX = 10;
        const tooltipPaddingY = 8;
        const tooltipWidth = Math.max(...tooltipLines.map((line) => ctx.measureText(line.text).width)) + (tooltipPaddingX * 2);
        const tooltipHeight = (tooltipLines.length * lineHeight) + (tooltipPaddingY * 2) - 4;
        let tooltipX = hoverX + 14;
        let tooltipY = Math.min(speedY, accY) - tooltipHeight - 14;

        if (tooltipX + tooltipWidth > w - 8) tooltipX = hoverX - tooltipWidth - 14;
        if (tooltipX < 8) tooltipX = 8;
        if (tooltipY < 8) tooltipY = Math.max(speedY, accY) + 14;
        if (tooltipY + tooltipHeight > h - 8) tooltipY = h - tooltipHeight - 8;

        ctx.fillStyle = 'rgba(9, 14, 24, 0.95)';
        ctx.strokeStyle = 'rgba(255,255,255,0.14)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.roundRect(tooltipX, tooltipY, tooltipWidth, tooltipHeight, 8);
        ctx.fill();
        ctx.stroke();

        tooltipLines.forEach((line, index) => {
            ctx.fillStyle = line.color;
            ctx.textAlign = 'left';
            ctx.fillText(line.text, tooltipX + tooltipPaddingX, tooltipY + tooltipPaddingY + 11 + (index * lineHeight));
        });

        ctx.restore();
    }

    function closeRacelogTrendOverlay() {
        document.querySelectorAll(`[${RACELOG_TREND_OVERLAY_ATTR}]`).forEach((el) => {
            if (typeof el._estatsClose === 'function') {
                el._estatsClose();
            } else {
                el.remove();
            }
        });
    }

    async function openRacelogTrendGraph(button) {
        if (document.querySelector(`[${RACELOG_TREND_OVERLAY_ATTR}]`)) return;

        const overlay = document.createElement('div');
        overlay.setAttribute(RACELOG_TREND_OVERLAY_ATTR, '');
        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;display:flex;align-items:center;justify-content:center;padding:10px;box-sizing:border-box;';

        const modal = document.createElement('div');
        modal.style.cssText = 'background:#1d2030;border-radius:12px;padding:14px 18px;width:min(1180px, 95vw);max-height:min(90vh, 760px);overflow:auto;position:relative;border:1px solid rgba(255,255,255,0.1);box-sizing:border-box;';

        const closeBtn = document.createElement('button');
        closeBtn.type = 'button';
        closeBtn.style.cssText = 'position:absolute;top:12px;right:16px;background:none;border:none;color:#a6aac1;font-size:24px;cursor:pointer;line-height:1;';
        closeBtn.innerHTML = '&times;';
        modal.appendChild(closeBtn);

        const title = document.createElement('h4');
        title.className = 'tss ttu tc-ts';
        title.style.cssText = 'margin:0 0 4px;';
        title.textContent = 'Racelog Trend Graph';
        modal.appendChild(title);

        const subtitle = document.createElement('div');
        subtitle.className = 'tsxs';
        subtitle.style.cssText = 'color:#a6aac1;margin-bottom:10px;';
        subtitle.textContent = 'Speed and accuracy across your racelog history.';
        modal.appendChild(subtitle);

        const content = document.createElement('div');
        content.style.cssText = 'min-height:420px;display:flex;align-items:center;justify-content:center;color:#a6aac1;';
        content.textContent = 'Loading racelog trend...';
        modal.appendChild(content);

        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        const onEsc = (event) => {
            if (event.key === 'Escape') {
                overlay.remove();
                document.removeEventListener('keydown', onEsc);
            }
        };
        document.addEventListener('keydown', onEsc);

        const teardown = () => {
            overlay.remove();
            document.removeEventListener('keydown', onEsc);
        };
        overlay._estatsClose = teardown;

        closeBtn.addEventListener('click', teardown);
        overlay.addEventListener('click', (event) => {
            if (event.target === overlay) teardown();
        });

        if (button) {
            button.disabled = true;
            button.style.opacity = '0.75';
        }

        try {
            const logs = await fetchAllDataForType('racelog');
            const allPoints = buildRacelogTrendPoints(logs);
            if (!allPoints.length) {
                content.textContent = 'No racelog history was available to graph.';
                return;
            }

            content.style.display = 'block';
            content.style.alignItems = 'stretch';
            content.style.justifyContent = 'flex-start';
            content.style.minHeight = '0';
            content.style.color = '';

            const smoothingWindow = allPoints[0].smoothingWindow || 1;
            let filteredPoints = allPoints;
            const rangeState = { mode: 'all', startStamp: null, endStamp: null };

            content.innerHTML = '';

            const rangeBar = document.createElement('div');
            rangeBar.style.cssText = 'display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;';
            content.appendChild(rangeBar);

            const presetGroup = document.createElement('div');
            presetGroup.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;';
            rangeBar.appendChild(presetGroup);

            const customGroup = document.createElement('div');
            customGroup.style.cssText = 'display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:6px;';
            rangeBar.appendChild(customGroup);

            const customStartInput = document.createElement('input');
            customStartInput.type = 'datetime-local';
            customStartInput.min = formatDatetimeLocalValue(allPoints[0].stamp);
            customStartInput.max = formatDatetimeLocalValue(allPoints[allPoints.length - 1].stamp);
            customStartInput.value = customStartInput.min;
            customStartInput.style.cssText = 'background:#151928;color:#d8dcf2;border:1px solid rgba(255,255,255,0.12);border-radius:6px;padding:5px 7px;font-size:10px;';
            customGroup.appendChild(customStartInput);

            const customEndInput = document.createElement('input');
            customEndInput.type = 'datetime-local';
            customEndInput.min = customStartInput.min;
            customEndInput.max = customStartInput.max;
            customEndInput.value = customEndInput.max;
            customEndInput.style.cssText = customStartInput.style.cssText;
            customGroup.appendChild(customEndInput);

            const customApplyBtn = document.createElement('button');
            customApplyBtn.type = 'button';
            customApplyBtn.className = 'btn btn--primary';
            customApplyBtn.style.cssText = 'padding:5px 10px;font-size:10px;';
            customApplyBtn.textContent = 'Apply';
            customGroup.appendChild(customApplyBtn);

            const statsRow = document.createElement('div');
            statsRow.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:6px;margin-bottom:8px;';
            statsRow.innerHTML = [
                `<div class="well tac" data-estats-racelog-races style="padding:8px 6px;"><div class="tsxs ttu tc-ts" style="margin-bottom:3px;">Races Graphed</div><div class="tss" style="font-weight:700;color:#d8dcf2;">—</div></div>`,
                `<div class="well tac" data-estats-racelog-avg-wpm style="padding:8px 6px;"><div class="tsxs ttu tc-ts" style="margin-bottom:3px;">Avg WPM</div><div class="tss" style="font-weight:700;color:#f3a81b;">—</div></div>`,
                `<div class="well tac" data-estats-racelog-avg-acc style="padding:8px 6px;"><div class="tsxs ttu tc-ts" style="margin-bottom:3px;">Avg Accuracy</div><div class="tss" style="font-weight:700;color:#4ade80;">—</div></div>`,
                `<div class="well tac" data-estats-racelog-date-range style="padding:8px 6px;"><div class="tsxs ttu tc-ts" style="margin-bottom:3px;">Date Range</div><div class="tss" style="font-weight:700;color:#a6aac1;">—</div></div>`,
                `<div class="well tac" data-estats-racelog-line-mode style="padding:8px 6px;">
                    <div class="tsxs ttu tc-ts" style="margin-bottom:3px;">Line Mode</div>
                    <div class="tss" style="font-weight:700;color:#a6aac1;">Smoothed (${smoothingWindow}-Race Avg)</div>
                </div>`
            ].join('');
            content.appendChild(statsRow);

            const emptyState = document.createElement('div');
            emptyState.className = 'tsxs';
            emptyState.style.cssText = 'display:none;color:#a6aac1;text-align:center;padding:18px 0 8px;';
            emptyState.textContent = 'No races matched that date range.';
            content.appendChild(emptyState);

            const canvas = document.createElement('canvas');
            canvas.width = 1120;
            canvas.height = 290;
            canvas.style.cssText = 'width:100%;height:auto;border-radius:8px;cursor:crosshair;';
            content.appendChild(canvas);

            const controls = document.createElement('div');
            controls.style.cssText = 'display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-top:6px;';

            const toggles = document.createElement('div');
            toggles.style.cssText = 'display:flex;gap:8px;flex-wrap:wrap;';
            controls.appendChild(toggles);

            const key = document.createElement('div');
            key.style.cssText = 'display:flex;gap:14px;flex-wrap:wrap;font-size:11px;color:rgba(255,255,255,0.42);align-items:center;';
            key.innerHTML = `
                <span style="display:flex;align-items:center;gap:6px;"><span style="display:inline-block;width:20px;height:2px;background:#f3a81b;"></span> Speed Trend</span>
                <span style="display:flex;align-items:center;gap:6px;"><span style="display:inline-block;width:20px;height:0;border-top:2px dashed #4ade80;"></span> Accuracy Trend</span>
            `;
            controls.appendChild(key);
            content.appendChild(controls);

            const chartState = { showSpeed: true, showAccuracy: true, useSmoothing: true, hoverMouse: null };
            const lineModeWellValue = statsRow.querySelector('[data-estats-racelog-line-mode] .tss');
            const racesWellValue = statsRow.querySelector('[data-estats-racelog-races] .tss');
            const avgWpmWellValue = statsRow.querySelector('[data-estats-racelog-avg-wpm] .tss');
            const avgAccWellValue = statsRow.querySelector('[data-estats-racelog-avg-acc] .tss');
            const dateRangeWellValue = statsRow.querySelector('[data-estats-racelog-date-range] .tss');
            let rafId = 0;
            const scheduleRedraw = () => {
                if (rafId) return;
                rafId = window.requestAnimationFrame(() => {
                    rafId = 0;
                    drawRacelogTrendChart(canvas, filteredPoints, chartState);
                });
            };
            const syncLineModeLabel = () => {
                if (!lineModeWellValue) return;
                lineModeWellValue.textContent = chartState.useSmoothing
                    ? `Smoothed (${smoothingWindow}-Race Avg)`
                    : 'Raw Race Values';
            };
            const updateSummary = () => {
                if (!filteredPoints.length) {
                    racesWellValue.textContent = '0';
                    avgWpmWellValue.textContent = '—';
                    avgAccWellValue.textContent = '—';
                    dateRangeWellValue.textContent = 'No races';
                    canvas.style.display = 'none';
                    controls.style.display = 'none';
                    emptyState.style.display = 'block';
                    return;
                }

                const avgWpm = Math.round(filteredPoints.reduce((sum, point) => sum + point.wpm, 0) / filteredPoints.length);
                const avgAcc = Math.round((filteredPoints.reduce((sum, point) => sum + point.accuracy, 0) / filteredPoints.length) * 100) / 100;
                const firstStamp = filteredPoints[0].stamp;
                const lastStamp = filteredPoints[filteredPoints.length - 1].stamp;

                racesWellValue.textContent = filteredPoints.length.toLocaleString();
                avgWpmWellValue.textContent = String(avgWpm);
                avgAccWellValue.textContent = `${avgAcc.toFixed(2)}%`;
                dateRangeWellValue.textContent = `${formatRacelogTrendLabel(firstStamp)} - ${formatRacelogTrendLabel(lastStamp)}`;
                canvas.style.display = '';
                controls.style.display = '';
                emptyState.style.display = 'none';
            };
            const presetButtons = new Map();
            const syncPresetButtons = () => {
                presetButtons.forEach((btn, key) => {
                    const active = rangeState.mode === key;
                    btn.style.opacity = active ? '1' : '0.5';
                    btn.style.background = active ? '#697187' : 'rgba(255,255,255,0.08)';
                });
            };
            const applyRangeState = () => {
                filteredPoints = filterRacelogTrendPoints(allPoints, rangeState);
                chartState.hoverMouse = null;
                updateSummary();
                scheduleRedraw();
                syncPresetButtons();
            };
            const makePresetButton = (label, mode) => {
                const btn = document.createElement('button');
                btn.type = 'button';
                btn.style.cssText = 'background:rgba(255,255,255,0.08);color:#fff;border:none;border-radius:4px;padding:4px 10px;font-size:10px;font-weight:700;cursor:pointer;letter-spacing:0.5px;text-transform:uppercase;opacity:0.5;transition:opacity 0.2s, background 0.2s;';
                btn.textContent = label;
                btn.addEventListener('click', () => {
                    rangeState.mode = mode;
                    rangeState.startStamp = null;
                    rangeState.endStamp = null;
                    applyRangeState();
                });
                presetButtons.set(mode, btn);
                presetGroup.appendChild(btn);
            };

            const makeToggle = (label, color, keyName) => {
                const toggle = document.createElement('button');
                toggle.type = 'button';
                toggle.style.cssText = `background:${color};color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:10px;font-weight:700;cursor:pointer;letter-spacing:0.5px;text-transform:uppercase;opacity:1;transition:opacity 0.2s;`;
                toggle.textContent = label;
                toggle.addEventListener('click', () => {
                    chartState[keyName] = !chartState[keyName];
                    toggle.style.opacity = chartState[keyName] ? '1' : '0.3';
                    scheduleRedraw();
                });
                toggles.appendChild(toggle);
            };

            makePresetButton('24H', '24h');
            makePresetButton('7D', '7d');
            makePresetButton('30D', '30d');
            makePresetButton('All', 'all');

            makeToggle('Speed', '#f3a81b', 'showSpeed');
            makeToggle('Accuracy', '#4ade80', 'showAccuracy');

            const modeToggle = document.createElement('button');
            modeToggle.type = 'button';
            modeToggle.style.cssText = 'background:#697187;color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:10px;font-weight:700;cursor:pointer;letter-spacing:0.5px;text-transform:uppercase;opacity:1;transition:opacity 0.2s;';
            const syncModeToggle = () => {
                modeToggle.textContent = chartState.useSmoothing ? 'Smoothed' : 'Raw';
                syncLineModeLabel();
            };
            modeToggle.addEventListener('click', () => {
                chartState.useSmoothing = !chartState.useSmoothing;
                syncModeToggle();
                scheduleRedraw();
            });
            toggles.appendChild(modeToggle);
            syncModeToggle();

            customApplyBtn.addEventListener('click', () => {
                const startStamp = parseDatetimeLocalValue(customStartInput.value);
                const endStamp = parseDatetimeLocalValue(customEndInput.value);
                if (startStamp != null && endStamp != null && startStamp > endStamp) {
                    return;
                }
                rangeState.mode = 'custom';
                rangeState.startStamp = startStamp;
                rangeState.endStamp = endStamp;
                applyRangeState();
            });

            const syncHoverMouse = (event) => {
                const rect = canvas.getBoundingClientRect();
                if (!rect.width || !rect.height) return;
                chartState.hoverMouse = {
                    x: ((event.clientX - rect.left) / rect.width) * (canvas._origW || canvas.width),
                    y: ((event.clientY - rect.top) / rect.height) * (canvas._origH || canvas.height)
                };
                scheduleRedraw();
            };

            canvas.addEventListener('mousemove', syncHoverMouse);
            canvas.addEventListener('mouseleave', () => {
                chartState.hoverMouse = null;
                scheduleRedraw();
            });

            applyRangeState();
        } catch (error) {
            console.error(LOG_PREFIX, 'Racelog trend graph error:', error);
            content.textContent = 'Failed to load racelog trend data.';
        } finally {
            if (button) {
                button.disabled = false;
                button.style.opacity = '1';
            }
        }
    }

    function injectRacelogTrendGraphButton() {
        const path = normalizePath(window.location.pathname);
        if (!path.startsWith('/racelog')) return;

        const returnBtn = document.querySelector('a.btn.btn--primary[href="/stats"]');
        if (!returnBtn) return;

        const actionWell = returnBtn.parentElement;
        if (!actionWell || actionWell.querySelector(`[${RACELOG_TREND_BTN_ATTR}]`)) return;

        rememberOriginalStyle(actionWell);
        actionWell.style.display = 'flex';
        actionWell.style.alignItems = 'center';
        actionWell.style.justifyContent = 'flex-end';
        actionWell.style.flexWrap = 'wrap';
        actionWell.style.gap = '8px';

        const trendBtn = document.createElement('button');
        trendBtn.type = 'button';
        trendBtn.className = 'btn btn--primary';
        trendBtn.setAttribute(RACELOG_TREND_BTN_ATTR, '');
        trendBtn.innerHTML = `<span style="display:flex;align-items:center;gap:6px;"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 17 9 11 13 15 21 7"></polyline><polyline points="14 7 21 7 21 14"></polyline></svg><span>Trend Graph</span></span>`;
        trendBtn.addEventListener('click', () => {
            void openRacelogTrendGraph(trendBtn);
        });
        actionWell.insertBefore(trendBtn, returnBtn);
    }

    function getActiveRacelogPageNumber() {
        const activeBtn = document.querySelector('.btn--page.is-active');
        const parsed = parseInt(activeBtn?.textContent || '1', 10);
        return Number.isFinite(parsed) && parsed > 0 ? parsed - 1 : 0;
    }

    function parseRacelogResultsRowMetrics(row) {
        if (!row) return null;

        const stats = Array.from(row.querySelectorAll('.list-item')).map((node) => node.textContent.replace(/\s+/g, ' ').trim());
        let wpm = null;
        let accuracy = null;
        let secs = null;

        stats.forEach((text) => {
            const wpmMatch = text.match(/([0-9]+(?:\.[0-9]+)?)\s*WPM/i);
            if (wpmMatch) {
                wpm = Number(wpmMatch[1]);
            }

            if (!/^n\/a/i.test(text)) {
                const accMatch = text.match(/([0-9]+(?:\.[0-9]+)?)\s*%?\s*Acc/i);
                if (accMatch) {
                    accuracy = Number(accMatch[1]);
                }
            }

            const secsMatch = text.match(/([0-9]+(?:\.[0-9]+)?)\s*secs/i);
            if (secsMatch) {
                secs = Number(secsMatch[1]);
            }
        });

        let placed = null;
        if (row.querySelector('.raceResults-placement-winner')) {
            placed = 1;
        } else {
            const placeText = row.querySelector('.raceResults-placement-other')?.textContent || '';
            const placeMatch = placeText.match(/(\d+)/);
            if (placeMatch) {
                placed = Number(placeMatch[1]);
            }
        }

        return {
            title: row.querySelector('.player-name--container[title]')?.getAttribute('title') || '',
            wpm,
            accuracy,
            secs,
            placed
        };
    }

    function computeRaceResultPoints(wpm, accuracyPercent) {
        if (!Number.isFinite(wpm) || !Number.isFinite(accuracyPercent)) return null;
        return Math.max(0, Math.round((accuracyPercent / 100) * (100 + (wpm / 2))));
    }

    function findMatchingRacelogSelfLog(logs, metrics) {
        if (!Array.isArray(logs) || !metrics) return null;

        return logs.find((log) => {
            const typed = Number(log?.typed);
            const secs = Number(log?.secs);
            const errs = Number(log?.errs);
            const placed = Number(log?.placed);

            if (Number.isFinite(metrics.placed) && Number.isFinite(placed) && placed !== metrics.placed) {
                return false;
            }

            const logWpm = Number.isFinite(typed) && Number.isFinite(secs) && secs > 0
                ? Math.round((typed / 5) / (secs / 60))
                : null;
            const logAcc = Number.isFinite(typed) && typed > 0 && Number.isFinite(errs)
                ? Math.round((((typed - errs) * 100) / typed) * 100) / 100
                : null;

            const wpmMatches = metrics.wpm == null || logWpm === metrics.wpm;
            const secsMatches = metrics.secs == null || (Number.isFinite(secs) && Math.abs(secs - metrics.secs) <= 0.051);
            const accMatches = metrics.accuracy == null || (logAcc != null && Math.abs(logAcc - metrics.accuracy) <= 0.02);
            return wpmMatches && secsMatches && accMatches;
        }) || null;
    }

    function getRacelogResultsModalSignature(results) {
        const selfRow = results?.querySelector('.gridTable-row.is-self') || results?.querySelector('.gridTable-row');
        const metrics = parseRacelogResultsRowMetrics(selfRow);
        if (!metrics) return '';
        return [
            metrics.title,
            metrics.wpm ?? '',
            metrics.accuracy ?? '',
            metrics.secs ?? '',
            metrics.placed ?? ''
        ].join('|');
    }

    function appendRacelogModalSummary(row, parts) {
        if (!row || !Array.isArray(parts) || parts.length === 0) return;
        const mainCell = row.querySelectorAll('.gridTable-cell')[1] || row.querySelector('.gridTable-cell');
        const splitReverse = mainCell?.querySelector('.split.split--flag.split--reverse');
        const statsList = splitReverse?.querySelector('.list.list--inline.list--flag');
        if (!mainCell || !splitReverse || !statsList) return;

        applyRaceResultStatsListLayout(mainCell, statsList);

        const expandedRow = document.createElement('div');
        expandedRow.className = 'tsxs db';
        expandedRow.setAttribute(RACELOG_MODAL_ITEM_ATTR, '');
        expandedRow.style.cssText = 'grid-column:1 / -1;padding:2px 16px 8px 0;box-sizing:border-box;';

        const expandedList = statsList.cloneNode(true);
        expandedList.style.cssText = 'display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:2px;';
        parts.forEach((partHtml) => {
            const item = document.createElement('div');
            item.className = 'list-item';
            item.innerHTML = partHtml;
            expandedList.appendChild(item);
        });
        expandedRow.appendChild(expandedList);
        row.appendChild(expandedRow);

        if (!splitReverse.hasAttribute(RACELOG_MODAL_SPLIT_DISPLAY_ATTR)) {
            splitReverse.setAttribute(RACELOG_MODAL_SPLIT_DISPLAY_ATTR, splitReverse.style.getPropertyValue('display') || '');
        }
        splitReverse.style.setProperty('display', 'none');

        const rowRect = row.getBoundingClientRect();
        const expandedRect = expandedRow.getBoundingClientRect();
        const overflow = Math.max(0, expandedRect.bottom - rowRect.bottom);
        if (!row.hasAttribute(RACELOG_MODAL_ROW_HEIGHT_ATTR)) {
            row.setAttribute(RACELOG_MODAL_ROW_HEIGHT_ATTR, row.style.getPropertyValue('height') || '');
        }
        if (!row.hasAttribute(RACELOG_MODAL_ROW_MIN_HEIGHT_ATTR)) {
            row.setAttribute(RACELOG_MODAL_ROW_MIN_HEIGHT_ATTR, row.style.getPropertyValue('min-height') || '');
        }
        if (overflow > 0) {
            const newHeight = Math.ceil(rowRect.height + overflow + 2);
            row.style.setProperty('height', `${newHeight}px`, 'important');
            row.style.setProperty('min-height', `${newHeight}px`, 'important');
        }
    }

    function restoreRacelogModalRowHeights(results) {
        if (!results) return;
        results.querySelectorAll('.gridTable-row').forEach((row) => {
            if (row.hasAttribute(RACELOG_MODAL_ROW_HEIGHT_ATTR)) {
                const originalHeight = row.getAttribute(RACELOG_MODAL_ROW_HEIGHT_ATTR) || '';
                if (originalHeight) {
                    row.style.setProperty('height', originalHeight);
                } else {
                    row.style.removeProperty('height');
                }
                row.removeAttribute(RACELOG_MODAL_ROW_HEIGHT_ATTR);
            }
            if (row.hasAttribute(RACELOG_MODAL_ROW_MIN_HEIGHT_ATTR)) {
                const originalMinHeight = row.getAttribute(RACELOG_MODAL_ROW_MIN_HEIGHT_ATTR) || '';
                if (originalMinHeight) {
                    row.style.setProperty('min-height', originalMinHeight);
                } else {
                    row.style.removeProperty('min-height');
                }
                row.removeAttribute(RACELOG_MODAL_ROW_MIN_HEIGHT_ATTR);
            }
        });
        results.querySelectorAll(`.split.split--flag.split--reverse[${RACELOG_MODAL_SPLIT_DISPLAY_ATTR}]`).forEach((splitReverse) => {
            const originalDisplay = splitReverse.getAttribute(RACELOG_MODAL_SPLIT_DISPLAY_ATTR) || '';
            if (originalDisplay) {
                splitReverse.style.setProperty('display', originalDisplay);
            } else {
                splitReverse.style.removeProperty('display');
            }
            splitReverse.removeAttribute(RACELOG_MODAL_SPLIT_DISPLAY_ATTR);
        });
    }

    async function enhanceOpenRacelogResultsModal() {
        const results = document.querySelector('.modal--raceResults.is-active .raceResults');
        if (!results) return;

        const signature = getRacelogResultsModalSignature(results);
        if (!signature) return;
        if (results.getAttribute(RACELOG_MODAL_ATTR) === signature) return;

        restoreRacelogModalRowHeights(results);
        results.querySelectorAll(`[${RACELOG_MODAL_ITEM_ATTR}]`).forEach((el) => el.remove());

        const selfMetrics = parseRacelogResultsRowMetrics(results.querySelector('.gridTable-row.is-self'));
        let selfLog = null;
        try {
            const logs = await fetchRacelogPage(getActiveRacelogPageNumber());
            selfLog = findMatchingRacelogSelfLog(logs, selfMetrics);
        } catch (error) {
            console.error(LOG_PREFIX, 'Racelog modal fetch error:', error);
        }

        results.querySelectorAll('.gridTable-row').forEach((row) => {
            const metrics = parseRacelogResultsRowMetrics(row);
            const summaryParts = [];
            const points = computeRaceResultPoints(metrics?.wpm, metrics?.accuracy);
            if (points != null) {
                summaryParts.push(`<span>${escapeHtml(String(points))}</span> <span class="tc-ts">Points</span>`);
            }

            if (row.classList.contains('is-self') && selfLog) {
                const errors = Number(selfLog.errs);
                const nitros = Number(selfLog.nitros);
                const typed = Number(selfLog.typed);
                if (Number.isFinite(errors)) {
                    summaryParts.push(`<span>${escapeHtml(String(errors))}</span> <span class="tc-ts">Errors</span>`);
                }
                if (Number.isFinite(nitros)) {
                    summaryParts.push(`<span>${escapeHtml(String(nitros))}</span> <span class="tc-ts">Nitro${nitros === 1 ? '' : 's'}</span>`);
                }
                if (Number.isFinite(typed) && typed > 0) {
                    summaryParts.push(`<span>${escapeHtml(String(typed))}</span> <span class="tc-ts">Length</span>`);
                }
            }

            appendRacelogModalSummary(row, summaryParts);
        });

        results.setAttribute(RACELOG_MODAL_ATTR, signature);
    }

    // ── Shared helpers ──
    function calcWPM(r) { return r.secs > 0 ? Math.round(((r.typed || 0) / 5) / (r.secs / 60)) : 0; }
    function calcAcc(r) { return r.typed > 0 ? ((r.typed - (r.errs || 0)) / r.typed) * 100 : 0; }
    function ordinal(n) {
        if (n >= 11 && n <= 13) return 'th';
        switch (n % 10) { case 1: return 'st'; case 2: return 'nd'; case 3: return 'rd'; default: return 'th'; }
    }
    function fmtDate(stamp) {
        const d = new Date(stamp * 1000);
        const mo = String(d.getMonth() + 1).padStart(2, '0');
        const da = String(d.getDate()).padStart(2, '0');
        const yr = String(d.getFullYear()).slice(2);
        let hr = d.getHours();
        const ap = hr >= 12 ? 'pm' : 'am';
        hr = hr % 12 || 12;
        const mn = String(d.getMinutes()).padStart(2, '0');
        return `${mo}/${da}/${yr} ${hr}:${mn} <span class="tsxs tc-ts ttu">${ap}</span>`;
    }
    function fmtDateShort(stamp) {
        const d = new Date(stamp * 1000);
        return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}/${String(d.getFullYear()).slice(2)}`;
    }
    function placeHTML(place) {
        const isFirst = place === 1;
        const pc = isFirst ? 'tc-lemon' : '';
        const sc = isFirst ? 'tc-lemon' : 'tc-ts';
        return `<span class="${pc}">${place}</span><span class="tsxs ttu ${sc}">${ordinal(place)}</span>`;
    }

    // ── Detect table type from headers ──
    function detectTableType(table) {
        const headers = Array.from(table.querySelectorAll('thead th.table-cell')).map(h => h.textContent.trim().replace(/[▲▼]/g, '').trim());
        if (table.classList.contains('table--selectable')) return 'racelog';
        if (headers[0] === 'Month') return 'monthly';
        if (headers.includes('Speed')) return 'topSpeed';
        if (headers.includes('Races')) return 'daily';
        return 'unknown';
    }

    function isElementVisible(el) {
        return !!(el && (el.offsetParent !== null || el.getClientRects().length > 0));
    }

    function restoreVisibleNativePagination() {
        document.querySelectorAll('.has-btn.has-btn--s').forEach(el => {
            if (!el.hasAttribute('data-estats-sort-pagination')) {
                el.style.display = '';
            }
        });
    }

    function cleanupDetachedSortPagination() {
        let removedAny = false;
        document.querySelectorAll('[data-estats-sort-pagination]').forEach(wrapper => {
            const ownerId = wrapper.getAttribute('data-estats-sort-owner-id');
            if (!ownerId) return;
            const ownerTable = document.querySelector(`table.table--striped[data-estats-sort-owner-id="${ownerId}"]`);
            const ownerActive = !!(ownerTable && ownerTable.hasAttribute('data-estats-sort-active'));
            if (!ownerTable || !isElementVisible(ownerTable) || !ownerActive) {
                wrapper.remove();
                removedAny = true;
            }
        });

        if (removedAny && !document.querySelector('[data-estats-sort-pagination]')) {
            restoreVisibleNativePagination();
        }
    }

    function scheduleSortPaginationCleanup() {
        if (_deferredSortCleanupTimer) {
            clearTimeout(_deferredSortCleanupTimer);
        }
        _deferredSortCleanupTimer = setTimeout(() => {
            _deferredSortCleanupTimer = null;
            cleanupDetachedSortPagination();
        }, 50);
    }

    function getEnhancedRacelogWorkKey(path) {
        const racelogTable = document.querySelector('.table--striped.table--selectable');
        const rows = racelogTable ? Array.from(racelogTable.querySelectorAll('tbody .table-row')) : [];
        const pageNum = racelogTable ? getActiveRacelogPageNumber() : 0;
        const pageKey = String(pageNum);
        const activeModalResults = document.querySelector('.modal--raceResults.is-active .raceResults');
        const modalSignature = activeModalResults ? getRacelogResultsModalSignature(activeModalResults) : '';
        const mainHydrated = !!(racelogTable
            && racelogTable.getAttribute(RACELOG_PAGE_ATTR) === pageKey
            && rows.length > 0
            && rows.every((row) => row.getAttribute(RACELOG_ROW_PAGE_ATTR) === pageKey && row.querySelectorAll('td[data-estats-injected]').length === 3));
        const sortable = !!(racelogTable && racelogTable.hasAttribute('data-estats-sortable'));
        const sortPaginationCount = document.querySelectorAll('[data-estats-sort-pagination]').length;
        return JSON.stringify({
            path,
            pageKey,
            rowCount: rows.length,
            mainHydrated,
            sortable,
            modalSignature,
            modalActive: !!activeModalResults,
            stripedTables: document.querySelectorAll('.table--striped').length,
            sortPaginationCount
        });
    }

    function resetAllRacelogTabSortState() {
        let removedAny = false;

        Array.from(_sortableTables).forEach(table => {
            if (!table || !table.isConnected) {
                _sortableTables.delete(table);
                return;
            }
            if (!table.hasAttribute('data-estats-sort-active')) return;

            const resetFn = table.__ntEstatsResetSort;
            if (typeof resetFn === 'function') {
                resetFn();
            } else {
                table.removeAttribute('data-estats-sort-active');
                table.querySelectorAll('[data-estats-sort-arrow]').forEach(el => el.remove());
            }
            removedAny = true;
        });

        if (removedAny) {
            document.querySelectorAll('[data-estats-sort-pagination]').forEach(el => el.remove());
            restoreVisibleNativePagination();
        }
    }

    function bindRacelogTabReset() {
        const tabsRoot = document.querySelector('.tabs.tabs--a.tabs--fourUp');
        if (!tabsRoot || tabsRoot.hasAttribute('data-estats-tab-reset-bound')) return;
        tabsRoot.setAttribute('data-estats-tab-reset-bound', '');

        tabsRoot.addEventListener('click', (event) => {
            const tabBtn = event.target.closest('button.tab');
            if (!tabBtn || tabBtn.classList.contains('is-active')) return;
            resetAllRacelogTabSortState();
        }, true);
    }

    // ── Build row for daily summary (pre-aggregated from API type=lastdays) ──
    // Fields: stamp, races, placed (total), secs, nitros, errs, typed, reward.money, reward.exp
    function buildDailyRow(g) {
        const tr = document.createElement('tr');
        tr.className = 'table-row';
        const avgPlace = g.races > 0 ? Math.round(g.placed / g.races) : 0;
        tr.innerHTML = `
            <td class="table-cell"><span>${fmtDateShort(g.stamp)}</span></td>
            <td class="table-cell">${(g.races || 0).toLocaleString()}</td>
            <td class="table-cell">${placeHTML(avgPlace)}</td>
            <td class="table-cell">${(g.secs || 0).toLocaleString()}<span class="tsxxs tc-ts mlxxs tc-ts ttu"> secs</span></td>
            <td class="table-cell">${(g.nitros || 0).toLocaleString()}</td>
            <td class="table-cell"><span class="tc-emerald as-nitro-cash--prefix"><span class="as-nitro-cash--prefix">$${(g.reward?.money || 0).toLocaleString()}</span></span></td>
        `;
        return tr;
    }

    // ── Build row for monthly summary (pre-aggregated from API type=bymonth) ──
    // Fields: month, year, races, placed (total), secs, nitros, errs, typed, reward.money, reward.exp
    function buildMonthlyRow(g) {
        const tr = document.createElement('tr');
        tr.className = 'table-row';
        const avgPlace = g.races > 0 ? Math.round(g.placed / g.races) : 0;
        const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
        const monthStr = `${months[(g.month || 1) - 1]} '${String(g.year || 0).slice(2)}`;
        tr.innerHTML = `
            <td class="table-cell"><span>${monthStr}</span></td>
            <td class="table-cell">${(g.races || 0).toLocaleString()}</td>
            <td class="table-cell">${placeHTML(avgPlace)}</td>
            <td class="table-cell">${(g.secs || 0).toLocaleString()}<span class="tsxxs tc-ts mlxxs tc-ts ttu"> secs</span></td>
            <td class="table-cell">${(g.nitros || 0).toLocaleString()}</td>
            <td class="table-cell"><span class="tc-emerald as-nitro-cash--prefix"><span class="as-nitro-cash--prefix">$${(g.reward?.money || 0).toLocaleString()}</span></span></td>
        `;
        return tr;
    }

    // ── Build row for top speeds (from API type=topspeeds) ──
    // Fields: speed, stamp, place, typed, secs, errs (no nitros or reward)
    function buildTopSpeedRow(r) {
        const tr = document.createElement('tr');
        tr.className = 'table-row';
        const acc = r.typed > 0 ? ((r.typed - (r.errs || 0)) / r.typed) * 100 : 0;
        const accStr = acc % 1 === 0 ? String(Math.round(acc)) : acc.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
        tr.innerHTML = `
            <td class="table-cell"><span>${fmtDate(r.stamp || 0)}</span></td>
            <td class="table-cell">${placeHTML(r.place || 0)}</td>
            <td class="table-cell">${r.secs || 0}<span class="tsxxs tc-ts mlxxs tc-ts ttu"> secs</span></td>
            <td class="table-cell">${accStr}<span class="tsxs tc-ts">%</span></td>
            <td class="table-cell">${r.speed || 0}<span class="tsxxs tc-ts mlxxs">WPM</span></td>
            <td class="table-cell" data-estats-injected="" style="color:${(r.errs||0) > 0 ? '#d62f3a' : '#a6aac1'};">${r.errs || 0}</td>
            <td class="table-cell" data-estats-injected="" style="color:#a6aac1;">${(r.typed||0) > 0 ? r.typed.toLocaleString() : '\u2014'}</td>
        `;
        return tr;
    }

    // ── Build row for main racelog ──
    function buildRacelogRow(r) {
        const tr = document.createElement('tr');
        tr.className = 'table-row';
        const wpm = calcWPM(r);
        const acc = calcAcc(r);
        const accStr = acc % 1 === 0 ? String(Math.round(acc)) : acc.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
        const errors = r.errs ?? 0;
        const length = r.typed ?? 0;
        const money = r.reward?.money || 0;
        const xp = r.reward?.exp || 0;
        const nitroUsed = (r.nitros ?? 0) > 0;
        tr.innerHTML = `
            <td class="table-cell"><span class="tsxs tc-ts ttu">#</span>${(r._raceNum || 0).toLocaleString()}</td>
            <td class="table-cell">${fmtDate(r.stamp || 0)}</td>
            <td class="table-cell">${placeHTML(r.placed || 0)} / 5</td>
            <td class="table-cell">${wpm}<span class="tsxxs tc-ts mlxxs">WPM</span></td>
            <td class="table-cell">${accStr}<span class="tsxs tc-ts">%</span></td>
            <td class="table-cell">${r.secs || 0}<span class="tsxxs tc-ts mlxxs tc-ts ttu"> secs</span></td>
            <td class="table-cell"><span class="tc-emerald as-nitro-cash--prefix"><span class="as-nitro-cash--prefix">$${money.toLocaleString()}</span></span></td>
            <td class="table-cell">${xp.toLocaleString()}</td>
            <td class="table-cell" data-estats-injected="" style="color:${errors > 0 ? '#d62f3a' : '#a6aac1'};">${errors}</td>
            <td class="table-cell" data-estats-injected="" style="color:#a6aac1;">${length > 0 ? length.toLocaleString() : '\u2014'}</td>
            <td class="table-cell" data-estats-injected="" style="color:${nitroUsed ? '#f3a81b' : '#a6aac1'};">${nitroUsed ? 'Yes' : 'No'}</td>
        `;
        return tr;
    }

    // ── Sort key configs per table type (matching actual API field names) ──
    const SORT_KEYS = {
        daily: {
            0: g => g.stamp || 0,                                    // Date
            1: g => g.races || 0,                                    // Races
            2: g => g.races > 0 ? g.placed / g.races : 0,           // Avg Place
            3: g => g.secs || 0,                                     // Total Time
            4: g => g.nitros || 0,                                   // Nitros Used
            5: g => g.reward?.money || 0                             // Money Won
        },
        monthly: {
            0: g => (g.year || 0) * 12 + (g.month || 0),            // Month (chronological)
            1: g => g.races || 0,
            2: g => g.races > 0 ? g.placed / g.races : 0,
            3: g => g.secs || 0,
            4: g => g.nitros || 0,
            5: g => g.reward?.money || 0
        },
        topSpeed: {
            0: r => r.stamp || 0,                                    // Date
            1: r => r.place || 0,                                    // Place
            2: r => r.secs || 0,                                     // Time
            3: r => calcAcc(r),                                      // Accuracy
            4: r => r.speed || 0,                                    // Speed (pre-computed WPM)
            5: r => r.errs || 0,                                     // Errors (injected)
            6: r => r.typed || 0                                     // Length (injected)
        },
        racelog: {
            0: r => r._raceNum || 0,
            1: r => r.stamp || 0,
            2: r => r.placed || 0,
            3: r => calcWPM(r),
            4: r => calcAcc(r),
            5: r => r.secs || 0,
            6: r => r.reward?.money || 0,
            7: r => r.reward?.exp || 0,
            8: r => r.errs || 0,
            9: r => r.typed || 0,
            10: r => (r.nitros||0)>0?1:0
        }
    };

    const ROW_BUILDERS = { daily: buildDailyRow, monthly: buildMonthlyRow, topSpeed: buildTopSpeedRow, racelog: buildRacelogRow };

    // ── Universal cross-page sorting for any table type ──
    function setupCrossPageSorting(table, tableType) {
        if (table.hasAttribute('data-estats-sortable')) return;
        table.setAttribute('data-estats-sortable', '');
        _sortableTables.add(table);
        if (!table.hasAttribute('data-estats-sort-owner-id')) {
            _sortOwnerCounter += 1;
            table.setAttribute('data-estats-sort-owner-id', `estats-sort-${_sortOwnerCounter}`);
        }

        const thead = table.querySelector('thead .table-row');
        const tbody = table.querySelector('tbody');
        if (!thead || !tbody) return;

        const headers = thead.querySelectorAll('th.table-cell');
        let currentSort = { col: -1, asc: true };
        let fetchedData = null;
        let isFetching = false;
        let sortedPage = 0;
        const originalTbodyHTML = tbody.innerHTML;
        const sortOwnerId = table.getAttribute('data-estats-sort-owner-id');

        // Find ALL NT pagination elements near this table, walking up the DOM
        const getNtPagination = () => {
            let parent = table.parentElement;
            for (let i = 0; i < 5 && parent; i++) {
                const found = Array.from(parent.querySelectorAll('.has-btn.has-btn--s'))
                    .filter(el => !el.hasAttribute('data-estats-sort-pagination'));
                if (found.length > 0) return found;
                parent = parent.parentElement;
            }
            return [];
        };

        const sortKeys = SORT_KEYS[tableType] || {};
        const buildRow = ROW_BUILDERS[tableType];
        const colCount = headers.length;
        const countLabel = tableType === 'racelog' ? 'races' : (tableType === 'daily' ? 'days' : (tableType === 'monthly' ? 'months' : 'races'));

        function renderPage(sorted, page) {
            tbody.innerHTML = '';
            const start = page * PER_PAGE;
            sorted.slice(start, start + PER_PAGE).forEach(r => tbody.appendChild(buildRow(r)));
            sortedPage = page;
        }

        function buildPagination(sorted) {
            // Remove previous sorted pagination (may be in NT's pagination parent, not table parent)
            const ntPags = getNtPagination();
            const searchRoot = (ntPags.length > 0 ? ntPags[0].parentElement : null) || table.parentElement;
            const prev = searchRoot.querySelector('[data-estats-sort-pagination]');
            if (prev) prev.remove();
            const totalPages = Math.ceil(sorted.length / PER_PAGE);
            if (totalPages <= 1) return;

            const wrapper = document.createElement('div');
            wrapper.setAttribute('data-estats-sort-pagination', '');
            wrapper.setAttribute('data-estats-sort-owner-id', sortOwnerId);
            wrapper.className = 'has-btn has-btn--s has-btn--wrap';
            wrapper.style.cssText = 'margin-top:12px;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:4px;';

            function addBtn(label, pg, isActive, isOutline) {
                const btn = document.createElement('button');
                btn.className = isOutline ? 'btn btn--outline' : ('btn btn--page' + (isActive ? ' is-active' : ''));
                btn.textContent = label;
                btn.style.cursor = 'pointer';
                if (!isActive) btn.addEventListener('click', () => { renderPage(sorted, pg); buildPagination(sorted); });
                return btn;
            }

            wrapper.appendChild(addBtn('First', 0, false, false));
            const pagesDiv = document.createElement('div');
            pagesDiv.className = 'has-btn has-btn--xs has-btn--wrap mrxs';
            let startP = Math.max(0, sortedPage - 4), endP = Math.min(totalPages, startP + 10);
            if (endP - startP < 10) startP = Math.max(0, endP - 10);
            for (let p = startP; p < endP; p++) pagesDiv.appendChild(addBtn(String(p + 1), p, p === sortedPage, false));
            wrapper.appendChild(pagesDiv);
            wrapper.appendChild(addBtn('Last', totalPages - 1, false, false));

            const resetBtn = document.createElement('button');
            resetBtn.className = 'btn btn--page';
            resetBtn.textContent = 'Reset';
            resetBtn.style.cssText = 'margin-left:8px;cursor:pointer;color:#d62f3a;';
            resetBtn.addEventListener('click', resetSort);
            wrapper.appendChild(resetBtn);

            const ct = document.createElement('span');
            ct.className = 'tsxs tc-ts';
            ct.style.cssText = 'margin-left:8px;';
            ct.textContent = `${sorted.length.toLocaleString()} ${countLabel}`;
            wrapper.appendChild(ct);

            // Insert in the same location as NT's native pagination
            const ntInsertRef = getNtPagination();
            if (ntInsertRef.length > 0) {
                ntInsertRef[0].parentElement.insertBefore(wrapper, ntInsertRef[0]);
            } else {
                table.parentElement.appendChild(wrapper);
            }
        }

        function resetSort() {
            currentSort = { col: -1, asc: true };
            tbody.innerHTML = originalTbodyHTML;
            table.removeAttribute('data-estats-sort-active');
            headers.forEach(h => { const a = h.querySelector('[data-estats-sort-arrow]'); if (a) a.remove(); });
            // Remove our pagination from wherever it was inserted
            const searchRoot2 = (getNtPagination()[0]?.parentElement) || table.parentElement;
            const sp = searchRoot2.querySelector('[data-estats-sort-pagination]') ||
                       document.querySelector('[data-estats-sort-pagination]');
            if (sp) sp.remove();
            restoreVisibleNativePagination();
        }

        table.__ntEstatsResetSort = resetSort;

        headers.forEach((th, colIdx) => {
            th.style.cursor = 'pointer';
            th.style.userSelect = 'none';
            th.style.whiteSpace = 'nowrap';
            th.title = 'Click to sort';

            th.addEventListener('click', async () => {
                if (isFetching) return;
                const asc = (currentSort.col === colIdx) ? !currentSort.asc : (colIdx === 0);
                currentSort = { col: colIdx, asc };

                headers.forEach((h, i) => {
                    const existing = h.querySelector('[data-estats-sort-arrow]');
                    if (existing) existing.remove();
                    if (i === colIdx) {
                        const arrow = document.createElement('span');
                        arrow.setAttribute('data-estats-sort-arrow', '');
                        arrow.style.cssText = 'margin-left:4px;font-size:10px;';
                        arrow.textContent = asc ? '▲' : '▼';
                        h.appendChild(arrow);
                    }
                });

                if (!fetchedData) {
                    tbody.innerHTML = `<tr class="table-row"><td class="table-cell" colspan="${colCount}" style="text-align:center;padding:20px;color:#a6aac1;">Loading all data...</td></tr>`;
                }

                try {
                    isFetching = true;
                    if (!fetchedData) {
                        fetchedData = await fetchAllDataForType(tableType);
                    }
                    isFetching = false;

                    if (!fetchedData || fetchedData.length === 0) return;

                    const keyFn = sortKeys[colIdx] || (() => 0);
                    const sorted = [...fetchedData].sort((a, b) => {
                        const va = keyFn(a), vb = keyFn(b);
                        return asc ? va - vb : vb - va;
                    });

                    table.setAttribute('data-estats-sort-active', '');
                    getNtPagination().forEach(el => { el.style.display = 'none'; });
                    renderPage(sorted, 0);
                    buildPagination(sorted);
                } catch (e) {
                    isFetching = false;
                    console.error(LOG_PREFIX, 'Sort fetch error:', e);
                    tbody.innerHTML = `<tr class="table-row"><td class="table-cell" colspan="${colCount}" style="text-align:center;padding:20px;color:#d62f3a;">Failed to load data for sorting</td></tr>`;
                }
            });
        });
    }

    async function handleEnhancedRacelog() {
        if (!isFeatureEnabled('ENABLE_ENHANCED_RACELOG')) return;

        const path = normalizePath(window.location.pathname);
        if (!path.startsWith('/racelog') && !path.startsWith('/stats')) return;

        if (path.startsWith('/stats')) {
            await enhanceOpenRacelogResultsModal();
            cleanupDetachedSortPagination();
            scheduleSortPaginationCleanup();
            return;
        }

        injectRacelogTrendGraphButton();

        const workKey = getEnhancedRacelogWorkKey(path);
        if (workKey === _lastEnhancedRacelogWorkKey) {
            return;
        }
        _lastEnhancedRacelogWorkKey = workKey;

        cleanupDetachedSortPagination();
        scheduleSortPaginationCleanup();
        bindRacelogTabReset();

        // ── Inject extra column helpers ──
        function injectHeaders(table, labels) {
            const thead = table.querySelector('thead .table-row');
            if (!thead || thead.querySelector('[data-estats-injected]')) return;
            labels.forEach(label => {
                const th = document.createElement('th');
                th.className = 'table-cell';
                th.setAttribute('data-estats-injected', '');
                th.textContent = label;
                thead.appendChild(th);
            });
        }

        function injectRacelogCells(row, race) {
            row.querySelectorAll('td[data-estats-injected]').forEach((cell) => cell.remove());
            const errors = race.errs ?? 0;
            const length = race.typed ?? 0;
            const nitroUsed = (race.nitros ?? 0) > 0;

            const errTd = document.createElement('td');
            errTd.className = 'table-cell';
            errTd.setAttribute('data-estats-injected', '');
            errTd.style.color = errors > 0 ? '#d62f3a' : '#a6aac1';
            errTd.textContent = String(errors);
            row.appendChild(errTd);

            const lenTd = document.createElement('td');
            lenTd.className = 'table-cell';
            lenTd.setAttribute('data-estats-injected', '');
            lenTd.style.color = '#a6aac1';
            lenTd.textContent = length > 0 ? length.toLocaleString() : '\u2014';
            row.appendChild(lenTd);

            const nitTd = document.createElement('td');
            nitTd.className = 'table-cell';
            nitTd.setAttribute('data-estats-injected', '');
            nitTd.style.color = nitroUsed ? '#f3a81b' : '#a6aac1';
            nitTd.textContent = nitroUsed ? 'Yes' : 'No';
            row.appendChild(nitTd);
        }

        function injectRacelogPlaceholderCells(row) {
            if (!row) return;
            row.querySelectorAll('td[data-estats-injected]').forEach((cell) => cell.remove());

            const placeholderValues = ['\u2014', '\u2014', '\u2014'];
            placeholderValues.forEach((text) => {
                const td = document.createElement('td');
                td.className = 'table-cell';
                td.setAttribute('data-estats-injected', '');
                td.style.color = '#a6aac1';
                td.textContent = text;
                row.appendChild(td);
            });
        }

        function clearRacelogCells(row) {
            if (!row) return;
            row.querySelectorAll('td[data-estats-injected]').forEach((cell) => cell.remove());
            row.removeAttribute(RACELOG_ROW_PAGE_ATTR);
        }

        async function hydrateMainRacelogTable(table) {
            if (!table) return;

            injectHeaders(table, ['Errors', 'Length', 'Nitro']);

            const rows = Array.from(table.querySelectorAll('tbody .table-row'));
            if (!rows.length) return;
            if (table.hasAttribute('data-estats-sort-active')) return;

            const pageNum = getActiveRacelogPageNumber();
            const pageKey = String(pageNum);
            if (table.getAttribute(RACELOG_HYDRATION_ATTR) === pageKey) {
                return;
            }
            const alreadyHydrated = table.getAttribute(RACELOG_PAGE_ATTR) === pageKey
                && rows.every((row) => row.getAttribute(RACELOG_ROW_PAGE_ATTR) === pageKey && row.querySelectorAll('td[data-estats-injected]').length === 3);
            if (alreadyHydrated) {
                return;
            }

            rows.forEach((row) => {
                injectRacelogPlaceholderCells(row);
                row.removeAttribute(RACELOG_ROW_PAGE_ATTR);
            });
            table.setAttribute(RACELOG_HYDRATION_ATTR, pageKey);

            try {
                const logs = await fetchRacelogPage(pageNum);
                if (table.getAttribute(RACELOG_HYDRATION_ATTR) !== pageKey) {
                    return;
                }

                const liveRows = Array.from(table.querySelectorAll('tbody .table-row'));
                liveRows.forEach((row, i) => {
                    const race = logs[i];
                    if (race) {
                        injectRacelogCells(row, race);
                        row.setAttribute(RACELOG_ROW_PAGE_ATTR, pageKey);
                    } else {
                        clearRacelogCells(row);
                    }
                });
                table.setAttribute(RACELOG_PAGE_ATTR, pageKey);
            } catch (e) {
                if (table.getAttribute(RACELOG_HYDRATION_ATTR) === pageKey) {
                    console.error(LOG_PREFIX, 'Racelog column injection error:', e);
                }
            } finally {
                if (table.getAttribute(RACELOG_HYDRATION_ATTR) === pageKey) {
                    table.removeAttribute(RACELOG_HYDRATION_ATTR);
                }
            }
        }

        function injectTopSpeedCells(row, race) {
            if (row.querySelector('[data-estats-injected]')) return;
            const errors = race.errs ?? 0;
            const length = race.typed ?? 0;

            const errTd = document.createElement('td');
            errTd.className = 'table-cell';
            errTd.setAttribute('data-estats-injected', '');
            errTd.style.color = errors > 0 ? '#d62f3a' : '#a6aac1';
            errTd.textContent = String(errors);
            row.appendChild(errTd);

            const lenTd = document.createElement('td');
            lenTd.className = 'table-cell';
            lenTd.setAttribute('data-estats-injected', '');
            lenTd.style.color = '#a6aac1';
            lenTd.textContent = length > 0 ? length.toLocaleString() : '\u2014';
            row.appendChild(lenTd);
        }

        // ── Set up sorting on daily and monthly tables (no extra columns) ──
        document.querySelectorAll('.table--striped').forEach(t => {
            if (t.hasAttribute('data-estats-sortable')) return;
            const type = detectTableType(t);
            if (type === 'daily' || type === 'monthly') {
                setupCrossPageSorting(t, type);
            }
        });

        // ── Main racelog table: inject 3 extra columns (Errors, Length, Nitro) ──
        const racelogTable = document.querySelector('.table--striped.table--selectable');
        if (racelogTable) {
            if (!racelogTable.hasAttribute(RACELOG_ATTR)) {
                racelogTable.setAttribute(RACELOG_ATTR, '');
            }

            await hydrateMainRacelogTable(racelogTable);

            // Set up sorting AFTER columns are injected so all 11 headers get handlers
            if (!racelogTable.hasAttribute('data-estats-sortable')) {
                setupCrossPageSorting(racelogTable, 'racelog');
            }
        }

        // ── Top speeds table: inject 2 extra columns (Errors, Length — no Nitro in API) ──
        document.querySelectorAll('.table--striped').forEach(t => {
            if (t === racelogTable) return;
            if (t.hasAttribute('data-estats-sortable')) return;
            const type = detectTableType(t);
            if (type === 'topSpeed') {
                injectHeaders(t, ['Errors', 'Length']);

                // Fetch from the correct topspeeds API endpoint
                fetchAllDataForType('topSpeed').then(records => {
                    const rows = t.querySelectorAll('tbody .table-row');
                    rows.forEach((row, i) => {
                        if (records[i]) injectTopSpeedCells(row, records[i]);
                    });
                    // Set up sorting AFTER columns are injected
                    setupCrossPageSorting(t, 'topSpeed');
                }).catch(e => {
                    console.error(LOG_PREFIX, 'Top speeds column injection error:', e);
                    setupCrossPageSorting(t, 'topSpeed');
                });
            }
        });

        await enhanceOpenRacelogResultsModal();
        scheduleSortPaginationCleanup();
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 8: League XP-Races Calculator (/leagues)
    // ─────────────────────────────────────────────────────────────────────────────
    const LEAGUE_CALC_ATTR = 'data-estats-league-calc';

    function cleanupLeagueCalculator() {
        document.querySelectorAll(`[${LEAGUE_CALC_ATTR}]`).forEach((el) => el.remove());
    }

    function handleLeagueCalculator() {
        if (!isFeatureEnabled('ENABLE_LEAGUE_CALCULATOR')) return;

        const path = normalizePath(window.location.pathname);
        if (path !== '/leagues') return;

        // Find self row in standings
        const selfRow = document.querySelector('tr.table-row.is-self');
        if (!selfRow) return;

        const selfXPCell = selfRow.querySelector('td.table-cell.leagues--standings--experience');
        const selfRacesCell = selfRow.querySelector('td.table-cell.leagues--standings--played');
        if (!selfXPCell || !selfRacesCell) return;

        const parseNum = (str) => { const n = parseInt(str.replace(/,/g, ''), 10); return isNaN(n) ? 0 : n; };
        const selfXP = parseNum(selfXPCell.textContent.trim());
        const selfRaces = parseNum(selfRacesCell.textContent.trim());
        if (selfXP <= 0 || selfRaces <= 0) return;

        const xpPerRace = selfXP / selfRaces;

        // Get all XP cells and inject race difference
        const allXPCells = document.querySelectorAll('td.table-cell.leagues--standings--experience');
        allXPCells.forEach(cell => {
            if (cell.querySelector('[data-estats-league-calc]')) return;

            const theirXP = parseNum(cell.textContent.trim());
            if (theirXP === selfXP) return; // Skip self

            const racesToMatch = Math.ceil(theirXP / xpPerRace);
            const racesDiff = racesToMatch - selfRaces;

            const badge = document.createElement('div');
            badge.setAttribute('data-estats-league-calc', '');
            badge.style.cssText = 'font-size:13px;font-weight:700;margin-top:2px;';

            if (selfXP > theirXP) {
                // You're ahead — they need this many races to catch you
                badge.style.color = '#d62f3a';
                badge.textContent = `${racesDiff}`;
            } else {
                // They're ahead — you need this many more races
                badge.style.color = '#4ade80';
                badge.textContent = `+${racesDiff}`;
            }
            badge.title = `~${Math.abs(racesDiff)} races difference at your avg ${Math.round(xpPerRace).toLocaleString()} XP/race`;
            cell.appendChild(badge);
        });
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 9: Tournament Wins (/leagues)
    // ─────────────────────────────────────────────────────────────────────────────
    const TOURNAMENT_WINS_ATTR = 'data-estats-tournament-wins';

    function cleanupTournamentWins() {
        document.querySelectorAll(`[${TOURNAMENT_WINS_ATTR}]`).forEach((el) => el.remove());
    }

    async function handleTournamentWins() {
        if (!isFeatureEnabled('ENABLE_TOURNAMENT_WINS')) return;

        const path = normalizePath(window.location.pathname);
        if (path !== '/leagues') return;

        if (document.querySelector(`[${TOURNAMENT_WINS_ATTR}]`)) return;

        const user = getCurrentUser();
        if (!user) return;

        const personalWins = user.tournamentsWon || 0;

        // Inject win count into the Personal toggle label
        const personalLabel = document.querySelector('label[for="showindividual"]');
        if (personalLabel && !personalLabel.querySelector(`[${TOURNAMENT_WINS_ATTR}]`)) {
            const span = document.createElement('span');
            span.setAttribute(TOURNAMENT_WINS_ATTR, '');
            span.style.cssText = 'margin-left:6px;';
            span.textContent = `| Wins: ${personalWins.toLocaleString()}`;
            personalLabel.appendChild(span);
        }

        // Fetch and inject team tournament wins
        if (user.tag) {
            const teamLabel = document.querySelector('label[for="showteam"]');
            if (teamLabel && !teamLabel.querySelector(`[${TOURNAMENT_WINS_ATTR}]`)) {
                let teamWins = 0;
                try {
                    const data = await apiFetch('/api/v2/leagues/team/activity');
                    teamWins = data?.results?.tournamentsWon ?? 0;
                } catch (e) {
                    console.error(LOG_PREFIX, 'Tournament wins fetch error:', e);
                }
                const span = document.createElement('span');
                span.setAttribute(TOURNAMENT_WINS_ATTR, '');
                span.style.cssText = 'margin-left:6px;';
                span.textContent = `| Wins: ${teamWins.toLocaleString()}`;
                teamLabel.appendChild(span);
            }
        }
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // FEATURE 8: Garage Car Count (/garage)
    // ─────────────────────────────────────────────────────────────────────────────
    const GARAGE_CAR_COUNT_ATTR = 'data-estats-garage-carcount';

    function cleanupGarageCarCount() {
        document.querySelectorAll(`[${GARAGE_CAR_COUNT_ATTR}]`).forEach((el) => el.remove());
    }

    function handleGarageCarCount() {
        if (!isFeatureEnabled('ENABLE_GARAGE_CAR_COUNT')) return;

        const path = normalizePath(window.location.pathname);
        if (path !== '/garage') return;

        // Already injected?
        if (document.querySelector(`[${GARAGE_CAR_COUNT_ATTR}]`)) return;

        // Get car count from current user's data (persist:nt)
        const user = getCurrentUser();
        if (!user) return;

        const totalCars = user.totalCars || user.carsOwned || 0;
        if (!totalCars) return;

        // Find the "My Cars" heading on the garage page
        const headings = document.querySelectorAll('h1, h2, h3');
        let carsHeading = null;
        for (const h of headings) {
            const txt = h.textContent.trim();
            if (txt === 'My Cars' || txt === 'Cars') {
                carsHeading = h;
                break;
            }
        }
        if (!carsHeading) return;

        const countSpan = document.createElement('span');
        countSpan.setAttribute(GARAGE_CAR_COUNT_ATTR, '');
        countSpan.className = 'tbs';
        countSpan.style.cssText = 'margin-left:8px;';
        countSpan.textContent = `| ${totalCars}`;
        carsHeading.appendChild(countSpan);
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // STARTUP
    // ─────────────────────────────────────────────────────────────────────────────
    function startEnhancedStats() {
        initObserverManager();

        applyAllLiveSettingSideEffects();

        window.NTObserverManager.register('enhanced-stats', () => {
            const path = normalizePath(window.location.pathname);

            try { handleSessionCounter(); } catch (e) { console.error(LOG_PREFIX, 'Session counter error:', e); }

            if (path.startsWith('/racer/')) {
                try { handleHiddenStats(); } catch (e) { console.error(LOG_PREFIX, 'Hidden stats error:', e); }
            }

            if (path === '/race' || path.startsWith('/race/')) {
                try { hookRaceServer(); } catch (e) { console.error(LOG_PREFIX, 'Race hook error:', e); }
                try { handleRaceEnhancements(); } catch (e) { console.error(LOG_PREFIX, 'Race enhance error:', e); }
                try { handleWPMCurve(); } catch (e) { console.error(LOG_PREFIX, 'WPM curve error:', e); }
            }

            if (path === '/stats') {
                try { handleEnhancedStatsPage(); } catch (e) { console.error(LOG_PREFIX, 'Stats page error:', e); }
            }

            if (path.startsWith('/racelog') || path.startsWith('/stats')) {
                try { handleEnhancedRacelog(); } catch (e) { console.error(LOG_PREFIX, 'Racelog error:', e); }
            }

            if (path === '/leagues') {
                try { handleLeagueCalculator(); } catch (e) { console.error(LOG_PREFIX, 'League calc error:', e); }
                try { handleTournamentWins(); } catch (e) { console.error(LOG_PREFIX, 'Tournament wins error:', e); }
            }

            if (path === '/garage') {
                try { handleGarageCarCount(); } catch (e) { console.error(LOG_PREFIX, 'Garage car count error:', e); }
            }
        });

        // Prefetch summary stats in the background (keeps cache warm for /stats page)
        prefetchSummaryStats().catch(e => console.error(LOG_PREFIX, 'Summary prefetch error:', e));

        // Initial run for global features
        try { handleSessionCounter(); } catch (e) { console.error(LOG_PREFIX, 'Session counter error:', e); }

        // Retry global elements (dropdown may not exist yet)
        let retryCount = 0;
        const retryGlobal = setInterval(() => {
            retryCount++;
            if (retryCount > 30) { clearInterval(retryGlobal); return; }
            try {
                const hasCounter = document.querySelector(`[${SESSION_COUNTER_ATTR}]`);
                if (!hasCounter) handleSessionCounter();
                if (hasCounter) clearInterval(retryGlobal);
            } catch { /* ignore */ }
        }, 500);

        // ── Race page polling fallback ──
        // MutationObserver can be unreliable when multiple userscripts are active.
        // Poll for race results like other scripts (Racer Badges, Race Options) do.
        const path = normalizePath(window.location.pathname);
        const isTopRaceShell = (path === '/race' || path.startsWith('/race/'))
            && window.top === window
            && !document.getElementById('raceContainer');
        if ((path === '/race' || path.startsWith('/race/')) && !isTopRaceShell) {
            const racePoller = setInterval(() => {
                try {
                    const rc = document.getElementById('raceContainer');
                    if (rc) hookRaceServer();
                    if (rc && rc.querySelector('.race-results')) {
                        handleRaceEnhancements();
                        handleWPMCurve();
                    }
                } catch (e) { console.error(LOG_PREFIX, 'Race poll error:', e); }
            }, 500);
            // Stop polling after 10 minutes (race should be long done)
            setTimeout(() => clearInterval(racePoller), 600000);
        }
    }

    if (document.body) {
        startEnhancedStats();
    } else {
        const bodyWait = new MutationObserver(() => {
            if (document.body) {
                bodyWait.disconnect();
                startEnhancedStats();
            }
        });
        bodyWait.observe(document.documentElement, { childList: true });
    }

})();