Nitro Type Startrack Leaderboard Integration

Optimized caching - only fetch on page load, instant tab switching

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Nitro Type Startrack Leaderboard Integration
// @version      8.0
// @description  Optimized caching - only fetch on page load, instant tab switching
// @author       Combined Logic (SuperJoelzy + Captain.Loveridge)
// @license      MIT
// @match        https://www.nitrotype.com/*
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// @namespace    https://greasyfork.org/users/1443935
// ==/UserScript==

(function() {
    'use strict';

    // --- CONFIGURATION ---
    const TAB_CLASS = 'nt-custom-leaderboards';
    const LEADERBOARD_PATH = '/leaderboards';
    const CACHE_KEY = 'ntStartrackCache_';
    const CACHE_TIMESTAMP_KEY = 'ntStartrackCacheTime_';
    const CACHE_DURATION = 60 * 60 * 1000; // 60 minutes (1 hour)
    const ASYNC_DELAY = 50;

    // --- CACHE QUEUE STATE ---
    let cacheQueue = [];
    let isCaching = false;
    let initialCacheComplete = false;

    // --- STATE & DATA ---
    let state = {
        view: 'individual',
        timeframe: 'season',
        currentDate: new Date(),
        dateRange: { start: null, end: null }
    };

    const SEASON_START = '2025-11-02 06:00:00';
    const SEASON_END = '2025-11-30 08:00:00';

    const timeframes = [
        { key: 'season', label: 'Season', hasNav: false },
        { key: '24hr', label: 'Last 24 Hours', hasNav: false },
        { key: '60min', label: '60 Minutes', hasNav: false },
        { key: '7day', label: 'Last 7 Days', hasNav: false },
        { key: 'daily', label: 'Daily', hasNav: true },
        { key: 'weekly', label: 'Weekly', hasNav: true },
        { key: 'monthly', label: 'Monthly', hasNav: true },
        { key: 'custom', label: 'Custom', hasNav: false }
    ];

    // --- UTILITIES ---
    function formatDate(date) {
        if (!date) return '';
        const d = new Date(date);
        const y = d.getFullYear();
        const m = ('0' + (d.getMonth() + 1)).slice(-2);
        const day = ('0' + d.getDate()).slice(-2);
        const h = ('0' + d.getHours()).slice(-2);
        const min = ('0' + d.getMinutes()).slice(-2);
        const s = ('0' + d.getSeconds()).slice(-2);
        return `${y}-${m}-${day} ${h}:${min}:${s}`;
    }

    function getStartOfDay(date) {
        const d = new Date(date);
        d.setHours(0, 0, 0, 0);
        return d;
    }

    function getEndOfDay(date) {
        const d = new Date(date);
        d.setHours(23, 59, 59, 999);
        return d;
    }

    function calculateDateRange(tempState) {
        let start, end;
        const current = new Date(tempState.currentDate);
        const timeframe = tempState.timeframe || state.timeframe;
        const now = new Date();

        if (timeframe === 'season') {
            return { start: SEASON_START, end: SEASON_END };
        } else if (timeframe === '60min') {
            end = now;
            start = new Date(now.getTime() - (60 * 60 * 1000));
        } else if (timeframe === '24hr') {
            end = now;
            start = new Date(now.getTime() - (24 * 60 * 60 * 1000));
        } else if (timeframe === '7day') {
            end = now;
            start = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000));
        } else if (timeframe === 'daily') {
            start = getStartOfDay(current);
            end = getEndOfDay(current);
        } else if (timeframe === 'weekly') {
            const dayOfWeek = current.getDay();
            start = getStartOfDay(current);
            start.setDate(start.getDate() - dayOfWeek);
            end = new Date(start);
            end.setDate(end.getDate() + 6);
            end = getEndOfDay(end);
        } else if (timeframe === 'monthly') {
            start = new Date(current.getFullYear(), current.getMonth(), 1);
            end = new Date(current.getFullYear(), current.getMonth() + 1, 0);
            end = getEndOfDay(end);
        } else if (timeframe === 'custom') {
            start = tempState.dateRange?.start || getStartOfDay(now);
            end = tempState.dateRange?.end || getEndOfDay(now);
        }

        return {
            start: formatDate(start),
            end: formatDate(end)
        };
    }

    function navigateDate(direction) {
        const current = state.currentDate;
        const date = new Date(current);

        if (state.timeframe === 'daily') {
            date.setDate(current.getDate() + direction);
        } else if (state.timeframe === 'weekly') {
            date.setDate(current.getDate() + (7 * direction));
        } else if (state.timeframe === 'monthly') {
            date.setMonth(current.getMonth() + direction);
        }

        state.currentDate = date;
        fetchLeaderboardData();
    }

    // --- PROGRESS INDICATOR ---
    function setIndicator(message, isUpdating = true) {
        const indicatorEl = document.getElementById('update-indicator');
        if (indicatorEl) {
            indicatorEl.textContent = message;
            indicatorEl.style.color = isUpdating ? '#FFC107' : '#28A745';
            document.querySelectorAll('[data-timeframe]').forEach(btn => {
                btn.classList.remove('is-active', 'is-frozen');
                if (btn.dataset.timeframe === state.timeframe) {
                    btn.classList.add('is-active', 'is-frozen');
                }
            });
            document.querySelectorAll('[data-view]').forEach(btn => {
                btn.classList.remove('is-active');
                if (btn.dataset.view === state.view) {
                    btn.classList.add('is-active');
                }
            });
        }
    }

    function getCacheKey(tempState) {
        const s = tempState || state;
        const ranges = calculateDateRange(s);

        let startKey = ranges.start;
        let endKey = ranges.end;

        if (s.timeframe === '60min' || s.timeframe === '24hr' || s.timeframe === '7day') {
            const roundToHour = (dateStr) => {
                const date = new Date(dateStr.replace(' ', 'T'));
                date.setMinutes(0, 0, 0);
                return formatDate(date);
            };
            startKey = roundToHour(ranges.start);
            endKey = roundToHour(ranges.end);
        }

        return `${CACHE_KEY}${s.view}_${s.timeframe}_${startKey}_${endKey}`;
    }

    // --- BUILD HTML ---
    function buildLeaderboardHTML() {
        const currentTF = timeframes.find(t => t.key === state.timeframe);
        const hasNav = currentTF?.hasNav || false;
        const isCustom = state.timeframe === 'custom';

        return `
            <section class="card card--b card--o card--shadow card--f card--grit well well--b well--l">
                <div class="card-cap bg--gradient">
                    <h1 class="h2 tbs">Startrack Leaderboards</h1>
                </div>

                <div class="well--p well--l_p">
                    <div class="row row--o well well--b well--l">
                        <div class="tabs tabs--a tabs--leaderboards">
                            <button class="tab" data-view="individual">
                                <div class="bucket bucket--c bucket--xs"><div class="bucket-media"><svg class="icon icon-racer"><use xlink:href="/dist/site/images/icons/icons.css.svg#icon-racer"></use></svg></div><div class="bucket-content">Top Racers</div></div>
                            </button>
                            <button class="tab" data-view="team">
                                <div class="bucket bucket--c bucket--xs"><div class="bucket-media"><svg class="icon icon-team"><use xlink:href="/dist/site/images/icons/icons.css.svg#icon-team"></use></svg></div><div class="bucket-content">Top Teams</div></div>
                            </button>
                        </div>

                        <div class="card card--d card--o card--sq card--f">
                            <div class="well--p well--pt">
                                <div class="row row--o has-btn">
                                    ${timeframes.map(tf => `<button type="button" class="btn btn--dark btn--outline btn--thin" data-timeframe="${tf.key}">${tf.label}</button>`).join('')}
                                </div>

                                <div class="divider divider--a mbf"></div>

                                <div class="seasonLeader seasonLeader--default">
                                    <div class="split split--start row">
                                        <div class="split-cell">
                                            <h1 class="seasonLeader-title" id="date-title">Loading...</h1>
                                            <p class="seasonLeader-date" id="date-range"></p>
                                        </div>
                                    </div>
                                    <div class="split-cell tac tsm" style="align-self: flex-end; margin-bottom: 5px;">
                                        <span id="update-indicator">Loading...</span>
                                    </div>
                                </div>

                                ${hasNav ? `
                                    <div class="row row--o mtm">
                                        <button class="btn btn--secondary btn--thin" id="nav-prev">&lt; Previous</button>
                                        <button class="btn btn--secondary btn--thin" id="nav-today">Today</button>
                                        <button class="btn btn--secondary btn--thin" id="nav-next">Next &gt;</button>
                                    </div>
                                ` : ''}

                                ${isCustom ? `
                                    <div class="row row--o mtm">
                                        <label class="tsm tc-ts">Start:</label>
                                        <input type="date" id="start-date" class="input input--mini mlxs mrm" value="${state.dateRange.start ? state.dateRange.start.toISOString().split('T')[0] : ''}">
                                        <label class="tsm tc-ts">End:</label>
                                        <input type="date" id="end-date" class="input input--mini mlxs mrm" value="${state.dateRange.end ? state.dateRange.end.toISOString().split('T')[0] : ''}">
                                        <button class="btn btn--primary btn--thin" id="update-custom">Update</button>
                                    </div>
                                ` : ''}

                                <div id="leaderboard-table-container">
                                    <div class="tac pxl mtl">
                                        <div class="loading-spinner loading-spinner--ts" style="margin: 0 auto;"></div>
                                        <div class="mtm">Loading content...</div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        `;
    }

    // --- CACHE MANAGEMENT ---
    function cleanOldCache() {
        try {
            const keys = Object.keys(localStorage);
            const cacheKeys = keys.filter(k => k.startsWith(CACHE_KEY));

            if (cacheKeys.length > 50) {
                console.log(`Cleaning cache: ${cacheKeys.length} items found, keeping newest 50`);
                cacheKeys.sort().slice(0, cacheKeys.length - 50).forEach(key => {
                    localStorage.removeItem(key);
                });
            }
        } catch (e) {
            console.error('Error cleaning cache:', e);
        }
    }

    function isCacheFresh(cacheKey) {
        const timestampKey = CACHE_TIMESTAMP_KEY + cacheKey;
        const timestamp = localStorage.getItem(timestampKey);
        if (!timestamp) return false;

        const age = Date.now() - parseInt(timestamp);
        return age < CACHE_DURATION;
    }

    function saveToCache(cacheKey, data) {
        try {
            localStorage.setItem(cacheKey, data);
            localStorage.setItem(CACHE_TIMESTAMP_KEY + cacheKey, Date.now().toString());
            console.log('✓ Cached:', cacheKey);
        } catch (quotaError) {
            if (quotaError.name === 'QuotaExceededError') {
                console.log('⚠ Quota exceeded - clearing old cache and retrying');
                cleanOldCache();
                try {
                    localStorage.setItem(cacheKey, data);
                    localStorage.setItem(CACHE_TIMESTAMP_KEY + cacheKey, Date.now().toString());
                    console.log('✓ Cached after cleanup:', cacheKey);
                } catch (e2) {
                    console.error('✗ Still cannot save - cache data too large, skipping cache');
                }
            }
        }
    }

    // --- RENDER TABLE ---
    function renderTable(data) {
        const container = document.getElementById('leaderboard-table-container');
        if (!container) return;

        if (!data || data.length === 0) {
            container.innerHTML = '<div class="tac pxl tsm tc-ts">No data available.</div>';
            return;
        }

        const top100 = data.slice(0, 100);
        const isIndividual = state.view === 'individual';

        let html = '<table class="table table--selectable table--striped table--fixed table--leaderboard">';

        html += '<thead class="table-head"><tr class="table-row">';
        if (isIndividual) {
            html += `<th scope="col" class="table-cell table-cell--place"></th><th scope="col" class="table-cell table-cell--racer">Team</th><th scope="col" class="table-cell table-cell--racer">Display Name</th><th scope="col" class="table-cell table-cell--speed">WPM</th><th scope="col" class="table-cell table-cell--races">Accuracy</th><th scope="col" class="table-cell table-cell--races">Races</th><th scope="col" class="table-cell table-cell--points">Points</th>`;
        } else {
            html += `<th scope="col" class="table-cell table-cell--place"></th><th scope="col" class="table-cell table-cell--racer">Team Tag</th><th scope="col" class="table-cell table-cell--speed">WPM</th><th scope="col" class="table-cell table-cell--races">Accuracy</th><th scope="col" class="table-cell table-cell--races">Races</th><th scope="col" class="table-cell table-cell--points">Points</th>`;
        }
        html += '</tr></thead><tbody class="table-body">';

        top100.forEach((item, index) => {
            const rank = index + 1;
            let rowClass = 'table-row';
            let medalHTML = `<div class="mhc"><span class="h3 tc-ts">${rank}</span></div>`;

            if (rank === 1) {
                rowClass = 'table-row table-row--gold';
                medalHTML = '<img class="db" src="/dist/site/images/medals/gold-sm.png">';
            } else if (rank === 2) {
                rowClass = 'table-row table-row--silver';
                medalHTML = '<img class="db" src="/dist/site/images/medals/silver-sm.png">';
            } else if (rank === 3) {
                rowClass = 'table-row table-row--bronze';
                medalHTML = '<img class="db" src="/dist/site/images/medals/bronze-sm.png">';
            }

            const wpm = parseFloat(item.WPM).toFixed(1);
            const acc = (parseFloat(item.Accuracy) * 100).toFixed(2);
            const points = Math.round(parseFloat(item.Points)).toLocaleString();

            html += `<tr class="${rowClass}"><td class="table-cell table-cell--place tac">${medalHTML}</td>`;

            if (isIndividual) {
                const teamTag = item.TeamTag || '--';
                const displayName = item.CurrentDisplayName || item.Username;
                const tagColor = item.tagColor || 'fff';

                html += `<td class="table-cell table-cell--racer" style="color: #${tagColor}">${teamTag}</td><td class="table-cell table-cell--racer"><a href="https://www.nitrotype.com/racer/${item.Username}" target="_blank" class="link">${displayName}</a></td><td class="table-cell table-cell--speed">${wpm}</td><td class="table-cell table-cell--races">${acc}%</td><td class="table-cell table-cell--races">${item.Races}</td><td class="table-cell table-cell--points">${points}</td>`;
            } else {
                const teamTag = item.TeamTag || '----';
                const teamName = item.TeamName || `${item.TeamTag || 'Unknown'} Team`;
                const tagColor = item.tagColor || 'B3C8DD';

                html += `
                    <td class="table-cell table-cell--racer">
                        <a href="https://www.nitrotype.com/team/${teamTag}" target="_blank" class="link" style="color: #${tagColor};">[${teamTag}]</a>
                        ${teamName}
                    </td>
                    <td class="table-cell table-cell--speed">${wpm}</td>
                    <td class="table-cell table-cell--races">${acc}%</td>
                    <td class="table-cell table-cell--races">${item.Races}</td>
                    <td class="table-cell table-cell--points">${points}</td>
                `;
            }
            html += '</tr>';
        });

        html += '</tbody></table>';
        container.innerHTML = html;

        // Show "Last updated" timestamp
        const cacheKey = getCacheKey();
        const timestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY + cacheKey);
        if (timestamp) {
            const updateTime = new Date(parseInt(timestamp));
            const timeString = updateTime.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
            setIndicator(`Last updated: ${timeString}`, false);
        } else {
            setIndicator('Updated', false);
        }

        // Start background pre-caching only once on initial page load
        if (!initialCacheComplete && !isCaching) {
            initialCacheComplete = true;
            isCaching = true;
            setTimeout(() => {
                populateCacheQueue();
                cacheAllViews();
            }, 1000);
        }
    }

    function updateDateDisplay() {
        const titleEl = document.getElementById('date-title');
        const rangeEl = document.getElementById('date-range');
        if (!titleEl || !rangeEl) return;

        const ranges = calculateDateRange(state);
        const start = new Date(ranges.start.replace(' ', 'T'));
        const end = new Date(ranges.end.replace(' ', 'T'));

        if (state.timeframe === 'season') {
            titleEl.textContent = 'Season';
            rangeEl.textContent = 'Nov 2 - Nov 30, 2025';
        } else if (state.timeframe === 'daily') {
            titleEl.textContent = 'Daily';
            rangeEl.textContent = start.toLocaleDateString();
        } else if (state.timeframe === 'weekly') {
            titleEl.textContent = 'Weekly';
            rangeEl.textContent = `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`;
        } else if (state.timeframe === 'monthly') {
            titleEl.textContent = 'Monthly';
            rangeEl.textContent = start.toLocaleDateString(undefined, { month: 'long', year: 'numeric' });
        } else if (state.timeframe === 'custom') {
            titleEl.textContent = 'Custom Range';
            rangeEl.textContent = `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`;
        } else {
            titleEl.textContent = timeframes.find(t => t.key === state.timeframe)?.label || 'Leaderboards';
            rangeEl.textContent = '';
        }
    }

    // --- CACHE QUEUE ---
    function populateCacheQueue() {
        cacheQueue = [];
        const views = ['individual', 'team'];
        const currentYear = state.currentDate.getFullYear();
        const currentMonth = state.currentDate.getMonth();
        const currentDay = state.currentDate.getDate();

        const priorityTimeframes = ['season', '24hr', '7day'];

        const now = new Date();
        now.setMinutes(0, 0, 0);

        timeframes.filter(t => priorityTimeframes.includes(t.key)).forEach(tf => {
            views.forEach(view => {
                cacheQueue.push({ view: view, timeframe: tf.key, currentDate: now });
            });
        });

        const dynamicTFs = timeframes.filter(t => t.hasNav);
        views.forEach(view => {
            dynamicTFs.forEach(tf => {
                let date = new Date(currentYear, currentMonth, currentDay);
                cacheQueue.push({ view: view, timeframe: tf.key, currentDate: date });
            });
        });

        const currentKey = getCacheKey();
        cacheQueue = cacheQueue.filter(item => getCacheKey(item) !== currentKey);

        console.log(`Cache queue populated with ${cacheQueue.length} items`);
    }

    function cacheAllViews() {
        if (cacheQueue.length === 0) {
            console.log('✓ All views pre-cached successfully!');
            isCaching = false;
            return;
        }

        const nextItem = cacheQueue.shift();
        const nextKey = getCacheKey(nextItem);

        if (localStorage.getItem(nextKey) && isCacheFresh(nextKey)) {
            console.log(`✓ ${nextItem.view} ${nextItem.timeframe} already cached (${cacheQueue.length} remaining)`);
            cacheAllViews();
            return;
        }

        console.log(`⟳ Pre-caching ${nextItem.view} ${nextItem.timeframe} (${cacheQueue.length} remaining)`);
        fetchFreshData(nextKey, nextItem.view, nextItem.timeframe, nextItem.currentDate, cacheAllViews);
    }

    // --- FETCH DATA ---
    function fetchLeaderboardData(forceRefresh = false) {
        const cacheKey = getCacheKey();
        const cachedData = localStorage.getItem(cacheKey);
        const isFresh = isCacheFresh(cacheKey);

        updateDateDisplay();

        // If we have fresh cache AND it's not a page reload, just show it - NO API call
        if (cachedData && isFresh && !forceRefresh) {
            try {
                const data = JSON.parse(cachedData);
                renderTable(data);
                console.log('✓ Loaded from cache (no API call):', state.view, state.timeframe);
                return;
            } catch (e) {
                console.error("Error parsing cached data:", e);
                localStorage.removeItem(cacheKey);
            }
        }

        // Cache is expired or missing - fetch fresh data
        const container = document.getElementById('leaderboard-table-container');
        if (container) {
            container.innerHTML = `<div class="tac pxl mtl"><div class="loading-spinner loading-spinner--ts" style="margin: 0 auto;"></div><div class="mtm">Loading data...</div></div>`;
        }
        setIndicator('Updating...', true);

        console.log('⟳ Fetching fresh data:', state.view, state.timeframe);
        fetchFreshData(cacheKey);
    }

    function fetchFreshData(cacheKey, view = state.view, timeframe = state.timeframe, currentDate = state.currentDate, callback) {
        const tempState = { view, timeframe, currentDate };
        const ranges = calculateDateRange(tempState);

        const apiUrl = view === 'individual'
            ? 'https://ntstartrack.org/api/individual-leaderboard'
            : 'https://ntstartrack.org/api/team-leaderboard';

        const cacheBuster = `cb=${new Date().getTime()}`;
        const url = `${apiUrl}?start_time=${encodeURIComponent(ranges.start)}&end_time=${encodeURIComponent(ranges.end)}&showbot=FALSE&${cacheBuster}`;

        if (view === state.view && timeframe === state.timeframe) {
            setIndicator('Updating...', true);
        }

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            onload: function(response) {
                if (response.status === 200) {
                    setTimeout(() => {
                        try {
                            if (!response.responseText || response.responseText.trim().length === 0) {
                                throw new Error('Empty response from API');
                            }

                            const trimmed = response.responseText.trim();
                            if (trimmed.startsWith('<') || trimmed.startsWith('<!DOCTYPE')) {
                                throw new Error('API returned HTML instead of JSON');
                            }

                            const data = JSON.parse(response.responseText);

                            if (!Array.isArray(data)) {
                                throw new Error('Invalid data format - expected array');
                            }

                            console.log('✓ Successfully parsed', data.length, 'items');

                            const top100 = data.slice(0, 100);
                            saveToCache(cacheKey, JSON.stringify(top100));

                            if (view === state.view && timeframe === state.timeframe) {
                                renderTable(data);
                            }

                            if (callback) callback();

                        } catch (e) {
                            console.error('Parse Error:', e);
                            if (view === state.view && timeframe === state.timeframe) {
                                setIndicator(`Update failed`, false);
                            }
                            if (callback) callback();
                        }
                    }, ASYNC_DELAY);
                } else {
                    console.error('API Error:', response.status, response.statusText);
                    if (view === state.view && timeframe === state.timeframe) {
                        setIndicator(`Update failed`, false);
                    }
                    if (callback) callback();
                }
            },
            onerror: function(error) {
                console.error('Network Error:', error);
                if (view === state.view && timeframe === state.timeframe) {
                    setIndicator('Update failed', false);
                }
                if (callback) callback();
            }
        });
    }

    // --- EVENT LISTENERS ---
    function attachListeners() {
        document.querySelectorAll('[data-view]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const view = e.currentTarget.dataset.view;
                if (state.view !== view) {
                    state.view = view;
                    fetchLeaderboardData(false);
                }
            });
        });

        document.querySelectorAll('[data-timeframe]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const tf = e.currentTarget.dataset.timeframe;
                if (state.timeframe !== tf) {
                    state.timeframe = tf;
                    state.currentDate = new Date();
                    fetchLeaderboardData(false);
                }
            });
        });

        document.getElementById('nav-prev')?.addEventListener('click', () => navigateDate(-1));
        document.getElementById('nav-next')?.addEventListener('click', () => navigateDate(1));
        document.getElementById('nav-today')?.addEventListener('click', () => {
            state.currentDate = new Date();
            fetchLeaderboardData(true);
        });

        document.getElementById('update-custom')?.addEventListener('click', () => {
            const startInput = document.getElementById('start-date');
            const endInput = document.getElementById('end-date');
            const startVal = startInput?.value;
            const endVal = endInput?.value;
            if (startVal && endVal) {
                state.dateRange.start = getStartOfDay(new Date(startVal + 'T00:00:00'));
                state.dateRange.end = getEndOfDay(new Date(endVal + 'T00:00:00'));
                fetchLeaderboardData(true);
            }
        });
    }

    // --- PAGE INTEGRATION ---
    function renderLeaderboardPage(forceRefresh = false) {
        const mainContent = document.querySelector('main.structure-content');
        if (!mainContent) return;

        mainContent.innerHTML = buildLeaderboardHTML();
        attachListeners();
        fetchLeaderboardData(forceRefresh);
        setActiveTab();
        setTabTitle();
    }

    function setActiveTab() {
        document.querySelectorAll('.nav-list-item').forEach(li => li.classList.remove('is-current'));
        const tab = document.querySelector('.' + TAB_CLASS);
        if (tab) {
            tab.classList.add('is-current');
        }
    }

    function setTabTitle() {
        if (window.location.pathname === LEADERBOARD_PATH) {
            document.title = 'Leaderboards | Nitro Type';
        }
    }

    function insertLeaderboardTab() {
        if (document.querySelector(`a[href="${LEADERBOARD_PATH}"]`)) return;
        const navList = document.querySelector('.nav-list');
        if (!navList) return;

        const li = document.createElement('li');
        li.className = `nav-list-item ${TAB_CLASS}`;
        li.innerHTML = `<a href="${LEADERBOARD_PATH}" class="nav-link"><span class="has-notify">Leaderboards</span></a>`;

        const news = Array.from(navList.children).find(li =>
            li.textContent.trim().includes('News')
        );
        if (news) news.before(li);
        else navList.appendChild(li);
    }

    function handlePage() {
        insertLeaderboardTab();

        if (location.pathname === LEADERBOARD_PATH) {
            if (document.getElementById('leaderboard-table-container')) {
                setActiveTab();
                return;
            }

            const observer = new MutationObserver((mutationsList, observer) => {
                const main = document.querySelector('main.structure-content');

                if (main && (main.children.length === 0 || main.querySelector('.error'))) {
                    renderLeaderboardPage();
                    observer.disconnect();
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });

            const main = document.querySelector('main.structure-content');
            if (main && (main.children.length === 0 || main.querySelector('.error'))) {
                renderLeaderboardPage();
            }

        } else {
            document.querySelector('.' + TAB_CLASS)?.classList.remove('is-current');
        }
    }

    // --- INIT ---
    const obs = new MutationObserver(handlePage);
    obs.observe(document.body, { childList: true, subtree: true });
    handlePage();

})();