Nitro Type - Enhanced Stats

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Nitro Type - Enhanced Stats
// @namespace    https://nitrotype.info
// @version      2.1.3
// @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();

    const publishEnhancedStatsManifestHeartbeat = () => {
        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 */ }
    };
    publishEnhancedStatsManifestHeartbeat();

    // ─── 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 = () => {
        const token = String(localStorage.getItem('player_token') || '').trim();
        return token || null;
    };

    /** 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);
            }
        }
    };

    let enhancedStatsSessionRetryTimer = null;
    let enhancedStatsSessionRetryAttempts = 0;
    let enhancedStatsRaceResultsPollerTimer = null;
    let enhancedStatsRaceResultsPollerStopTimer = null;
    let enhancedStatsBodyWaitObserver = null;

    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 = {};
        syncEnhancedStatsRouteFallbacks();
    }

    // ─── 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) {
            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.removeAttribute(STATS_PAGE_ATTR);
            statBoxContainer.querySelectorAll(`[${STATS_PAGE_ATTR}]`).forEach((el) => el.removeAttribute(STATS_PAGE_ATTR));
        }

        const overviewRow = Array.from(document.querySelectorAll('.row')).find((row) => {
            const heading = row.querySelector('h3');
            return heading && heading.textContent.trim() === 'Overview';
        });
        if (overviewRow) {
            overviewRow.querySelectorAll('[data-estats-injected]').forEach((el) => el.remove());
            restoreOriginalStyle(overviewRow);
        }

        const summaryTable = document.querySelector('.table--striped');
        if (summaryTable) {
            summaryTable.querySelectorAll('[data-estats-injected],[data-estats-lifetime],[data-estats-week]').forEach((el) => el.remove());
            summaryTable.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');
            });
            summaryTable.removeAttribute(STATS_SUMMARY_NOSORT_ATTR);
            summaryTable.removeAttribute(STATS_PAGE_ATTR);
            summaryTable.querySelectorAll(`[${STATS_SUMMARY_NOSORT_ATTR}]`).forEach((el) => el.removeAttribute(STATS_SUMMARY_NOSORT_ATTR));
            summaryTable.querySelectorAll(`[${STATS_PAGE_ATTR}]`).forEach((el) => el.removeAttribute(STATS_PAGE_ATTR));
            summaryTable.querySelectorAll(`[${STATS_SUMMARY_HEADER_LOCK_ATTR}]`).forEach((el) => {
                el.removeAttribute(STATS_SUMMARY_HEADER_LOCK_ATTR);
                el.style.cursor = '';
                el.style.userSelect = '';
                el.title = '';
            });
        }
    }

    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;
        let nativeTbodyHTML = '';
        let nativeSnapshotKey = '';
        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 getNativeSnapshotKey() {
            if (tableType === 'racelog') {
                return `racelog:${getActiveRacelogPageNumber()}`;
            }
            return tableType;
        }

        function captureNativeSnapshot() {
            if (table.hasAttribute('data-estats-sort-active')) return;
            nativeTbodyHTML = tbody.innerHTML;
            nativeSnapshotKey = getNativeSnapshotKey();
        }

        captureNativeSnapshot();

        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 = nativeTbodyHTML;
            table.removeAttribute('data-estats-sort-active');
            headers.forEach(h => { const a = h.querySelector('[data-estats-sort-arrow]'); if (a) a.remove(); });
            if (tableType === 'racelog' && nativeSnapshotKey.startsWith('racelog:')) {
                table.setAttribute(RACELOG_PAGE_ATTR, nativeSnapshotKey.slice('racelog:'.length));
            }
            // 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;
                captureNativeSnapshot();
                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 stopEnhancedStatsSessionCounterRetry() {
        if (enhancedStatsSessionRetryTimer) {
            clearInterval(enhancedStatsSessionRetryTimer);
            enhancedStatsSessionRetryTimer = null;
        }
        enhancedStatsSessionRetryAttempts = 0;
    }

    function startEnhancedStatsSessionCounterRetry() {
        if (enhancedStatsSessionRetryTimer || !isFeatureEnabled('ENABLE_SESSION_COUNTER')) return;
        enhancedStatsSessionRetryAttempts = 0;
        enhancedStatsSessionRetryTimer = setInterval(() => {
            enhancedStatsSessionRetryAttempts++;
            if (enhancedStatsSessionRetryAttempts > 30) {
                stopEnhancedStatsSessionCounterRetry();
                return;
            }
            try {
                const hasCounter = document.querySelector(`[${SESSION_COUNTER_ATTR}]`);
                if (!hasCounter) handleSessionCounter();
                if (hasCounter) stopEnhancedStatsSessionCounterRetry();
            } catch { /* ignore */ }
        }, 500);
    }

    function stopEnhancedStatsRaceResultsPoller() {
        if (enhancedStatsRaceResultsPollerTimer) {
            clearInterval(enhancedStatsRaceResultsPollerTimer);
            enhancedStatsRaceResultsPollerTimer = null;
        }
        if (enhancedStatsRaceResultsPollerStopTimer) {
            clearTimeout(enhancedStatsRaceResultsPollerStopTimer);
            enhancedStatsRaceResultsPollerStopTimer = null;
        }
    }

    function isEnhancedStatsRacePath(path) {
        return path === '/race' || path.startsWith('/race/');
    }

    function isEnhancedStatsRacelogPath(path) {
        return path.startsWith('/racelog') || path.startsWith('/stats');
    }

    function shouldUseEnhancedStatsRaceResultsPoller(path) {
        const isTopRaceShell = isEnhancedStatsRacePath(path)
            && window.top === window
            && !document.getElementById('raceContainer');
        return isEnhancedStatsRacePath(path) && !isTopRaceShell;
    }

    function startEnhancedStatsRaceResultsPoller(path) {
        if (!shouldUseEnhancedStatsRaceResultsPoller(path)) {
            stopEnhancedStatsRaceResultsPoller();
            return;
        }
        if (enhancedStatsRaceResultsPollerTimer) return;

        enhancedStatsRaceResultsPollerTimer = 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);

        enhancedStatsRaceResultsPollerStopTimer = setTimeout(() => {
            stopEnhancedStatsRaceResultsPoller();
        }, 600000);
    }

    function syncEnhancedStatsRouteFallbacks(path = normalizePath(window.location.pathname)) {
        if (isFeatureEnabled('ENABLE_SESSION_COUNTER') && !document.querySelector(`[${SESSION_COUNTER_ATTR}]`)) {
            startEnhancedStatsSessionCounterRetry();
        } else {
            stopEnhancedStatsSessionCounterRetry();
        }

        startEnhancedStatsRaceResultsPoller(path);
    }

    function stopEnhancedStatsBodyWaitObserver() {
        if (!enhancedStatsBodyWaitObserver) return;
        try { enhancedStatsBodyWaitObserver.disconnect(); } catch { /* ignore */ }
        enhancedStatsBodyWaitObserver = null;
    }

    function runEnhancedStatsGuarded(label, callback) {
        try {
            callback();
        } catch (e) {
            console.error(LOG_PREFIX, `${label} error:`, e);
        }
    }

    function runEnhancedStatsRouteHandlers(path) {
        runEnhancedStatsGuarded('Session counter', handleSessionCounter);

        if (path.startsWith('/racer/')) {
            runEnhancedStatsGuarded('Hidden stats', handleHiddenStats);
        }

        if (isEnhancedStatsRacePath(path)) {
            runEnhancedStatsGuarded('Race hook', hookRaceServer);
            runEnhancedStatsGuarded('Race enhance', handleRaceEnhancements);
            runEnhancedStatsGuarded('WPM curve', handleWPMCurve);
        }

        if (path === '/stats') {
            runEnhancedStatsGuarded('Stats page', handleEnhancedStatsPage);
        }

        if (isEnhancedStatsRacelogPath(path)) {
            runEnhancedStatsGuarded('Racelog', handleEnhancedRacelog);
        }

        if (path === '/leagues') {
            runEnhancedStatsGuarded('League calc', handleLeagueCalculator);
            runEnhancedStatsGuarded('Tournament wins', handleTournamentWins);
        }

        if (path === '/garage') {
            runEnhancedStatsGuarded('Garage car count', handleGarageCarCount);
        }
    }

    function startEnhancedStats() {
        initObserverManager();

        applyAllLiveSettingSideEffects();

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

        // 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
        runEnhancedStatsGuarded('Session counter', handleSessionCounter);

        // Keep startup fallbacks aligned with the current SPA route.
        syncEnhancedStatsRouteFallbacks();
    }

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

})();