GeoPixels Guild Overhaul

Complete guild overhaul - draggable modal, XP tracking, message collapsing, responsive layout, and coordinate display in XP tracker!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GeoPixels Guild Overhaul
// @namespace    http://tampermonkey.net/
// @version      2.0.0
// @description  Complete guild overhaul - draggable modal, XP tracking, message collapsing, responsive layout, and coordinate display in XP tracker!
// @author       ariapokoteng
// @match        *://geopixels.net/*
// @match        *://*.geopixels.net/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geopixels.net
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration & State ---
    const CONFIG = {
        debugMode: false,
        timeOffset: GM_getValue('debug_time_offset', 0), // Offset in ms
        minSnapshotInterval: GM_getValue('min_snapshot_interval', 60 * 60 * 1000), // Default 1h (cached)
        maxSnapshots: GM_getValue('max_snapshots', 750) // Default 750 (cached)
    };

    // Snapshot interval presets (in milliseconds)
    const SNAPSHOT_INTERVALS = {
        HOURLY: 60 * 60 * 1000,           // 1 hour
        TWELVE_HOURS: 12 * 60 * 60 * 1000, // 12 hours
        TWENTY_FOUR_HOURS: 24 * 60 * 60 * 1000 // 24 hours
    };

    // Session-only tracking for visited map buttons
    const sessionState = {
        visitedCoords: new Set()
    };

    // --- CSS Styles ---
    const style = document.createElement('style');
    style.textContent = `
        .guild-modal-header {
            touch-action: none !important;
            -webkit-user-select: none !important;
            user-select: none !important;
        }

        .guild-modal-header span {
            touch-action: none !important;
            -webkit-user-select: none !important;
            user-select: none !important;
            display: block;
            flex: 1;
            padding-right: 10px;
        }

        .draggable-panel {
            touch-action: none !important;
        }

        /* Guild message collapsible styling */
        .guild-message-section {
            border: 1px solid #e5e7eb;
            border-radius: 0.5rem;
            overflow: hidden;
        }

        .guild-message-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0.75rem;
            background-color: #f9fafb;
            cursor: pointer;
            user-select: none;
        }

        .guild-message-header:hover {
            background-color: #f3f4f6;
        }

        .guild-message-toggle {
            display: inline-block;
            width: 20px;
            height: 20px;
            text-align: center;
            line-height: 20px;
            font-weight: bold;
            color: #6b7280;
            transition: transform 0.2s ease;
        }

        .guild-message-toggle.collapsed {
            transform: rotate(-90deg);
        }

        .guild-message-content {
            max-height: 500px;
            overflow: hidden;
            transition: max-height 0.3s ease, padding 0.3s ease;
            padding: 0.75rem;
        }

        .guild-message-content.collapsed {
            max-height: 0;
            padding: 0;
        }

        /* Responsive layout for guild info grid */
        @media (max-width: 1024px) {
            #infoTab .grid.grid-cols-1.lg\\:grid-cols-3 {
                grid-template-columns: 1fr !important;
            }

            #infoTab .lg\\:col-span-2 {
                grid-column: auto !important;
            }

            #infoTab .lg\\:col-span-1 {
                grid-column: auto !important;
                order: 1;
            }

            #infoTab > .grid {
                display: flex;
                flex-direction: column;
            }

            #guildMembersContainer {
                order: 1;
                margin-top: 2rem;
            }
        }

        /* Force members container to bottom when message is collapsed */
        #infoTab.message-collapsed > .grid {
            display: block;
        }

        #infoTab.message-collapsed #guildMembersContainer {
            margin-top: 1rem;
        }

        /* Find button visited state */
        .guild-find-btn.visited {
            background-color: #a855f7 !important;
        }

        .guild-find-btn.visited:hover {
            background-color: #9333ea !important;
        }

        /* XP Changes Section */
        .xp-changes-section {
            margin-top: 1.5rem;
            border: 1px solid #e5e7eb;
            border-radius: 0.5rem;
            overflow: hidden;
            width: 100%;
        }

        .xp-changes-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0.75rem;
            background-color: #f1f5f9;
            cursor: pointer;
            user-select: none;
            font-weight: 600;
            color: #334155;
        }

        .xp-changes-header:hover {
            background-color: #e2e8f0;
        }

        .xp-changes-content {
            padding: 1rem;
            background-color: white;
            display: block;
        }

        .xp-changes-content.hidden {
            display: none;
        }

        .daily-brief-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
        }
        .daily-brief-table th, .daily-brief-table td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        .daily-brief-table th {
            background-color: #f2f2f2;
        }
        .xp-gain { color: green; }
        .xp-loss { color: red; }
        .xp-neutral { color: #94a3b8; }

        /* Select dropdowns */
        select {
            border: 2px solid #3b82f6 !important;
            border-radius: 4px;
        }

        /* User cell with coordinates */
        .user-cell-content {
            display: flex;
            flex-direction: column;
            gap: 2px;
        }

        .user-name {
            font-weight: 500;
        }

        .user-coords {
            font-size: 13px;
        }

        /* Icons */
        .member-icon-btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 24px;
            height: 24px;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.2s;
            margin-left: 4px;
            border: none;
            background: transparent;
            padding: 0;
        }
        .member-icon-btn:hover {
            background-color: rgba(0,0,0,0.05);
        }
        .discord-icon { color: #5865F2; }
        .map-icon { color: #0ea5e9; }
        .map-icon.visited { color: #a855f7; }

        .control-button {
            padding: 6px 12px;
            border: 1px solid #ddd;
            background: white;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            transition: background-color 0.2s;
        }
        .control-button:hover {
            background-color: #f0f0f0;
        }
        .control-button.active {
            background-color: #3b82f6;
            color: white;
            border-color: #3b82f6;
        }

        .trash-btn {
            background: none;
            border: none;
            color: #ef4444;
            cursor: pointer;
            padding: 2px 4px;
            font-size: 12px;
        }
        .trash-btn:hover {
            color: #dc2626;
        }

        .tooltip-popup {
            position: fixed;
            background: #333;
            color: white;
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 12px;
            z-index: 10000;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.2s;
        }
        .tooltip-popup.visible {
            opacity: 1;
        }
    `;
    document.head.appendChild(style);

    // --- Helper Functions ---

    function getVirtualNow() {
        return Date.now() + CONFIG.timeOffset;
    }

    function waitForElement(selector, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();
            const checkInterval = setInterval(() => {
                const element = document.querySelector(selector);
                if (element) {
                    clearInterval(checkInterval);
                    resolve(element);
                } else if (Date.now() - startTime > timeout) {
                    clearInterval(checkInterval);
                    reject(new Error(`Element ${selector} not found within ${timeout}ms`));
                }
            }, 100);
        });
    }

    async function fetchUserProfile(targetUserId) {
        try {
            if (!targetUserId) {
                console.error("Missing targetId");
                return null;
            }
            const response = await fetch('/GetUserProfile', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ "targetId": parseInt(targetUserId) })
            });

            if (!response.ok) {
                throw new Error(`Server returned ${response.status}`);
            }

            const data = await response.json();
            return data;
        } catch (err) {
            console.error("Failed to fetch user profile:", err);
            return null;
        }
    }

    function showTooltip(x, y, text) {
        let tooltip = document.getElementById('custom-tooltip');
        if (!tooltip) {
            tooltip = document.createElement('div');
            tooltip.id = 'custom-tooltip';
            tooltip.className = 'tooltip-popup';
            document.body.appendChild(tooltip);
        }
        tooltip.textContent = text;
        tooltip.style.left = x + 10 + 'px';
        tooltip.style.top = y + 'px';
        tooltip.classList.add('visible');

        setTimeout(() => {
            tooltip.classList.remove('visible');
        }, 2000);
    }

    // --- XP Tracking Logic ---

    function parseGuildMembers() {
        const container = document.getElementById('guildMembersContainer');
        if (!container) return null;

        const members = {};
        const memberRows = container.querySelectorAll('div.flex.items-center.justify-between.p-2.rounded-md.bg-white.shadow-sm');

        memberRows.forEach(row => {
            const nameEl = row.querySelector('p.font-semibold');
            const xpEl = row.querySelector('p.text-xs.text-gray-500');

            if (nameEl && xpEl) {
                let fullName = nameEl.textContent.trim();
                const badge = nameEl.querySelector('span');
                if (badge) {
                    fullName = fullName.replace(badge.textContent, '').trim();
                }

                const xpText = xpEl.textContent;
                const xpMatch = xpText.match(/([\d,.]+)\s*XP$/);

                // Extract Coords
                let coords = null;
                const findBtn = row.querySelector('button[onclick^="goToGridLocation"]');
                if (findBtn) {
                    const match = findBtn.getAttribute('onclick').match(/goToGridLocation\((-?\d+),\s*(-?\d+)\)/);
                    if (match) {
                        coords = [parseInt(match[1]), parseInt(match[2])];
                    }
                }

                if (fullName && xpMatch) {
                    const xp = parseInt(xpMatch[1].replace(/[.,]/g, ''), 10);
                    members[fullName] = { xp, coords };
                }
            }
        });

        return members;
    }

    function saveGuildSnapshot(members, forceNew = false) {
        const now = getVirtualNow();
        let history = GM_getValue('guild_xp_history', []);

        const lastEntry = history[history.length - 1];
        const lastBucketStart = lastEntry ? (lastEntry.bucketStartTime || lastEntry.timestamp) : 0;

        const newEntry = {
            timestamp: now,
            bucketStartTime: now,
            members: members
        };

        if (!forceNew && lastEntry && (now - lastBucketStart < CONFIG.minSnapshotInterval)) {
            newEntry.bucketStartTime = lastBucketStart;
            history[history.length - 1] = newEntry;
            if (CONFIG.debugMode) console.log('[Guild XP] Updated recent snapshot');
        } else {
            history.push(newEntry);
            console.log('[Guild XP] Created new snapshot');
        }

        if (history.length > CONFIG.maxSnapshots) {
            history = history.slice(history.length - CONFIG.maxSnapshots);
        }

        GM_setValue('guild_xp_history', history);
        return history;
    }

    function getXp(val) {
        if (typeof val === 'number') return val;
        if (val && typeof val === 'object' && val.xp !== undefined) return val.xp;
        return 0;
    }

    function getCoords(val) {
        if (val && typeof val === 'object' && val.coords) return val.coords;
        return null;
    }

    async function fetchAllGuildMembersData() {
        const currentMembers = parseGuildMembers();
        if (!currentMembers || Object.keys(currentMembers).length === 0) {
            alert('No guild members found. Please wait for members to load.');
            return null;
        }

        const memberNames = Object.keys(currentMembers);
        const allUsersData = [];
        let successCount = 0;
        let failCount = 0;

        // Show progress indicator
        const progressDiv = document.createElement('div');
        progressDiv.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            z-index: 10000;
            min-width: 300px;
            text-align: center;
        `;
        progressDiv.innerHTML = `
            <p style="font-weight: bold; margin-bottom: 10px;">Fetching guild member data...</p>
            <p id="progressText" style="font-size: 14px; color: #666;">0/${memberNames.length}</p>
            <div style="width: 100%; height: 20px; background: #e5e7eb; border-radius: 4px; margin-top: 10px; overflow: hidden;">
                <div id="progressBar" style="height: 100%; background: #3b82f6; width: 0%; transition: width 0.3s;"></div>
            </div>
        `;
        document.body.appendChild(progressDiv);

        // Fetch data for each member
        for (let i = 0; i < memberNames.length; i++) {
            const memberName = memberNames[i];
            const match = memberName.match(/#(\d+)$/);

            if (match) {
                const userId = match[1];
                const data = await fetchUserProfile(userId);

                if (data) {
                    allUsersData.push(data);
                    successCount++;
                } else {
                    failCount++;
                }
            } else {
                failCount++;
            }

            // Update progress
            const progressPercent = ((i + 1) / memberNames.length) * 100;
            document.getElementById('progressBar').style.width = progressPercent + '%';
            document.getElementById('progressText').textContent = `${i + 1}/${memberNames.length} (${successCount} fetched)`;
        }

        // Copy to clipboard
        const jsonString = JSON.stringify(allUsersData, null, 2);
        navigator.clipboard.writeText(jsonString).then(() => {
            progressDiv.innerHTML = `
                <p style="font-weight: bold; color: #10b981; margin-bottom: 5px;">✓ Success!</p>
                <p style="font-size: 14px; color: #666;">
                    Fetched: ${successCount} users<br>
                    Failed: ${failCount} users<br><br>
                    <strong>JSON copied to clipboard!</strong>
                </p>
            `;
            setTimeout(() => progressDiv.remove(), 3000);
        }).catch((err) => {
            progressDiv.innerHTML = `
                <p style="font-weight: bold; color: #dc2626;">Error copying to clipboard!</p>
                <p style="font-size: 12px; color: #666;">${err.message}</p>
            `;
            setTimeout(() => progressDiv.remove(), 3000);
        });

        return allUsersData;
    }

    function calculateXPChanges(oldMembers, newMembers) {
        const changes = [];

        for (const [id, oldVal] of Object.entries(oldMembers)) {
            const oldXp = getXp(oldVal);
            if (newMembers.hasOwnProperty(id)) {
                const newVal = newMembers[id];
                const newXp = getXp(newVal);
                const diff = newXp - oldXp;
                const coords = getCoords(newVal) || getCoords(oldVal);
                changes.push({ type: 'gain', id, diff, oldXp, newXp, coords });
            } else {
                const coords = getCoords(oldVal);
                changes.push({ type: 'left', id, oldXp, coords });
            }
        }

        for (const [id, newVal] of Object.entries(newMembers)) {
            if (!oldMembers.hasOwnProperty(id)) {
                const newXp = getXp(newVal);
                const coords = getCoords(newVal);
                changes.push({ type: 'join', id, newXp, coords });
            }
        }

        return changes;
    }

    // Helper function to get color based on coordinate quadrant and distance
    function getCoordinateColor(coords) {
        if (!coords || coords.length < 2) return { bg: '#f3f4f6', text: '#1f2937' };

        const x = coords[0];
        const y = coords[1];

        // Calculate distance from origin
        const distance = Math.sqrt(x * x + y * y);
        const distanceBand = Math.floor(distance / 25000);

        // Base colors for each quadrant with intensity variation (very light/transparent)
        let baseColor;

        if (x >= 0 && y >= 0) {
            // Top Right - Green tint
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(120, 50%, ${97 - intensity}%)`;
        } else if (x < 0 && y >= 0) {
            // Top Left - Red tint
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(0, 50%, ${97 - intensity}%)`;
        } else if (x < 0 && y < 0) {
            // Bottom Left - Blue tint
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(240, 50%, ${97 - intensity}%)`;
        } else {
            // Bottom Right (x >= 0, y < 0) - Orange tint
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(30, 50%, ${97 - intensity}%)`;
        }

        return {
            bg: baseColor,
            text: '#1f2937'
        };
    }

    // --- XP Changes Section (Embedded) ---
    function ensureXPChangesSection() {
        // Find the tab navigation container by looking for the Information tab button
        const infoBtn = document.getElementById('infoTabBtn');
        if (!infoBtn) {
            // If we can't even find the info button, we can't inject our tab properly.
            // Fallback to legacy if we can find the infoTab content at least.
            if (document.getElementById('infoTab')) {
                console.log('[Guild XP] Could not find tab buttons, appending to infoTab instead');
                ensureXPChangesSectionLegacy();
            }
            return;
        }

        const tabNav = infoBtn.parentElement;

        // Check if our tab already exists
        if (document.getElementById('xpTrackerTabBtn')) return;

        // Remove any existing panes to prevent duplicates
        const existingPanes = document.querySelectorAll('#xpTrackerPane');
        existingPanes.forEach(pane => pane.remove());

        // Create XP Tracker tab button
        const xpTabBtn = document.createElement('button');
        xpTabBtn.textContent = 'XP Tracker';
        xpTabBtn.id = 'xpTrackerTabBtn';

        // Copy classes from the Information button (or a sibling)
        // We want it to look like an inactive tab initially
        xpTabBtn.className = infoBtn.className;
        // Force inactive style initially
        xpTabBtn.classList.remove('text-blue-600', 'border-blue-500');
        xpTabBtn.classList.add('text-gray-500', 'border-transparent');
        xpTabBtn.style.borderBottom = '2px solid transparent';

        // Create XP Tracker tab pane
        const xpTabPane = document.createElement('div');
        xpTabPane.id = 'xpTrackerPane';
        xpTabPane.style.display = 'none';
        xpTabPane.className = 'hidden guild-tab-content';

        // Find the content container
        const infoTab = document.getElementById('infoTab');
        const contentContainer = infoTab?.parentElement;

        if (!contentContainer) {
            console.log('[Guild XP] Could not find content container');
            ensureXPChangesSectionLegacy();
            return;
        }

        // Add click handler to tab button
        xpTabBtn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();

            // Hide all tabs
            const allPanes = contentContainer.querySelectorAll('.guild-tab-content, [id$="Tab"], [id$="Pane"]');
            allPanes.forEach(pane => {
                pane.style.display = 'none';
                pane.classList.add('hidden');
            });

            // Deactivate all tab buttons
            const allBtns = tabNav.querySelectorAll('button');
            allBtns.forEach(btn => {
                btn.classList.remove('text-blue-600', 'border-blue-500');
                btn.classList.add('text-gray-500', 'border-transparent');
                btn.style.borderBottom = '2px solid transparent';
                btn.style.color = '';
            });

            // Show this tab
            xpTabPane.style.display = 'block';
            xpTabPane.classList.remove('hidden');

            // Activate this button
            xpTabBtn.classList.remove('text-gray-500', 'border-transparent');
            xpTabBtn.classList.add('text-blue-600', 'border-blue-500');
            xpTabBtn.style.borderBottom = '2px solid #3b82f6';
            xpTabBtn.style.color = '#3b82f6';

            // Render content
            renderXPChanges(xpTabPane);
        };

        // Hook into other tab buttons to deactivate our tab when they are clicked
        const existingTabs = tabNav.querySelectorAll('button');
        existingTabs.forEach(btn => {
            if (btn.id === 'xpTrackerTabBtn' || btn.dataset.xpTrackerHooked) return;

            const originalOnClick = btn.onclick;
            btn.onclick = (e) => {
                // Hide our tab
                xpTabPane.style.display = 'none';
                xpTabPane.classList.add('hidden');

                // Reset inline styles on other tabs so game logic can control them
                const allPanes = contentContainer.querySelectorAll('.guild-tab-content');
                allPanes.forEach(pane => {
                    if (pane.id !== 'xpTrackerPane') {
                        pane.style.display = '';
                    }
                });

                // Deactivate our button
                xpTabBtn.classList.remove('text-blue-600', 'border-blue-500');
                xpTabBtn.classList.add('text-gray-500', 'border-transparent');
                xpTabBtn.style.borderBottom = '2px solid transparent';
                xpTabBtn.style.color = '';

                // Call original handler
                if (originalOnClick) originalOnClick.call(btn, e);
            };
            btn.dataset.xpTrackerHooked = 'true';
        });

        // Append tab button to nav
        tabNav.appendChild(xpTabBtn);

        // Append tab pane to content container
        contentContainer.appendChild(xpTabPane);

        // Add observer to ensure button stays
        const navObserver = new MutationObserver((mutations) => {
            if (!document.getElementById('xpTrackerTabBtn')) {
                // Re-append if missing
                tabNav.appendChild(xpTabBtn);
            }
        });
        navObserver.observe(tabNav, { childList: true });
    }

    function ensureXPChangesSectionLegacy() {
        const infoTab = document.getElementById('infoTab');
        if (!infoTab || document.getElementById('xpChangesSection')) return;

        const section = document.createElement('div');
        section.id = 'xpChangesSection';
        section.className = 'xp-changes-section';

        const header = document.createElement('div');
        header.className = 'xp-changes-header';
        header.innerHTML = `<span>XP Changes Tracker</span><span class="toggle-icon">▼</span>`;

        const content = document.createElement('div');
        content.className = 'xp-changes-content hidden';
        content.id = 'xpChangesContent';

        header.onclick = () => {
            content.classList.toggle('hidden');
            const icon = header.querySelector('.toggle-icon');
            icon.style.transform = content.classList.contains('hidden') ? 'rotate(0deg)' : 'rotate(180deg)';

            if (!content.classList.contains('hidden')) {
                renderXPChanges(content);
            }
        };

        section.appendChild(header);
        section.appendChild(content);

        // Append to infoTab, after the grid
        infoTab.appendChild(section);
    }

    function collapseOtherSections() {
        const messageSection = document.querySelector('.guild-message-section');
        if (messageSection) {
            const content = messageSection.querySelector('.guild-message-content');
            const header = messageSection.querySelector('.guild-message-header');
            const toggle = messageSection.querySelector('.guild-message-toggle');
            if (content && !content.classList.contains('collapsed')) {
                content.classList.add('collapsed');
                toggle.classList.add('collapsed');
                document.getElementById('infoTab').classList.add('message-collapsed');
            }
        }
    }

    function expandOtherSections() {
        const messageSection = document.querySelector('.guild-message-section');
        if (messageSection) {
            const content = messageSection.querySelector('.guild-message-content');
            const toggle = messageSection.querySelector('.guild-message-toggle');
            if (content && content.classList.contains('collapsed')) {
                content.classList.remove('collapsed');
                toggle.classList.remove('collapsed');
                document.getElementById('infoTab').classList.remove('message-collapsed');
            }
        }
    }

    function exportToCSV(snapshots, currentMembers, fromVal, toVal) {
        // Determine which snapshots to compare based on current selection
        // If called from the button, we might need to pass these values or read them from DOM
        // But since this function was originally designed to dump EVERYTHING, let's adapt it
        // to dump the CURRENT VIEW if specific snapshots are provided, or EVERYTHING if not.

        let csvContent = '';

        if (fromVal !== undefined && toVal !== undefined) {
            // Export current view (comparison)
            const getSnapshot = (val) => val === 'current' ? { members: currentMembers } : snapshots[val];
            const fromData = getSnapshot(fromVal);
            const toData = getSnapshot(toVal);

            if (!fromData || !toData) return;

            const changes = calculateXPChanges(fromData.members, toData.members);

            // Sort (same as view)
            changes.sort((a, b) => {
                if (a.type === 'join') return -1;
                if (b.type === 'join') return 1;
                if (a.type === 'left') return 1;
                if (b.type === 'left') return -1;
                return b.diff - a.diff;
            });

            const csvRows = [
                ["Username", "Change Type", "XP Change", "Old XP", "New XP"],
                ...changes.map(c => {
                    const oldVal = c.oldXp || 0;
                    const newVal = c.newXp || 0;
                    const diff = c.diff !== undefined ? c.diff : (newVal - oldVal);
                    return [`"${c.id}"`, c.type, diff, oldVal, newVal];
                })
            ];
            csvContent = csvRows.map(e => e.join(",")).join("\n");

        } else {
            // Export Full History (Legacy behavior)
            let csv = 'Snapshot,Timestamp,User,XP\n';
            snapshots.forEach((snap, idx) => {
                const timestamp = new Date(snap.timestamp).toLocaleString();
                for (const [user, data] of Object.entries(snap.members)) {
                    const xp = data.xp || data;
                    csv += `${idx + 1},"${timestamp}","${user}",${xp}\n`;
                }
            });
            // Add current
            const now = new Date(getVirtualNow()).toLocaleString();
            for (const [user, data] of Object.entries(currentMembers)) {
                const xp = data.xp || data;
                csv += `Current,"${now}","${user}",${xp}\n`;
            }
            csvContent = csv;
        }

        // Open CSV Modal
        const csvOverlay = document.createElement('div');
        csvOverlay.style.position = 'fixed';
        csvOverlay.style.top = '0';
        csvOverlay.style.left = '0';
        csvOverlay.style.right = '0';
        csvOverlay.style.bottom = '0';
        csvOverlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
        csvOverlay.style.zIndex = '10001';
        csvOverlay.onclick = () => { csvOverlay.remove(); csvModal.remove(); };

        const csvModal = document.createElement('div');
        csvModal.style.position = 'fixed';
        csvModal.style.top = '50%';
        csvModal.style.left = '50%';
        csvModal.style.transform = 'translate(-50%, -50%)';
        csvModal.style.backgroundColor = 'white';
        csvModal.style.padding = '20px';
        csvModal.style.borderRadius = '8px';
        csvModal.style.boxShadow = '0 4px 20px rgba(0,0,0,0.2)';
        csvModal.style.zIndex = '10002';
        csvModal.style.width = '500px';
        csvModal.style.maxWidth = '90%';
        csvModal.style.display = 'flex';
        csvModal.style.flexDirection = 'column';
        csvModal.style.gap = '10px';

        const title = document.createElement('h3');
        title.textContent = 'CSV Export';
        title.style.margin = '0 0 10px 0';
        title.style.color = '#1e293b';
        title.style.fontSize = '1.25rem';
        title.style.fontWeight = '600';

        const textarea = document.createElement('textarea');
        textarea.value = csvContent;
        textarea.style.width = '100%';
        textarea.style.height = '300px';
        textarea.style.fontFamily = 'monospace';
        textarea.style.fontSize = '12px';
        textarea.style.border = '1px solid #ccc';
        textarea.style.borderRadius = '4px';
        textarea.style.resize = 'vertical';
        textarea.readOnly = true;
        textarea.onclick = () => textarea.select();

        const btnRow = document.createElement('div');
        btnRow.style.display = 'flex';
        btnRow.style.justifyContent = 'flex-end';
        btnRow.style.gap = '10px';

        const copyBtn = document.createElement('button');
        copyBtn.innerHTML = '📋 Copy';
        copyBtn.className = 'control-button';
        copyBtn.onclick = () => {
            textarea.select();
            navigator.clipboard.writeText(csvContent).then(() => {
                const orig = copyBtn.innerHTML;
                copyBtn.innerHTML = '✅ Copied!';
                setTimeout(() => copyBtn.innerHTML = orig, 1000);
            });
        };

        const downloadBtn = document.createElement('button');
        downloadBtn.innerHTML = '💾 Download';
        downloadBtn.className = 'control-button';
        downloadBtn.style.backgroundColor = '#10b981';
        downloadBtn.style.color = 'white';
        downloadBtn.style.borderColor = '#10b981';
        downloadBtn.onclick = () => {
            const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
            const link = document.createElement("a");
            if (link.download !== undefined) {
                const url = URL.createObjectURL(blob);
                link.setAttribute("href", url);
                link.setAttribute("download", `guild_xp_export_${Date.now()}.csv`);
                link.style.visibility = 'hidden';
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
            }
        };

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = 'Close';
        closeBtn.className = 'control-button';
        closeBtn.onclick = () => { csvOverlay.remove(); csvModal.remove(); };

        btnRow.appendChild(copyBtn);
        btnRow.appendChild(downloadBtn);
        btnRow.appendChild(closeBtn);

        csvModal.appendChild(title);
        csvModal.appendChild(textarea);
        csvModal.appendChild(btnRow);

        document.body.appendChild(csvOverlay);
        document.body.appendChild(csvModal);
    }

    // --- History Pruning Functions ---

    function deleteAllHistory() {
        if (confirm('Delete ALL snapshots? This cannot be undone.')) {
            GM_setValue('guild_xp_history', []);
            return [];
        }
        return null;
    }

    function keepDailyHistory() {
        let history = GM_getValue('guild_xp_history', []);
        const dailyMap = new Map();

        // Group by day (YYYY-MM-DD)
        history.forEach(entry => {
            const date = new Date(entry.timestamp);
            const dayKey = date.toISOString().split('T')[0]; // YYYY-MM-DD

            // Keep the latest snapshot from each day
            if (!dailyMap.has(dayKey) || entry.timestamp > dailyMap.get(dayKey).timestamp) {
                dailyMap.set(dayKey, entry);
            }
        });

        const pruned = Array.from(dailyMap.values()).sort((a, b) => a.timestamp - b.timestamp);
        const removed = history.length - pruned.length;

        if (confirm(`This will keep only the latest snapshot from each day.\nSnapshots: ${history.length} → ${pruned.length} (removing ${removed}).\nContinue?`)) {
            GM_setValue('guild_xp_history', pruned);
            return pruned;
        }
        return null;
    }

    function keepWeeklyHistory() {
        let history = GM_getValue('guild_xp_history', []);
        const weeklyMap = new Map();

        // Group by week (ISO week)
        history.forEach(entry => {
            const date = new Date(entry.timestamp);
            const dayOfWeek = date.getUTCDay();
            const diff = date.getUTCDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1);
            const weekStart = new Date(date.setUTCDate(diff));
            const weekKey = weekStart.toISOString().split('T')[0]; // Start of week (YYYY-MM-DD)

            // Keep the latest snapshot from each week
            if (!weeklyMap.has(weekKey) || entry.timestamp > weeklyMap.get(weekKey).timestamp) {
                weeklyMap.set(weekKey, entry);
            }
        });

        const pruned = Array.from(weeklyMap.values()).sort((a, b) => a.timestamp - b.timestamp);
        const removed = history.length - pruned.length;

        if (confirm(`This will keep only the latest snapshot from each week.\nSnapshots: ${history.length} → ${pruned.length} (removing ${removed}).\nContinue?`)) {
            GM_setValue('guild_xp_history', pruned);
            return pruned;
        }
        return null;
    }

    function deleteHistoryOlderThan7Days() {
        let history = GM_getValue('guild_xp_history', []);
        const now = getVirtualNow();
        const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;

        const pruned = history.filter(entry => (now - entry.timestamp) <= sevenDaysMs);
        const removed = history.length - pruned.length;

        if (confirm(`This will delete all snapshots older than 7 days.\nSnapshots: ${history.length} → ${pruned.length} (removing ${removed}).\nContinue?`)) {
            GM_setValue('guild_xp_history', pruned);
            return pruned;
        }
        return null;
    }

    function renderCleanHistoryMenu(container, onClose) {
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9999;
        `;

        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 2px solid #3b82f6;
            border-radius: 8px;
            padding: 20px;
            z-index: 10000;
            min-width: 350px;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
        `;

        const title = document.createElement('h3');
        title.textContent = 'Clean History Options';
        title.style.cssText = 'margin: 0 0 15px 0; font-size: 18px; font-weight: bold; color: #1f2937;';
        modal.appendChild(title);

        const buttonStyles = `
            display: block;
            width: 100%;
            padding: 10px;
            margin: 8px 0;
            border: 1px solid #ddd;
            border-radius: 4px;
            background: white;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.2s;
            text-align: left;
        `;

        const deleteAllBtn = document.createElement('button');
        deleteAllBtn.innerHTML = 'Select All Snapshots for Deletion';
        deleteAllBtn.style.cssText = buttonStyles + ' color: #ef4444;';
        deleteAllBtn.onmouseover = (e) => e.target.style.backgroundColor = '#fee2e2';
        deleteAllBtn.onmouseout = (e) => e.target.style.backgroundColor = 'white';
        deleteAllBtn.onclick = () => {
            const result = deleteAllHistory();
            if (result !== null) {
                onClose(result);
            }
        };
        modal.appendChild(deleteAllBtn);

        const keepDailyBtn = document.createElement('button');
        keepDailyBtn.innerHTML = 'Keep One Snapshot Per Day (Latest)';
        keepDailyBtn.style.cssText = buttonStyles + ' color: #f59e0b;';
        keepDailyBtn.onmouseover = (e) => e.target.style.backgroundColor = '#fef3c7';
        keepDailyBtn.onmouseout = (e) => e.target.style.backgroundColor = 'white';
        keepDailyBtn.onclick = () => {
            const result = keepDailyHistory();
            if (result !== null) {
                onClose(result);
            }
        };
        modal.appendChild(keepDailyBtn);

        const keepWeeklyBtn = document.createElement('button');
        keepWeeklyBtn.innerHTML = 'Keep One Snapshot Per Week (Latest)';
        keepWeeklyBtn.style.cssText = buttonStyles + ' color: #3b82f6;';
        keepWeeklyBtn.onmouseover = (e) => e.target.style.backgroundColor = '#dbeafe';
        keepWeeklyBtn.onmouseout = (e) => e.target.style.backgroundColor = 'white';
        keepWeeklyBtn.onclick = () => {
            const result = keepWeeklyHistory();
            if (result !== null) {
                onClose(result);
            }
        };
        modal.appendChild(keepWeeklyBtn);

        const delete7DaysBtn = document.createElement('button');
        delete7DaysBtn.innerHTML = 'Select Snapshots Older Than 7 Days for Deletion';
        delete7DaysBtn.style.cssText = buttonStyles + ' color: #8b5cf6;';
        delete7DaysBtn.onmouseover = (e) => e.target.style.backgroundColor = '#f3e8ff';
        delete7DaysBtn.onmouseout = (e) => e.target.style.backgroundColor = 'white';
        delete7DaysBtn.onclick = () => {
            const result = deleteHistoryOlderThan7Days();
            if (result !== null) {
                onClose(result);
            }
        };
        modal.appendChild(delete7DaysBtn);

        const cancelBtn = document.createElement('button');
        cancelBtn.innerHTML = 'Cancel';
        cancelBtn.style.cssText = buttonStyles + ' color: #6b7280; margin-top: 15px; border-top: 1px solid #ddd; padding-top: 15px;';
        cancelBtn.onmouseover = (e) => e.target.style.backgroundColor = '#f3f4f6';
        cancelBtn.onmouseout = (e) => e.target.style.backgroundColor = 'white';
        cancelBtn.onclick = () => {
            overlay.remove();
            modal.remove();
        };
        modal.appendChild(cancelBtn);

        overlay.onclick = () => {
            overlay.remove();
            modal.remove();
        };

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

    // --- Export/Import Functions ---

    function exportSnapshots() {
        let history = GM_getValue('guild_xp_history', []);
        if (history.length === 0) {
            alert('No snapshots to export.');
            return;
        }

        const exportData = {
            version: 1,
            exportDate: new Date().toISOString(),
            snapshotCount: history.length,
            snapshots: history
        };

        const jsonString = JSON.stringify(exportData, null, 2);
        const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8;' });
        const link = document.createElement('a');
        const url = URL.createObjectURL(blob);
        link.setAttribute('href', url);
        link.setAttribute('download', `guild_snapshots_${Date.now()}.json`);
        link.style.visibility = 'hidden';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);

        alert(`Exported ${history.length} snapshots successfully.`);
    }

    function importSnapshots() {
        if (!confirm('WARNING: Importing will ERASE all current snapshots and replace them with the imported data.\n\nAre you sure you want to continue?')) {
            return;
        }

        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json';
        input.onchange = (e) => {
            const file = e.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = (event) => {
                try {
                    const importData = JSON.parse(event.target.result);

                    if (!importData.snapshots || !Array.isArray(importData.snapshots)) {
                        alert('Invalid snapshot file format.');
                        return;
                    }

                    if (importData.snapshots.length === 0) {
                        alert('No snapshots found in file.');
                        return;
                    }

                    GM_setValue('guild_xp_history', importData.snapshots);
                    alert(`Successfully imported ${importData.snapshots.length} snapshots.`);

                    // Refresh the UI if open
                    const xpTrackerPane = document.getElementById('xpTrackerPane');
                    if (xpTrackerPane && xpTrackerPane.style.display !== 'none') {
                        renderXPChanges(xpTrackerPane);
                    }
                } catch (error) {
                    alert(`Error importing file: ${error.message}`);
                }
            };
            reader.readAsText(file);
        };
        input.click();
    }

    function renderCleanHistoryModal(onClose) {
        let history = GM_getValue('guild_xp_history', []);
        const selectedIndices = new Set();

        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9999;
        `;

        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 2px solid #3b82f6;
            border-radius: 8px;
            padding: 20px;
            z-index: 10000;
            width: 90%;
            max-width: 600px;
            max-height: 80vh;
            display: flex;
            flex-direction: column;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
        `;

        // Header
        const header = document.createElement('div');
        header.style.cssText = 'margin-bottom: 15px; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;';

        const title = document.createElement('h3');
        title.textContent = 'Manage Snapshots';
        title.style.cssText = 'margin: 0 0 10px 0; font-size: 18px; font-weight: bold; color: #1f2937;';
        header.appendChild(title);

        const info = document.createElement('p');
        info.textContent = `Total snapshots: ${history.length}`;
        info.style.cssText = 'margin: 0; font-size: 12px; color: #6b7280;';
        header.appendChild(info);

        modal.appendChild(header);

        // Max snapshots control
        const maxSnapshotsDiv = document.createElement('div');
        maxSnapshotsDiv.style.cssText = `
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 15px;
            padding: 10px;
            background: #f9fafb;
            border-radius: 4px;
            border: 1px solid #e5e7eb;
        `;

        const maxLabel = document.createElement('label');
        maxLabel.textContent = 'Max Snapshots:';
        maxLabel.style.cssText = 'font-weight: 600; font-size: 12px; color: #374151; user-select: none;';
        maxSnapshotsDiv.appendChild(maxLabel);

        const maxSelect = document.createElement('select');
        maxSelect.style.cssText = `
            padding: 6px 10px;
            border: 1px solid #d1d5db;
            border-radius: 4px;
            background: white;
            font-size: 12px;
            cursor: pointer;
            color: #374151;
        `;

        const presets = [50, 100, 250, 500, 750, 1000, 2500, 5000, 10000];
        presets.forEach(value => {
            const option = document.createElement('option');
            option.value = value;
            option.textContent = value;
            if (value === CONFIG.maxSnapshots) option.selected = true;
            maxSelect.appendChild(option);
        });

        maxSelect.onchange = (e) => {
            const newMax = parseInt(e.target.value);
            CONFIG.maxSnapshots = newMax;
            GM_setValue('max_snapshots', newMax);
        };

        maxSnapshotsDiv.appendChild(maxSelect);
        modal.appendChild(maxSnapshotsDiv);

        // Snapshot Interval Control
        const intervalDiv = document.createElement('div');
        intervalDiv.style.cssText = `
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 15px;
            padding: 10px;
            background: #f9fafb;
            border-radius: 4px;
            border: 1px solid #e5e7eb;
        `;

        const intervalLabel = document.createElement('label');
        intervalLabel.textContent = 'Snapshot Interval:';
        intervalLabel.style.cssText = 'font-weight: 600; font-size: 12px; color: #374151; user-select: none;';
        intervalDiv.appendChild(intervalLabel);

        const intervalSelect = document.createElement('select');
        intervalSelect.id = 'snapshotIntervalSelect';
        intervalSelect.style.cssText = `
            padding: 6px 10px;
            border: 1px solid #d1d5db;
            border-radius: 4px;
            background: white;
            font-size: 12px;
            cursor: pointer;
            color: #374151;
        `;

        const hourlyOpt = document.createElement('option');
        hourlyOpt.value = 'hourly';
        hourlyOpt.textContent = 'Hourly (1h)';
        intervalSelect.appendChild(hourlyOpt);

        const twelveHourOpt = document.createElement('option');
        twelveHourOpt.value = '12h';
        twelveHourOpt.textContent = '12 Hours';
        intervalSelect.appendChild(twelveHourOpt);

        const twentyFourHourOpt = document.createElement('option');
        twentyFourHourOpt.value = '24h';
        twentyFourHourOpt.textContent = '24 Hours';
        intervalSelect.appendChild(twentyFourHourOpt);

        const customOpt = document.createElement('option');
        customOpt.value = 'custom';
        customOpt.textContent = `Custom (${formatSnapshotInterval(CONFIG.minSnapshotInterval)})`;
        intervalSelect.appendChild(customOpt);

        // Set current value
        updateSnapshotIntervalDropdown(intervalSelect);

        intervalSelect.onchange = (e) => {
            const selectedValue = e.target.value;
            if (selectedValue === 'hourly') {
                CONFIG.minSnapshotInterval = SNAPSHOT_INTERVALS.HOURLY;
            } else if (selectedValue === '12h') {
                CONFIG.minSnapshotInterval = SNAPSHOT_INTERVALS.TWELVE_HOURS;
            } else if (selectedValue === '24h') {
                CONFIG.minSnapshotInterval = SNAPSHOT_INTERVALS.TWENTY_FOUR_HOURS;
            } else if (selectedValue === 'custom') {
                const userInput = prompt("Enter custom snapshot interval in minutes:", (CONFIG.minSnapshotInterval / (60 * 1000)).toString());
                if (userInput !== null && userInput.trim() !== '') {
                    const minutes = parseFloat(userInput);
                    if (!isNaN(minutes) && minutes > 0) {
                        CONFIG.minSnapshotInterval = minutes * 60 * 1000;
                        const customOption = intervalSelect.querySelector('option[value="custom"]');
                        if (customOption) {
                            customOption.textContent = `Custom (${formatSnapshotInterval(CONFIG.minSnapshotInterval)})`;
                        }
                    } else {
                        alert("Invalid input. Please enter a positive number.");
                        updateSnapshotIntervalDropdown(intervalSelect);
                        return;
                    }
                } else {
                    updateSnapshotIntervalDropdown(intervalSelect);
                    return;
                }
            }

            // Persist the change
            GM_setValue('min_snapshot_interval', CONFIG.minSnapshotInterval);
        };

        intervalDiv.appendChild(intervalSelect);
        modal.appendChild(intervalDiv);

        // Track which preset option is selected (null = none, or the option name)
        let selectedPreset = null;

        // Shortcut options - mutually exclusive checkboxes + Select All toggle
        const shortcutsDiv = document.createElement('div');
        shortcutsDiv.style.cssText = `
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 8px;
            margin-bottom: 15px;
        `;

        const checkboxStyle = `
            display: flex;
            align-items: center;
            padding: 8px;
            border: 2px solid #ddd;
            border-radius: 4px;
            background: white;
            cursor: pointer;
            font-size: 12px;
            transition: all 0.2s;
        `;

        const checkboxInputStyle = `
            width: 16px;
            height: 16px;
            margin-right: 8px;
            cursor: pointer;
            accent-color: #3b82f6;
        `;

        // Helper function to update preset selection
        function updatePresetSelection(newPreset) {
            selectedPreset = selectedPreset === newPreset ? null : newPreset;

            // Clear the selection if switching presets
            selectedIndices.clear();

            if (selectedPreset === 'all') {
                // Select all snapshots
                if (history.length === 0) {
                    alert('No snapshots to select.');
                    selectedPreset = null;
                } else {
                    for (let i = 0; i < history.length; i++) {
                        selectedIndices.add(i);
                    }
                }
            } else if (selectedPreset === 'daily') {
                // Keep daily
                const dailyMap = new Map();
                history.forEach((entry, idx) => {
                    const date = new Date(entry.timestamp);
                    const dayKey = date.toISOString().split('T')[0];
                    if (!dailyMap.has(dayKey)) {
                        dailyMap.set(dayKey, []);
                    }
                    dailyMap.get(dayKey).push(idx);
                });
                dailyMap.forEach(indices => {
                    for (let i = 0; i < indices.length - 1; i++) {
                        selectedIndices.add(indices[i]);
                    }
                });
            } else if (selectedPreset === 'weekly') {
                // Keep weekly
                const weeklyMap = new Map();
                history.forEach((entry, idx) => {
                    const date = new Date(entry.timestamp);
                    const dayOfWeek = date.getUTCDay();
                    const diff = date.getUTCDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1);
                    const weekStart = new Date(date.setUTCDate(diff));
                    const weekKey = weekStart.toISOString().split('T')[0];
                    if (!weeklyMap.has(weekKey)) {
                        weeklyMap.set(weekKey, []);
                    }
                    weeklyMap.get(weekKey).push(idx);
                });
                weeklyMap.forEach(indices => {
                    for (let i = 0; i < indices.length - 1; i++) {
                        selectedIndices.add(indices[i]);
                    }
                });
            } else if (selectedPreset === '7days') {
                // 7+ days old
                const now = getVirtualNow();
                const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
                history.forEach((entry, idx) => {
                    if ((now - entry.timestamp) > sevenDaysMs) {
                        selectedIndices.add(idx);
                    }
                });
            }

            renderCheckboxList();
            updateCheckboxStates();
        }

        function updateCheckboxStates() {
            allCheckbox.checked = selectedPreset === 'all';
            dailyCheckbox.checked = selectedPreset === 'daily';
            weeklyCheckbox.checked = selectedPreset === 'weekly';
            deleteOldCheckbox.checked = selectedPreset === '7days';
        }

        // All snapshots checkbox
        const allOption = document.createElement('label');
        allOption.style.cssText = checkboxStyle + ' color: #ef4444; user-select: none;';
        const allCheckbox = document.createElement('input');
        allCheckbox.type = 'checkbox';
        allCheckbox.style.cssText = checkboxInputStyle;
        const allLabel = document.createElement('span');
        allLabel.textContent = 'Select All';
        allLabel.style.cssText = 'user-select: none;';
        allOption.appendChild(allCheckbox);
        allOption.appendChild(allLabel);
        allOption.onclick = (e) => {
            if (e.target === allCheckbox) updatePresetSelection('all');
        };
        allOption.onmouseover = (e) => e.currentTarget.style.borderColor = '#ef4444';
        allOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === 'all' ? '#ef4444' : '#ddd';
        shortcutsDiv.appendChild(allOption);

        // Keep daily checkbox
        const dailyOption = document.createElement('label');
        dailyOption.style.cssText = checkboxStyle + ' color: #f59e0b; user-select: none;';
        const dailyCheckbox = document.createElement('input');
        dailyCheckbox.type = 'checkbox';
        dailyCheckbox.style.cssText = checkboxInputStyle;
        dailyCheckbox.style.accentColor = '#f59e0b';
        const dailyLabel = document.createElement('span');
        dailyLabel.textContent = 'Keep One Per Day';
        dailyLabel.style.cssText = 'user-select: none;';
        dailyOption.appendChild(dailyCheckbox);
        dailyOption.appendChild(dailyLabel);
        dailyOption.onclick = (e) => {
            if (e.target === dailyCheckbox) updatePresetSelection('daily');
        };
        dailyOption.onmouseover = (e) => e.currentTarget.style.borderColor = '#f59e0b';
        dailyOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === 'daily' ? '#f59e0b' : '#ddd';
        shortcutsDiv.appendChild(dailyOption);

        // Keep weekly checkbox
        const weeklyOption = document.createElement('label');
        weeklyOption.style.cssText = checkboxStyle + ' color: #3b82f6; user-select: none;';
        const weeklyCheckbox = document.createElement('input');
        weeklyCheckbox.type = 'checkbox';
        weeklyCheckbox.style.cssText = checkboxInputStyle;
        const weeklyLabel = document.createElement('span');
        weeklyLabel.textContent = 'Keep One Per Week';
        weeklyLabel.style.cssText = 'user-select: none;';
        weeklyOption.appendChild(weeklyCheckbox);
        weeklyOption.appendChild(weeklyLabel);
        weeklyOption.onclick = (e) => {
            if (e.target === weeklyCheckbox) updatePresetSelection('weekly');
        };
        weeklyOption.onmouseover = (e) => e.currentTarget.style.borderColor = '#3b82f6';
        weeklyOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === 'weekly' ? '#3b82f6' : '#ddd';
        shortcutsDiv.appendChild(weeklyOption);

        // 7+ days old checkbox
        const deleteOldOption = document.createElement('label');
        deleteOldOption.style.cssText = checkboxStyle + ' color: #8b5cf6; user-select: none;';
        const deleteOldCheckbox = document.createElement('input');
        deleteOldCheckbox.type = 'checkbox';
        deleteOldCheckbox.style.cssText = checkboxInputStyle;
        deleteOldCheckbox.style.accentColor = '#8b5cf6';
        const deleteOldLabel = document.createElement('span');
        deleteOldLabel.textContent = 'Delete 7+ Days Old';
        deleteOldLabel.style.cssText = 'user-select: none;';
        deleteOldOption.appendChild(deleteOldCheckbox);
        deleteOldOption.appendChild(deleteOldLabel);
        deleteOldOption.onclick = (e) => {
            if (e.target === deleteOldCheckbox) updatePresetSelection('7days');
        };
        deleteOldOption.onmouseover = (e) => e.currentTarget.style.borderColor = '#8b5cf6';
        deleteOldOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === '7days' ? '#8b5cf6' : '#ddd';
        shortcutsDiv.appendChild(deleteOldOption);

        modal.appendChild(shortcutsDiv);

        // Snapshot list container
        const listContainer = document.createElement('div');
        listContainer.style.cssText = `
            flex: 1;
            overflow-y: auto;
            border: 1px solid #e5e7eb;
            border-radius: 4px;
            padding: 10px;
            margin-bottom: 15px;
            background: #f9fafb;
        `;
        modal.appendChild(listContainer);

        function renderCheckboxList() {
            listContainer.innerHTML = '';

            if (history.length === 0) {
                listContainer.innerHTML = '<p style="color: #6b7280; text-align: center; padding: 20px;">No snapshots available.</p>';
                return;
            }

            let currentDayKey = null;
            let useAltColor = false;

            history.forEach((entry, idx) => {
                const item = document.createElement('div');
                const isSelected = selectedIndices.has(idx);

                // Check if date changed
                const entryDate = new Date(entry.timestamp);
                const entryDayKey = entryDate.toISOString().split('T')[0]; // YYYY-MM-DD
                if (entryDayKey !== currentDayKey) {
                    currentDayKey = entryDayKey;
                    useAltColor = !useAltColor; // Toggle color when day changes
                }

                item.style.cssText = `
                    display: flex;
                    align-items: center;
                    padding: 8px;
                    margin: 4px 0;
                    background: ${isSelected ? '#fee2e2' : (useAltColor ? '#f3f4f6' : 'white')};
                    border-radius: 4px;
                    border: 1px solid ${isSelected ? '#fecaca' : '#e5e7eb'};
                    transition: background-color 0.2s, border-color 0.2s;
                `;

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.checked = selectedIndices.has(idx);
                checkbox.style.cssText = 'margin-right: 10px; cursor: pointer;';
                checkbox.onchange = (e) => {
                    if (e.target.checked) {
                        selectedIndices.add(idx);
                    } else {
                        selectedIndices.delete(idx);
                    }
                    renderCheckboxList();
                };
                item.appendChild(checkbox);

                const label = document.createElement('label');
                label.style.cssText = `flex: 1; cursor: pointer; font-size: 12px; color: ${isSelected ? '#991b1b' : '#374151'}; ${isSelected ? 'text-decoration: line-through;' : ''}`;
                label.onclick = () => {
                    checkbox.checked = !checkbox.checked;
                    if (checkbox.checked) {
                        selectedIndices.add(idx);
                    } else {
                        selectedIndices.delete(idx);
                    }
                    renderCheckboxList();
                };

                const timestamp = new Date(entry.timestamp);
                const memberCount = Object.keys(entry.members).length;
                label.innerHTML = `
                    <span style="font-weight: bold;">${idx + 1})</span>
                    ${timestamp.toLocaleString()}
                    <span style="color: ${isSelected ? '#b91c1c' : '#6b7280'};\">(${memberCount} members)</span>
                `;
                item.appendChild(label);

                listContainer.appendChild(item);
            });
        }

        renderCheckboxList();

        // Bottom buttons
        const buttonDiv = document.createElement('div');
        buttonDiv.style.cssText = `
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            border-top: 1px solid #e5e7eb;
            padding-top: 15px;
        `;

        const modernButtonStyle = (bgColor, textColor, hoverBg) => `
            flex: 1;
            min-width: 120px;
            padding: 12px 16px;
            border: none;
            border-radius: 6px;
            background: ${bgColor};
            color: ${textColor};
            font-weight: 600;
            font-size: 13px;
            cursor: pointer;
            transition: all 0.3s ease;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
        `;

        const deleteSelectedBtn = document.createElement('button');
        deleteSelectedBtn.innerHTML = '🗑️ Delete Selected';
        deleteSelectedBtn.style.cssText = modernButtonStyle('#dc2626', 'white', '#991b1b');
        deleteSelectedBtn.onmouseover = (e) => {
            e.target.style.backgroundColor = '#b91c1c';
            e.target.style.boxShadow = '0 4px 12px rgba(220, 38, 38, 0.3)';
        };
        deleteSelectedBtn.onmouseout = (e) => {
            e.target.style.backgroundColor = '#dc2626';
            e.target.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
        };
        deleteSelectedBtn.onclick = () => {
            if (selectedIndices.size === 0) {
                alert('No snapshots selected.');
                return;
            }
            const newHistory = history.filter((_, idx) => !selectedIndices.has(idx));
            const deleted = history.length - newHistory.length;
            if (confirm(`Delete ${deleted} snapshot(s)?`)) {
                GM_setValue('guild_xp_history', newHistory);
                overlay.remove();
                modal.remove();
                onClose(newHistory);
            }
        };
        buttonDiv.appendChild(deleteSelectedBtn);

        const exportBtn = document.createElement('button');
        exportBtn.innerHTML = '💾 Export Snapshots';
        exportBtn.style.cssText = modernButtonStyle('#3b82f6', 'white', '#1d4ed8');
        exportBtn.onmouseover = (e) => {
            e.target.style.backgroundColor = '#2563eb';
            e.target.style.boxShadow = '0 4px 12px rgba(59, 130, 246, 0.3)';
        };
        exportBtn.onmouseout = (e) => {
            e.target.style.backgroundColor = '#3b82f6';
            e.target.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
        };
        exportBtn.onclick = () => {
            exportSnapshots();
        };
        buttonDiv.appendChild(exportBtn);

        const importBtn = document.createElement('button');
        importBtn.innerHTML = '📥 Import Snapshots';
        importBtn.style.cssText = modernButtonStyle('#10b981', 'white', '#047857');
        importBtn.onmouseover = (e) => {
            e.target.style.backgroundColor = '#059669';
            e.target.style.boxShadow = '0 4px 12px rgba(16, 185, 129, 0.3)';
        };
        importBtn.onmouseout = (e) => {
            e.target.style.backgroundColor = '#10b981';
            e.target.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
        };
        importBtn.onclick = () => {
            importSnapshots();
        };
        buttonDiv.appendChild(importBtn);

        const cancelBtn = document.createElement('button');
        cancelBtn.innerHTML = '✕ Close';
        cancelBtn.style.cssText = modernButtonStyle('#f3f4f6', '#6b7280', '#e5e7eb');
        cancelBtn.onmouseover = (e) => {
            e.target.style.backgroundColor = '#e5e7eb';
            e.target.style.boxShadow = '0 4px 12px rgba(0,0,0,0.08)';
        };
        cancelBtn.onmouseout = (e) => {
            e.target.style.backgroundColor = '#f3f4f6';
            e.target.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
        };
        cancelBtn.onclick = () => {
            overlay.remove();
            modal.remove();
        };
        buttonDiv.appendChild(cancelBtn);

        modal.appendChild(buttonDiv);

        overlay.onclick = () => {
            overlay.remove();
            modal.remove();
        };

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

    function renderXPChanges(container) {
        container.innerHTML = '';
        container.style.display = 'flex';
        container.style.flexDirection = 'column';
        container.style.height = '100%';

        const currentMembers = parseGuildMembers();
        let history = GM_getValue('guild_xp_history', []);

        if (!currentMembers || Object.keys(currentMembers).length === 0) {
            container.innerHTML = '<p class="text-gray-500">Please wait for members to load...</p>';
            return;
        }

        // --- Controls ---
        const controls = document.createElement('div');
        controls.style.marginBottom = '15px';
        controls.style.display = 'flex';
        controls.style.flexDirection = 'column';
        controls.style.gap = '10px';

        // Snapshot Button + Action Buttons
        const snapRow = document.createElement('div');
        snapRow.style.display = 'flex';
        snapRow.style.justifyContent = 'flex-end';
        snapRow.style.gap = '8px';
        snapRow.style.flexWrap = 'wrap';

        const snapBtn = document.createElement('button');
        snapBtn.innerHTML = '📷 Take a Snapshot';
        snapBtn.className = 'control-button';
        snapBtn.onclick = () => {
            history = saveGuildSnapshot(currentMembers, true);
            renderXPChanges(container);
        };
        snapRow.appendChild(snapBtn);

        const csvBtn = document.createElement('button');
        csvBtn.innerHTML = '📥 Export CSV';
        csvBtn.className = 'control-button';
        csvBtn.onclick = () => {
            // Pass current selection to export function
            exportToCSV(history, currentMembers, fromSelect.value, toSelect.value);
        };
        snapRow.appendChild(csvBtn);

        const exportAllDataBtn = document.createElement('button');
        exportAllDataBtn.innerHTML = '🎨 Export All User Data';
        exportAllDataBtn.className = 'control-button';
        exportAllDataBtn.style.color = '#a855f7';
        exportAllDataBtn.title = 'Fetch and export all guild members\' data (including colors) as JSON';
        exportAllDataBtn.onclick = async () => {
            exportAllDataBtn.disabled = true;
            exportAllDataBtn.style.opacity = '0.5';
            await fetchAllGuildMembersData();
            exportAllDataBtn.disabled = false;
            exportAllDataBtn.style.opacity = '1';
        };
        snapRow.appendChild(exportAllDataBtn);

        const cleanBtn = document.createElement('button');
        cleanBtn.innerHTML = '🧹 Manage History';
        cleanBtn.className = 'control-button';
        cleanBtn.style.color = '#ef4444';
        cleanBtn.onclick = () => {
            renderCleanHistoryModal((newHistory) => {
                history = newHistory;
                renderXPChanges(container);
            });
        };
        snapRow.appendChild(cleanBtn);

        controls.appendChild(snapRow);

        // Selectors
        const getOptions = () => {
            const snaps = history.map((entry, index) => ({
                label: `${index + 1}) ${new Date(entry.timestamp).toLocaleString()}`,
                value: index,
                members: entry.members
            }));
            const curr = {
                label: `Now (${new Date(getVirtualNow()).toLocaleString()})`,
                value: 'current',
                members: currentMembers
            };
            return { snaps, curr, all: [...snaps, curr] };
        };

        let { snaps: snapshots, curr: currentSnapshot, all: allOptions } = getOptions();

        // Filter buttons
        const filterRow = document.createElement('div');
        filterRow.style.display = 'flex';
        filterRow.style.gap = '8px';
        filterRow.style.flexWrap = 'wrap';

        let filterMode = 'all'; // 'all', 'active', 'inactive'
        const allBtn = document.createElement('button');
        allBtn.innerHTML = 'Show All';
        allBtn.className = 'control-button active';
        allBtn.onclick = () => {
            filterMode = 'all';
            allBtn.classList.add('active');
            activeBtn.classList.remove('active');
            inactiveBtn.classList.remove('active');
            updateTable();
        };
        filterRow.appendChild(allBtn);

        const activeBtn = document.createElement('button');
        activeBtn.innerHTML = 'Active';
        activeBtn.className = 'control-button';
        activeBtn.onclick = () => {
            filterMode = 'active';
            allBtn.classList.remove('active');
            activeBtn.classList.add('active');
            inactiveBtn.classList.remove('active');
            updateTable();
        };
        filterRow.appendChild(activeBtn);

        const inactiveBtn = document.createElement('button');
        inactiveBtn.innerHTML = 'Inactive';
        inactiveBtn.className = 'control-button';
        inactiveBtn.onclick = () => {
            filterMode = 'inactive';
            allBtn.classList.remove('active');
            activeBtn.classList.remove('active');
            inactiveBtn.classList.add('active');
            updateTable();
        };
        filterRow.appendChild(inactiveBtn);
        controls.appendChild(filterRow);

        const row1 = document.createElement('div');
        row1.style.display = 'flex';
        row1.style.gap = '10px';
        row1.style.alignItems = 'center';
        row1.style.flexWrap = 'wrap';

        const fromSelect = document.createElement('select');
        fromSelect.style.flex = '1';
        fromSelect.style.padding = '4px';
        fromSelect.style.border = '2px solid #3b82f6';
        fromSelect.style.borderRadius = '4px';

        const toSelect = document.createElement('select');
        toSelect.style.flex = '1';
        toSelect.style.padding = '4px';
        toSelect.style.border = '2px solid #3b82f6';
        toSelect.style.borderRadius = '4px';

        // Populate
        allOptions.forEach(opt => {
            fromSelect.add(new Option(opt.label, opt.value));
            toSelect.add(new Option(opt.label, opt.value));
        });

        // Defaults
        if (snapshots.length >= 1) {
            fromSelect.value = snapshots[snapshots.length - 1].value;
        } else {
            fromSelect.value = 'current';
        }
        toSelect.value = 'current';

        row1.appendChild(document.createTextNode('From:'));
        row1.appendChild(fromSelect);

        // Delete "From" button
        const deleteFromBtn = document.createElement('button');
        deleteFromBtn.className = 'trash-btn';
        deleteFromBtn.innerHTML = '🗑️';
        deleteFromBtn.title = 'Delete this snapshot';
        deleteFromBtn.onclick = () => {
            const snapIndex = parseInt(fromSelect.value);
            if (snapIndex >= 0 && snapIndex < history.length) {
                if (confirm('Delete this snapshot?')) {
                    history.splice(snapIndex, 1);
                    GM_setValue('guild_xp_history', history);
                    renderXPChanges(container);
                }
            }
        };
        row1.appendChild(deleteFromBtn);

        row1.appendChild(document.createTextNode('To:'));
        row1.appendChild(toSelect);

        // Delete "To" button
        const deleteToBtn = document.createElement('button');
        deleteToBtn.className = 'trash-btn';
        deleteToBtn.innerHTML = '🗑️';
        deleteToBtn.title = 'Delete this snapshot';
        deleteToBtn.onclick = () => {
            const snapIndex = parseInt(toSelect.value);
            if (snapIndex >= 0 && snapIndex < history.length) {
                if (confirm('Delete this snapshot?')) {
                    history.splice(snapIndex, 1);
                    GM_setValue('guild_xp_history', history);
                    renderXPChanges(container);
                }
            }
        };
        row1.appendChild(deleteToBtn);

        controls.appendChild(row1);

        // Results Area
        const resultsDiv = document.createElement('div');
        resultsDiv.style.flex = '1';
        resultsDiv.style.overflowY = 'auto';
        resultsDiv.style.minHeight = '0'; // Crucial for flexbox scrolling
        resultsDiv.style.border = '1px solid #e5e7eb';
        resultsDiv.style.borderRadius = '0.5rem';

        const updateTable = () => {
            resultsDiv.innerHTML = '';
            const fromVal = fromSelect.value;
            const toVal = toSelect.value;

            const fromData = fromVal === 'current' ? currentSnapshot : snapshots[fromVal];
            const toData = toVal === 'current' ? currentSnapshot : snapshots[toVal];

            if (!fromData || !toData) return;

            let changes = calculateXPChanges(fromData.members, toData.members);

            // Apply filter
            if (filterMode === 'active') {
                changes = changes.filter(c => {
                    // Active = Joined OR Positive XP Gain
                    return c.type === 'join' || c.diff > 0;
                });
            } else if (filterMode === 'inactive') {
                changes = changes.filter(c => {
                    // Inactive = Left OR Zero/Negative XP Gain
                    return c.type === 'left' || c.diff <= 0;
                });
            }

            // Sort
            changes.sort((a, b) => {
                if (a.type === 'join') return -1;
                if (b.type === 'join') return 1;
                if (a.type === 'left') return 1;
                if (b.type === 'left') return -1;
                return b.diff - a.diff;
            });

            const table = document.createElement('table');
            table.className = 'daily-brief-table';
            table.innerHTML = `<thead><tr><th>User</th><th>Change</th><th>Details</th></tr></thead>`;
            const tbody = document.createElement('tbody');

            if (changes.length === 0) {
                tbody.innerHTML = `<tr><td colspan="3" style="text-align:center">No changes.</td></tr>`;
            } else {
                changes.forEach(change => {
                    const tr = document.createElement('tr');

                    // User Cell with Buttons and Coordinates
                    const userTd = document.createElement('td');
                    userTd.style.display = 'flex';
                    userTd.style.alignItems = 'center';
                    userTd.style.gap = '4px';

                    // Create user info (name only)
                    const nameSpan = document.createElement('span');
                    nameSpan.className = 'user-name';
                    nameSpan.textContent = change.id;

                    userTd.appendChild(nameSpan);

                    // Extract ID
                    const match = change.id.match(/#(\d+)$/);
                    if (match) {
                        const userId = match[1];
                        // Discord Button
                        const discordBtn = document.createElement('button');
                        discordBtn.className = 'member-icon-btn discord-icon';
                        discordBtn.title = 'Check Discord';
                        discordBtn.innerHTML = `
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36" width="16" height="16" fill="currentColor">
                                <path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.11,77.11,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22c1.24-23.25-13.28-47.54-18.9-72.15ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
                            </svg>
                        `;
                        discordBtn.onclick = async (e) => {
                            e.stopPropagation();
                            const data = await fetchUserProfile(userId);
                            if (data && data.discordUser) {
                                navigator.clipboard.writeText(data.discordUser).then(() => {
                                    showTooltip(e.clientX, e.clientY, `Discord ID: ${data.discordUser} copied!`);
                                });
                            } else {
                                showTooltip(e.clientX, e.clientY, 'No Discord ID found.');
                            }
                        };
                        userTd.appendChild(discordBtn);
                    }

                    // Map Button
                    if (change.coords) {
                        const mapBtn = document.createElement('button');
                        mapBtn.className = 'member-icon-btn map-icon';
                        mapBtn.title = 'Find on Map';
                        mapBtn.innerHTML = `
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                <circle cx="12" cy="10" r="3"/>
                                <path d="M12 21.7C17.3 17 20 13 20 10a8 8 0 1 0-16 0c0 3 2.7 7 8 11.7z"/>
                            </svg>
                        `;
                        const coordKey = `${change.coords[0]},${change.coords[1]}`;
                        if (sessionState.visitedCoords.has(coordKey)) {
                            mapBtn.classList.add('visited');
                        }
                        mapBtn.onclick = () => {
                            // Find the original Find button in the member row and click it
                            const memberName = change.id;
                            const memberRows = document.querySelectorAll('#guildMembersContainer div.flex.items-center.justify-between');
                            let found = false;
                            for (const row of memberRows) {
                                const nameEl = row.querySelector('p.font-semibold');
                                if (nameEl) {
                                    // Remove badge the same way parseGuildMembers does
                                    let displayName = nameEl.textContent.trim();
                                    const badge = nameEl.querySelector('span');
                                    if (badge) {
                                        displayName = displayName.replace(badge.textContent, '').trim();
                                    }
                                    // Match by exact name to handle both users with and without usernames
                                    if (displayName === memberName) {
                                        const findBtn = row.querySelector('button[onclick^="goToGridLocation"]');
                                        if (findBtn) {
                                            findBtn.click();
                                            found = true;
                                            break;
                                        }
                                    }
                                }
                            }
                            if (!found && window.goToGridLocation) {
                                window.goToGridLocation(change.coords[0], change.coords[1]);
                            }
                            // Mark as visited
                            sessionState.visitedCoords.add(coordKey);
                            mapBtn.classList.add('visited');
                        };
                        userTd.appendChild(mapBtn);
                    }

                    // Display coordinates if available (right-aligned)
                    if (change.coords) {
                        const spacer = document.createElement('div');
                        spacer.style.flex = '1';
                        userTd.appendChild(spacer);

                        const coordsSpan = document.createElement('span');
                        coordsSpan.className = 'user-coords';

                        // Get colors based on quadrant and distance
                        const colors = getCoordinateColor(change.coords);
                        coordsSpan.style.backgroundColor = colors.bg;
                        coordsSpan.style.padding = '2px 6px';
                        coordsSpan.style.borderRadius = '3px';

                        // Create styled parts
                        const openParen = document.createElement('span');
                        openParen.style.color = colors.text;
                        openParen.textContent = '(';

                        const xVal = document.createElement('span');
                        xVal.style.color = colors.text;
                        xVal.style.fontWeight = '500';
                        xVal.textContent = change.coords[0];

                        const comma = document.createElement('span');
                        comma.style.color = colors.text;
                        comma.textContent = ', ';

                        const yVal = document.createElement('span');
                        yVal.style.color = colors.text;
                        yVal.style.fontWeight = '500';
                        yVal.textContent = change.coords[1];

                        const closeParen = document.createElement('span');
                        closeParen.style.color = colors.text;
                        closeParen.textContent = ')';

                        coordsSpan.appendChild(openParen);
                        coordsSpan.appendChild(xVal);
                        coordsSpan.appendChild(comma);
                        coordsSpan.appendChild(yVal);
                        coordsSpan.appendChild(closeParen);

                        userTd.appendChild(coordsSpan);
                    }

                    let changeCell = '';
                    if (change.type === 'gain') {
                        changeCell = change.diff > 0 ? `<td class="xp-gain">+${change.diff.toLocaleString()}</td>` :
                                     (change.diff < 0 ? `<td class="xp-loss">${change.diff.toLocaleString()}</td>` : `<td class="xp-neutral">0</td>`);
                    } else if (change.type === 'join') {
                        changeCell = `<td class="xp-gain">JOINED</td>`;
                    } else if (change.type === 'left') {
                        changeCell = `<td class="xp-loss">LEFT</td>`;
                    }

                    tr.appendChild(userTd);

                    // Change Cell
                    const changeTd = document.createElement('td');
                    changeTd.innerHTML = changeCell.replace(/^<td.*?>|<\/td>$/g, ''); // Strip outer td tags since we are creating td
                    changeTd.className = changeCell.match(/class="([^"]+)"/)?.[1] || '';
                    tr.appendChild(changeTd);

                    // Details Cell
                    const detailsTd = document.createElement('td');
                    detailsTd.textContent = `${change.oldXp?.toLocaleString() || 0} → ${change.newXp?.toLocaleString() || 0}`;
                    tr.appendChild(detailsTd);

                    tbody.appendChild(tr);
                });
            }
            table.appendChild(tbody);
            resultsDiv.appendChild(table);
        };

        fromSelect.onchange = updateTable;
        toSelect.onchange = updateTable;

        updateTable();

        container.appendChild(controls);
        container.appendChild(resultsDiv);
    }

    // --- Main Logic ---

    async function transformGuildModal() {
        try {
            await waitForElement('#myGuildModal', 10000);

            const modal = document.getElementById('myGuildModal');
            const panel = document.getElementById('myGuildPanel');

            if (!modal || !panel) {
                console.error('[Guild Modal] myGuildModal or myGuildPanel not found');
                return;
            }

            if (panel.classList.contains('draggable-panel')) {
                return;
            }

            modal.style.position = 'fixed';
            modal.style.inset = 'auto';
            modal.style.backgroundColor = 'transparent';
            modal.style.justifyContent = 'flex-start';
            modal.style.alignItems = 'flex-start';
            modal.style.padding = '0';
            modal.style.pointerEvents = 'none';

            panel.style.position = 'fixed';
            panel.style.top = '100px';
            panel.style.left = 'calc(50% - 25rem)';
            panel.style.width = '50rem';
            panel.style.maxWidth = '90vw';
            panel.style.maxHeight = '85vh';
            panel.style.zIndex = '40';
            panel.style.cursor = 'default';
            panel.style.transform = 'none';
            panel.style.opacity = '1';
            panel.style.scale = '1';
            panel.style.pointerEvents = 'auto';
            panel.classList.add('draggable-panel');

            const existingHeader = panel.querySelector('.guild-modal-header');
            if (existingHeader) existingHeader.remove();

            const headerBar = document.createElement('div');
            headerBar.className = 'guild-modal-header';
            headerBar.style.cssText = `
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                height: 40px;
                background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                cursor: move;
                border-radius: 0.75rem 0.75rem 0 0;
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 0 16px;
                color: white;
                font-weight: 600;
                user-select: none;
                z-index: 50;
                pointer-events: auto;
            `;

            const titleSpan = document.createElement('span');
            titleSpan.textContent = 'Guild Panel';
            titleSpan.style.cursor = 'move';

            const closeBtn = document.createElement('button');
            closeBtn.textContent = '✕';
            closeBtn.style.cssText = `
                background: none;
                border: none;
                color: white;
                font-size: 24px;
                cursor: pointer;
                padding: 0;
                margin: 0;
                display: flex;
                align-items: center;
                justify-content: center;
                width: 30px;
                height: 30px;
                border-radius: 4px;
                transition: background-color 0.2s;
            `;
            closeBtn.onmouseover = () => closeBtn.style.backgroundColor = 'rgba(255,255,255,0.2)';
            closeBtn.onmouseout = () => closeBtn.style.backgroundColor = 'transparent';
            closeBtn.onclick = (e) => {
                e.stopPropagation();
                if (typeof window.toggleMyGuildModal === 'function') {
                    window.toggleMyGuildModal();
                } else {
                    const originalClose = document.querySelector('#myGuildModal .close-modal, #myGuildModal [onclick*="toggleMyGuildModal"]');
                    if (originalClose) {
                        originalClose.click();
                    } else {
                        modal.style.display = 'none';
                    }
                }
            };

            headerBar.appendChild(titleSpan);
            headerBar.appendChild(closeBtn);

            const resizeHandle = document.createElement('div');
            resizeHandle.className = 'guild-modal-resize';
            resizeHandle.style.cssText = `
                position: absolute;
                bottom: 0;
                right: 0;
                width: 20px;
                height: 20px;
                cursor: nwse-resize;
                background: linear-gradient(135deg, transparent 0%, #3b82f6 100%);
                border-radius: 0 0 0.75rem 0;
                z-index: 51;
                pointer-events: auto;
            `;

            panel.style.paddingTop = '50px';

            if (panel.firstChild) {
                panel.insertBefore(headerBar, panel.firstChild);
            } else {
                panel.appendChild(headerBar);
            }

            panel.appendChild(resizeHandle);

            setupDragHandling(panel, titleSpan);
            setupResizeHandling(panel, resizeHandle);
            setupMessageCollapsible();
            setupContentTracking();

            console.log('[Guild Modal] Transformed to draggable floating panel');

        } catch (error) {
            console.error('[Guild Modal] Error transforming modal:', error);
        }
    }

    function setupDragHandling(panel, header) {
        let isDragging = false;
        let startX = 0;
        let startY = 0;
        let offsetX = 0;
        let offsetY = 0;

        const onMouseDown = (e) => {
            if (e.target.closest('.guild-modal-resize') || e.target.closest('button')) return;

            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;

            const rect = panel.getBoundingClientRect();
            offsetX = rect.left;
            offsetY = rect.top;

            panel.style.userSelect = 'none';

            document.addEventListener('mousemove', onMouseMove, true);
            document.addEventListener('mouseup', onMouseUp, true);

            e.preventDefault();
            e.stopPropagation();
        };

        const onMouseMove = (e) => {
            if (!isDragging) return;

            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;

            const newX = offsetX + deltaX;
            const newY = offsetY + deltaY;

            const maxX = window.innerWidth - 100;
            const maxY = window.innerHeight - 100;

            const constrainedX = Math.max(-panel.offsetWidth + 100, Math.min(newX, maxX));
            const constrainedY = Math.max(0, Math.min(newY, maxY));

            panel.style.left = constrainedX + 'px';
            panel.style.top = constrainedY + 'px';
            panel.style.transform = 'none';
            panel.style.position = 'fixed';

            e.preventDefault();
            e.stopPropagation();
        };

        const onMouseUp = (e) => {
            if (isDragging) {
                isDragging = false;
                panel.style.userSelect = 'auto';
                document.removeEventListener('mousemove', onMouseMove, true);
                document.removeEventListener('mouseup', onMouseUp, true);
                e.preventDefault();
                e.stopPropagation();
            }
        };

        header.addEventListener('mousedown', onMouseDown, false);
    }

    function setupResizeHandling(panel, resizeHandle) {
        let isResizing = false;
        let startX = 0;
        let startY = 0;
        let startWidth = 0;
        let startHeight = 0;

        const onMouseDown = (e) => {
            isResizing = true;
            startX = e.clientX;
            startY = e.clientY;
            startWidth = panel.offsetWidth;
            startHeight = panel.offsetHeight;

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);

            e.preventDefault();
        };

        const onMouseMove = (e) => {
            if (!isResizing) return;

            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;

            const newWidth = Math.max(400, startWidth + deltaX);
            const newHeight = Math.max(300, startHeight + deltaY);

            panel.style.width = newWidth + 'px';
            panel.style.height = newHeight + 'px';
        };

        const onMouseUp = () => {
            isResizing = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        };

        resizeHandle.addEventListener('mousedown', onMouseDown);
    }

    function setupMessageCollapsible() {
        const infoTab = document.getElementById('infoTab');
        const messageDiv = infoTab?.querySelector('div:has(> h3:first-child):has(#guildInfoMessage)');

        if (!messageDiv) return;

        const messageSection = document.createElement('div');
        messageSection.className = 'guild-message-section';

        const messageHeader = document.createElement('div');
        messageHeader.className = 'guild-message-header';

        const toggleSpan = document.createElement('span');
        toggleSpan.className = 'guild-message-toggle';
        toggleSpan.textContent = '▼';

        const titleSpan = document.createElement('span');
        titleSpan.textContent = 'Message';
        titleSpan.style.fontWeight = '500';

        messageHeader.appendChild(toggleSpan);
        messageHeader.appendChild(titleSpan);

        const messageContent = document.createElement('div');
        messageContent.className = 'guild-message-content';

        const h3 = messageDiv.querySelector('h3');
        const p = messageDiv.querySelector('#guildInfoMessage');

        if (h3 && p) {
            messageContent.appendChild(h3.cloneNode(true));
            messageContent.appendChild(p.cloneNode(true));

            messageDiv.parentNode.replaceChild(messageSection, messageDiv);
            messageSection.appendChild(messageHeader);
            messageSection.appendChild(messageContent);

            let isCollapsed = false;

            messageHeader.addEventListener('click', () => {
                isCollapsed = !isCollapsed;
                toggleSpan.classList.toggle('collapsed', isCollapsed);
                messageContent.classList.toggle('collapsed', isCollapsed);
                infoTab.classList.toggle('message-collapsed', isCollapsed);
            });
        }
    }

    function setupContentTracking() {
        const infoTab = document.getElementById('infoTab');
        if (!infoTab) return;

        const observer = new MutationObserver((mutations) => {
            let membersUpdated = false;

            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length) {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.id === 'guildMembersContainer' || node.querySelector('#guildMembersContainer')) {
                                membersUpdated = true;
                            }
                        }
                    });
                }
            });

            if (membersUpdated || (document.getElementById('guildMembersContainer') && document.getElementById('guildMembersContainer').children.length > 1)) {
                const members = parseGuildMembers();
                if (members && Object.keys(members).length > 0) {
                    saveGuildSnapshot(members);
                }
                ensureXPChangesSection();
            }
        });

        // Initial scan
        ensureXPChangesSection();

        observer.observe(infoTab, {
            childList: true,
            subtree: true
        });
    }

    // --- Snapshot Interval Helper Functions ---

    function formatSnapshotInterval(ms) {
        const seconds = ms / 1000;
        if (seconds < 60) return `${seconds}s`;
        const minutes = seconds / 60;
        if (minutes < 60) return `${minutes.toFixed(1)}m`;
        const hours = minutes / 60;
        if (hours < 24) return `${hours.toFixed(1)}h`;
        const days = hours / 24;
        return `${days.toFixed(1)}d`;
    }

    function getSnapshotIntervalLabel(ms) {
        if (ms === SNAPSHOT_INTERVALS.HOURLY) return 'Hourly (1h)';
        if (ms === SNAPSHOT_INTERVALS.TWELVE_HOURS) return '12 Hours';
        if (ms === SNAPSHOT_INTERVALS.TWENTY_FOUR_HOURS) return '24 Hours';
        return `Custom (${formatSnapshotInterval(ms)})`;
    }

    function updateSnapshotIntervalUI() {
        const dropdown = document.getElementById('snapshotIntervalSelect');
        if (dropdown) {
            updateSnapshotIntervalDropdown(dropdown);
        }
    }

    function updateSnapshotIntervalDropdown(dropdown) {
        // Update dropdown to show current value
        if (CONFIG.minSnapshotInterval === SNAPSHOT_INTERVALS.HOURLY) {
            dropdown.value = 'hourly';
        } else if (CONFIG.minSnapshotInterval === SNAPSHOT_INTERVALS.TWELVE_HOURS) {
            dropdown.value = '12h';
        } else if (CONFIG.minSnapshotInterval === SNAPSHOT_INTERVALS.TWENTY_FOUR_HOURS) {
            dropdown.value = '24h';
        } else {
            dropdown.value = 'custom';
            const customOption = dropdown.querySelector('option[value="custom"]');
            if (customOption) {
                customOption.textContent = `Custom (${formatSnapshotInterval(CONFIG.minSnapshotInterval)})`;
            }
        }
    }

    // --- Menu Commands ---

    // Snapshot Interval Menu Commands
    GM_registerMenuCommand("Snapshot Interval: Hourly", () => {
        CONFIG.minSnapshotInterval = SNAPSHOT_INTERVALS.HOURLY;
        GM_setValue('min_snapshot_interval', CONFIG.minSnapshotInterval);
        updateSnapshotIntervalUI();
        alert(`Snapshot Interval set to: Hourly (1 hour)`);
    });

    GM_registerMenuCommand("Snapshot Interval: 12 Hours", () => {
        CONFIG.minSnapshotInterval = SNAPSHOT_INTERVALS.TWELVE_HOURS;
        GM_setValue('min_snapshot_interval', CONFIG.minSnapshotInterval);
        updateSnapshotIntervalUI();
        alert(`Snapshot Interval set to: 12 Hours`);
    });

    GM_registerMenuCommand("Snapshot Interval: 24 Hours", () => {
        CONFIG.minSnapshotInterval = SNAPSHOT_INTERVALS.TWENTY_FOUR_HOURS;
        GM_setValue('min_snapshot_interval', CONFIG.minSnapshotInterval);
        updateSnapshotIntervalUI();
        alert(`Snapshot Interval set to: 24 Hours`);
    });

    GM_registerMenuCommand("Snapshot Interval: Custom", () => {
        const userInput = prompt("Enter custom snapshot interval in minutes:", (CONFIG.minSnapshotInterval / (60 * 1000)).toString());
        if (userInput !== null && userInput.trim() !== '') {
            const minutes = parseFloat(userInput);
            if (!isNaN(minutes) && minutes > 0) {
                CONFIG.minSnapshotInterval = minutes * 60 * 1000;
                GM_setValue('min_snapshot_interval', CONFIG.minSnapshotInterval);
                updateSnapshotIntervalUI();
                alert(`Snapshot Interval set to: ${minutes} minute(s) (${(CONFIG.minSnapshotInterval / 1000).toLocaleString()} seconds)`);
            } else {
                alert("Invalid input. Please enter a positive number.");
            }
        }
    });

    GM_registerMenuCommand("Toggle Debug Mode", () => {
        CONFIG.debugMode = !CONFIG.debugMode;
        alert(`Debug Mode: ${CONFIG.debugMode ? 'ON' : 'OFF'}`);
    });

    GM_registerMenuCommand("Time Travel: Advance 1 Day", () => {
        CONFIG.timeOffset += 24 * 60 * 60 * 1000;
        GM_setValue('debug_time_offset', CONFIG.timeOffset);
        const virtualDate = new Date(getVirtualNow());
        alert(`Time Travel Active! Virtual Date: ${virtualDate.toDateString()}\nReload the page to apply.`);
    });

    GM_registerMenuCommand("Time Travel: Reset", () => {
        CONFIG.timeOffset = 0;
        GM_setValue('debug_time_offset', 0);
        alert(`Time Travel Reset. Back to reality.`);
    });

    GM_registerMenuCommand("Reset Guild XP History", () => {
        if (confirm("Are you sure you want to clear all stored Guild XP history? This cannot be undone.")) {
            GM_setValue('guild_xp_history', []);
            alert("Guild XP history has been reset.");
        }
    });

    // --- Initialization ---

    function init() {
        // Initial check
        transformGuildModal();

        // Global observer for modal re-creation
        const bodyObserver = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        if (node.id === 'myGuildModal' || node.querySelector('#myGuildModal')) {
                            console.log('[Guild Modal] Modal detected, re-initializing...');
                            transformGuildModal();
                        }
                    }
                }
            }
        });

        bodyObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    console.log('[Guild Modal] v2.0 - Loaded with coordinate display');

})();