GeoPixelcons++

Unified GeoPixels enhancement suite - by Pixelcons

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         GeoPixelcons++
// @namespace    http://tampermonkey.net/
// @version      1.3.25
// @description  Unified GeoPixels enhancement suite - by Pixelcons
// @author       ariapokoteng, Manako, D.V.H.
// @match        *://geopixels.net/*
// @match        *://*.geopixels.net/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @connect      *
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geopixels.net
// ==/UserScript==


(function () {
    'use strict';

    const VERSION = '1.3.25';

    // ============================================================
    //  SETTINGS SYSTEM
    // ============================================================
    const STORAGE_KEY = 'geopixelcons_settings';
    const FEATURE_LIST = [
        { key: 'bulkPurchaseColors', name: 'Bulk Purchase Colors', icon: '🛒', desc: 'Advanced color purchasing with queue management.', features: ['Bulk color purchase with preview modal', 'Queue management in profile panel', 'Duplicate detection & insufficient-pixels handling', 'Purchase progress tracking'] },
        { key: 'ghostPaletteSearch', name: 'Ghost Palette Color Search', icon: '🔍', desc: 'Adds a searchable color filter to the ghost image palette.', features: ['Search ghost palette colors by hex code', 'Hide unmatched colors with a toggle', 'Enable filtered: enable matched colors and disable all others in the ghost palette', 'Real-time glow/highlight on matching swatches'] },
        { key: 'ghostTemplateManager', name: 'Ghost Template Manager', icon: '👻', desc: 'Full ghost image template history with import/export and overlay preview.', features: ['IndexedDB-backed template history', 'Import/export ghost templates as files', 'Preview overlay on the map', 'Position encoding in image header', 'Duplicate detection'] },
        { key: 'guildOverhaul', name: 'Guild Overhaul', icon: '⚔️', desc: 'Comprehensive guild interface improvements.', features: ['Enhanced member management UI', 'Bank/treasury system', 'Color limit tracking', 'Role hierarchy display', 'Guild-specific moderation tools'] },
        { key: 'hidePaintMenu', name: 'Paint Menu Controls', icon: '🫣', desc: 'Adds a collapse/expand toggle for the bottom controls panel.', features: ['Collapse & expand the bottom paint controls', 'Reposition controls (left/center/right)', 'Smooth CSS animations'] },
        { key: 'paintBrushSwap', name: 'Paint Brush Swap', icon: '🖌️', desc: 'Rapid paintbrush tool switching with keyboard shortcuts.', features: ['Configurable keyboard shortcuts for brush swap', 'Brush preset profiles for different painting patterns', 'Quick-switch between brush types'] },
        { key: 'regionScreenshot', name: 'Region Screenshot', icon: '📸', desc: 'Capture region-level screenshots with coordinate overlays.', features: ['Region image capture with coordinate overlay', 'Alpha channel support', 'Save as PNG directly'] },
        { key: 'regionsHighscore', name: 'Regions Highscore', icon: '🏆', desc: 'Displays regional pixel/color contribution rankings.', features: ['Sort rankings by player or guild', 'Filter by pixel count, color, or region', 'Historical contribution statistics'] },
        { key: 'themeEditor', name: 'Theme Editor', icon: '🎨', desc: 'Visual map theme editor — edit MapLibre GL styles with color pickers, save/load/manage custom themes.', features: ['Bundled themes (Fjord, Obsidian, Monokai, Ayu Mirage, etc.)', 'Simple & Full color editing modes', 'Live preview toggle for instant feedback', 'Import/export themes as JSON files', 'Quick theme-switch submenu in the dropdown', 'Theme manager with create, edit & delete'] },
        { key: 'mapMarkers', name: 'Map Markers', icon: '📌', desc: 'Place and manage image stickers on the map canvas. Images scale and persist with the map.', features: ['Upload PNG/JPEG/WebP files or use image URLs', 'Drag to define placement bounds (click-only rejected with prompt)', 'Hold Shift during drag to force aspect-ratio lock', 'Per-marker lock/unlock aspect ratio toggle', 'Per-marker opacity slider and visibility toggle', 'Edit mode with 8 fixed-size handles (corners + edge midpoints)', 'Drag-to-sort cards to reorder rendering order', 'Compact card view with click-to-expand controls', 'Draggable management modal', 'Persistent storage via IndexedDB'] },
    ];

    const EXTENSION_LIST = [
        { key: 'extAutoHoverMenus', name: 'Auto-open Menus on Hover', icon: '🖱️', desc: 'Automatically opens group button dropdown menus when you hover over them.', features: ['Hover over any group button to auto-open its dropdown', 'Configurable vertical hover zone (250px)', 'Per-button cooldown to prevent rapid toggles', 'MutationObserver-based — detects new buttons automatically'] },
        { key: 'extGoToLastLocation', name: 'Auto-Go to Last Location', icon: '📍', desc: 'Automatically returns you to your last location on page load if you spawned at the default area.', features: ['Detects if you spawned in the default area', 'Auto-clicks the "Last Location" button on load', 'One-shot — only fires once per page load', 'Automatic cleanup after 10 seconds to prevent leaks'] },
        { key: 'extPillHoverLabels', name: 'Hover Labels', icon: '💊', desc: 'Adds the expanding pill-style hover animation with text labels to all submenu buttons under controls-left.', features: ['Expanding pill animation on hover', 'Shows button title/name as a label', 'Applies to all native dropdown submenu buttons', 'Respects dark mode colors', 'MutationObserver-based — detects dynamically added buttons'] },
        { key: 'extJanitorView', name: 'Janitor View', icon: '🛡️', desc: 'Reveals the hidden moderation button for janitors/moderators.', features: ['Removes the hidden class from the moderation group button', 'Makes the 🛡️ Moderation button visible in the controls'] },
    ];

    const DEFAULT_SETTINGS = { useEmojiIcon: false, compactPaintOverflow: false, disableGroupNoise: false, startShiftLock: false, startInspectMode: false, smoothZoomButtons: false, enableDebug: false, modernizeGhostPaletteBtns: false, showSyncGhostBtn: false };
    FEATURE_LIST.forEach(f => DEFAULT_SETTINGS[f.key] = true);
    EXTENSION_LIST.forEach(f => DEFAULT_SETTINGS[f.key] = f.key === 'extPillHoverLabels' ? true : false);

    function loadSettings() {
        try {
            const raw = localStorage.getItem(STORAGE_KEY);
            if (!raw) return { ...DEFAULT_SETTINGS };
            const parsed = JSON.parse(raw);
            // Merge with defaults so new features default to enabled
            return { ...DEFAULT_SETTINGS, ...parsed };
        } catch (e) {
            return { ...DEFAULT_SETTINGS };
        }
    }

    function saveSettings(settings) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
    }

    const _settings = loadSettings();

    // ============================================================
    //  DEBUG SYSTEM
    // ============================================================
    const _debugLog = [];

    /**
     * Push an entry into the debug log. Only records when enableDebug is true.
     * @param {string} message  Human-readable description of the event.
     * @param {object} [opts]
     * @param {Error|any} [opts.error]       The caught error object (message + stack captured).
     * @param {string}    [opts.uiComponent] Description of the UI element that triggered the action.
     */
    function dbgPush(message, opts = {}) {
        if (!_settings.enableDebug) return;
        const entry = { timestamp: new Date().toISOString(), message };
        if (opts.uiComponent) entry.uiComponent = opts.uiComponent;
        if (opts.error != null) {
            entry.error = String(opts.error);
            if (opts.error && opts.error.stack) entry.stack = opts.error.stack;
        }
        _debugLog.push(entry);
    }

    /** Returns the number of currently collected debug entries. */
    function dbgCount() { return _debugLog.length; }

    /** Downloads the current debug log as a .txt file. */
    function dbgExport() {
        const lines = _debugLog.map(e => {
            let s = `[${e.timestamp}] ${e.message}`;
            if (e.uiComponent) s += `\n  UI Component : ${e.uiComponent}`;
            if (e.error)       s += `\n  Error        : ${e.error}`;
            if (e.stack)       s += `\n  Stack        :\n    ${e.stack.replace(/\n/g, '\n    ')}`;
            return s;
        });
        const sep = '\n' + '\u2500'.repeat(60) + '\n';
        const header = `GeoPixelcons++ Debug Log\nVersion : ${VERSION}\nGenerated: ${new Date().toISOString()}\nEntries  : ${_debugLog.length}\n${'='.repeat(60)}\n\n`;
        const blob = new Blob([header + lines.join(sep)], { type: 'text/plain' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = `gpc-debug-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
        document.body.appendChild(a);
        a.click();
        setTimeout(() => { try { a.remove(); URL.revokeObjectURL(a.href); } catch (_) {} }, 1000);
    }

    let _themeEditor = null; // Populated by theme editor module
    let _regionScreenshot = null; // Populated by region screenshot module
    let _regionsHighscore = null; // Populated by regions highscore module
    let _mapMarkers = null; // Populated by map markers module

    // ─── Shared coord cache for screenshot/highscore flyouts ────────
    const COORD_CACHE_KEY = 'gpc_cachedCoords';
    const AUTO_SS_KEY = 'gpc_autoScreenshotEnabled';
    function loadCachedCoords() { try { return JSON.parse(localStorage.getItem(COORD_CACHE_KEY)); } catch { return null; } }
    function saveCachedCoords(c) { localStorage.setItem(COORD_CACHE_KEY, JSON.stringify(c)); }
    function isAutoScreenshotEnabled() { return localStorage.getItem(AUTO_SS_KEY) === '1'; }
    function setAutoScreenshot(on) { localStorage.setItem(AUTO_SS_KEY, on ? '1' : '0'); }

    const _featureStatus = {}; // key => 'ok' | 'error' | 'disabled'
    FEATURE_LIST.forEach(f => {
        _featureStatus[f.key] = _settings[f.key] ? 'pending' : 'disabled';
    });
    EXTENSION_LIST.forEach(f => {
        _featureStatus[f.key] = _settings[f.key] ? 'pending' : 'disabled';
    });

    // ============================================================
    //  DARK THEME DETECTION (Geopixels++ compatibility)
    // ============================================================
    function isDarkMode() {
        const gppSettings = localStorage.getItem('geo++_settings');
        if (gppSettings) {
            try {
                const parsed = JSON.parse(gppSettings);
                if (parsed.theme && parsed.theme !== 'system') {
                    return parsed.theme === 'simple_black';
                }
            } catch(e) {}
        }
        return document.body.classList.contains('dark') ||
               window.matchMedia('(prefers-color-scheme: dark)').matches;
    }

    // Theme-aware colors
    function t(light, dark) { return isDarkMode() ? dark : light; }

    // ============================================================
    //  UI: SETTINGS MODAL (Tabbed)
    // ============================================================
    function createSettingsModal() {
        // Remove existing
        const existing = document.getElementById('gpc-settings-modal');
        if (existing) { existing.remove(); return; }

        const dark = isDarkMode();
        const overlay = document.createElement('div');
        overlay.id = 'gpc-settings-modal';
        overlay.style.cssText = `
            position: fixed; inset: 0; z-index: 100000;
            background: rgba(0,0,0,0.5); display: flex;
            align-items: center; justify-content: center;
            font-family: system-ui, -apple-system, sans-serif;
        `;

        const modal = document.createElement('div');
        modal.style.cssText = `
            background: ${dark ? '#1e1e2e' : '#ffffff'};
            color: ${dark ? '#cdd6f4' : '#1e293b'};
            border-radius: 12px; padding: 0; width: 460px; max-width: 95vw;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        `;

        // Header
        const header = document.createElement('div');
        header.style.cssText = `
            padding: 16px 20px; display: flex; align-items: center;
            justify-content: space-between;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border-bottom: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
        `;
        header.innerHTML = `<span style="font-weight:700;font-size:16px;">⚙️ GeoPixelcons++</span>`;

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '✕';
        closeBtn.style.cssText = `
            background:none; border:none; font-size:18px; cursor:pointer;
            color:${dark ? '#a6adc8' : '#64748b'}; padding:4px 8px; border-radius:4px;
        `;
        closeBtn.onmouseenter = () => closeBtn.style.background = dark ? '#45475a' : '#e2e8f0';
        closeBtn.onmouseleave = () => closeBtn.style.background = 'none';
        closeBtn.onclick = () => overlay.remove();
        header.appendChild(closeBtn);
        modal.appendChild(header);

        // Tab bar
        const tabBar = document.createElement('div');
        tabBar.style.cssText = `
            display: flex; background: ${dark ? '#1e1e2e' : '#ffffff'};
            border-bottom: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
        `;
        const tabs = ['Extensions', 'GeoPixelcons++ Settings'];
        const tabBtns = [];
        const tabPanels = [];

        tabs.forEach((tabName, i) => {
            const btn = document.createElement('button');
            btn.textContent = tabName;
            btn.style.cssText = `
                flex: 1; padding: 10px 16px; font-size: 13px; font-weight: 600;
                border: none; cursor: pointer; transition: 0.2s;
                background: ${i === 0 ? (dark ? '#1e1e2e' : '#ffffff') : (dark ? '#313244' : '#f1f5f9')};
                color: ${i === 0 ? (dark ? '#cdd6f4' : '#1e293b') : (dark ? '#6c7086' : '#94a3b8')};
                border-bottom: 2px solid ${i === 0 ? '#22c55e' : 'transparent'};
            `;
            btn.addEventListener('click', () => switchTab(i));
            tabBtns.push(btn);
            tabBar.appendChild(btn);
        });
        modal.appendChild(tabBar);

        function switchTab(idx) {
            tabBtns.forEach((b, i) => {
                const active = i === idx;
                b.style.background = active ? (dark ? '#1e1e2e' : '#ffffff') : (dark ? '#313244' : '#f1f5f9');
                b.style.color = active ? (dark ? '#cdd6f4' : '#1e293b') : (dark ? '#6c7086' : '#94a3b8');
                b.style.borderBottom = active ? '2px solid #22c55e' : '2px solid transparent';
            });
            tabPanels.forEach((p, i) => {
                p.style.display = i === idx ? 'block' : 'none';
            });
        }

        // Warning banner (hidden by default)
        const banner = document.createElement('div');
        banner.id = 'gpc-restart-banner';
        banner.style.cssText = `
            display: none; padding: 10px 20px;
            background: ${dark ? '#f9e2af33' : '#fef3c7'};
            color: ${dark ? '#f9e2af' : '#92400e'};
            font-size: 13px; font-weight: 600;
            border-bottom: 1px solid ${dark ? '#f9e2af44' : '#fde68a'};
        `;
        banner.textContent = '⚠️ Refresh the page to apply changes';
        modal.appendChild(banner);

        // ---- Floating tooltip helper ----
        let activeTooltip = null;
        function removeTooltip() {
            if (activeTooltip) { activeTooltip.remove(); activeTooltip = null; }
        }

        function showTooltip(e, feature) {
            removeTooltip();
            const tip = document.createElement('div');
            tip.style.cssText = `
                position: fixed; z-index: 100001; padding: 12px 16px; border-radius: 8px;
                background: ${dark ? '#313244' : '#ffffff'}; color: ${dark ? '#cdd6f4' : '#1e293b'};
                box-shadow: 0 8px 24px rgba(0,0,0,0.25); font-size: 13px; max-width: 280px;
                border: 1px solid ${dark ? '#45475a' : '#e2e8f0'}; pointer-events: none;
            `;
            let html = `<div style="font-weight:700;margin-bottom:6px;">${feature.icon} ${feature.name}</div>`;
            html += `<div style="margin-bottom:6px;color:${dark ? '#a6adc8' : '#64748b'};">${feature.desc}</div>`;
            html += '<ul style="margin:0;padding-left:18px;">';
            feature.features.forEach(f => { html += `<li style="margin-bottom:2px;">${f}</li>`; });
            html += '</ul>';
            tip.innerHTML = html;
            document.body.appendChild(tip);
            // Position near cursor
            const tipW = tip.offsetWidth, tipH = tip.offsetHeight;
            let tx = e.clientX + 12, ty = e.clientY + 12;
            if (tx + tipW > window.innerWidth - 8) tx = e.clientX - tipW - 12;
            if (ty + tipH > window.innerHeight - 8) ty = e.clientY - tipH - 12;
            tip.style.left = tx + 'px';
            tip.style.top = ty + 'px';
            activeTooltip = tip;
        }

        function showSimpleTooltip(e, text) {
            removeTooltip();
            const tip = document.createElement('div');
            tip.style.cssText = `
                position: fixed; z-index: 100001; padding: 10px 14px; border-radius: 8px;
                background: ${dark ? '#313244' : '#ffffff'}; color: ${dark ? '#cdd6f4' : '#1e293b'};
                box-shadow: 0 8px 24px rgba(0,0,0,0.25); font-size: 13px; max-width: 260px;
                border: 1px solid ${dark ? '#45475a' : '#e2e8f0'}; pointer-events: none; line-height: 1.5;
            `;
            tip.textContent = text;
            document.body.appendChild(tip);
            const tipW = tip.offsetWidth, tipH = tip.offsetHeight;
            let tx = e.clientX + 12, ty = e.clientY + 12;
            if (tx + tipW > window.innerWidth - 8) tx = e.clientX - tipW - 12;
            if (ty + tipH > window.innerHeight - 8) ty = e.clientY - tipH - 12;
            tip.style.left = tx + 'px';
            tip.style.top = ty + 'px';
            activeTooltip = tip;
        }

        // ---- Navigate to a feature's UI element ----
        function navigateToFeature(key) {
            function flashEl(el) {
                if (!el) return;
                el.scrollIntoView?.({ behavior: 'smooth', block: 'center' });
                el.style.transition = 'box-shadow .3s';
                el.style.boxShadow = '0 0 0 3px #facc15, 0 0 16px 4px rgba(250,204,21,.5)';
                setTimeout(() => { el.style.boxShadow = ''; setTimeout(() => { el.style.transition = ''; }, 300); }, 1500);
            }
            function flashAll(els) { els.forEach(el => flashEl(el)); }
            const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
            const nav = {
                ghostPaletteSearch: () => {
                    // Open ghost modal, then flash the color search container
                    if (typeof _pw.toggleGhostModal === 'function') _pw.toggleGhostModal(true);
                    setTimeout(() => flashEl(document.querySelector('.color-search-container')), 400);
                },
                ghostTemplateManager: () => {
                    // Open ghost modal, then flash the toolbar buttons
                    if (typeof _pw.toggleGhostModal === 'function') _pw.toggleGhostModal(true);
                    setTimeout(() => flashAll(Array.from(document.querySelectorAll('.gp-to-btn'))), 400);
                },
                guildOverhaul: () => {
                    const guildBtn = document.querySelector('#guildMenuBtn');
                    if (guildBtn) guildBtn.click();
                },
                hidePaintMenu: () => {
                    flashEl(document.querySelector('#gpc-hide-paint-toggle'));
                },
                paintBrushSwap: () => {
                    flashEl(document.querySelector('#brush-swap-toggle'));
                },
                regionsHighscore: () => {
                    if (_regionsHighscore) _regionsHighscore.toggleSelectionMode();
                },
                regionScreenshot: () => {
                    if (_regionScreenshot) _regionScreenshot.toggleSelectionMode();
                },
                bulkPurchaseColors: () => {
                    if (typeof _pw.toggleProfile === 'function') _pw.toggleProfile();
                    setTimeout(() => flashEl(document.querySelector('#gp-bulk-profile-card')), 400);
                },
                themeEditor: () => {
                    if (_themeEditor) _themeEditor.toggleModal();
                },
                extAutoHoverMenus: () => {
                    // No specific UI to navigate to
                },
                extGoToLastLocation: () => {
                    // No specific UI to navigate to
                },
                extPillHoverLabels: () => {
                    // No specific UI to navigate to
                },
                extJanitorView: () => {
                    const modBtn = document.getElementById('modGroupBtn');
                    if (modBtn) flashEl(modBtn);
                },
            };
            const fn = nav[key];
            if (fn) fn();
        }

        // ---- Helper: build a toggle row ----
        function buildToggleRow(f, showHelp) {
            const status = _featureStatus[f.key];
            const enabled = _settings[f.key] !== false;

            const row = document.createElement('div');
            row.style.cssText = `
                display: flex; align-items: center; justify-content: space-between;
                padding: 10px 14px; border-radius: 8px;
                background: ${enabled
                    ? (status === 'error' ? (dark ? '#f9e2af22' : '#fefce8') : (dark ? '#a6e3a122' : '#f0fdf4'))
                    : (dark ? '#f38ba822' : '#fef2f2')};
                border: 1px solid ${enabled
                    ? (status === 'error' ? (dark ? '#f9e2af44' : '#fde68a') : (dark ? '#a6e3a144' : '#bbf7d0'))
                    : (dark ? '#f38ba844' : '#fecaca')};
                transition: all 0.2s;
            `;

            const labelWrap = document.createElement('div');
            labelWrap.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;min-width:0;';

            const iconSpan = document.createElement('span');
            iconSpan.textContent = f.icon;
            const nameSpan = document.createElement('span');
            nameSpan.textContent = f.name;
            nameSpan.style.cssText = 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;border-bottom:1px dashed ' + (dark ? '#6c7086' : '#94a3b8') + ';transition:color .15s,border-color .15s;';
            nameSpan.addEventListener('mouseenter', () => { nameSpan.style.color = '#3b82f6'; nameSpan.style.borderBottomColor = '#3b82f6'; });
            nameSpan.addEventListener('mouseleave', () => { nameSpan.style.color = ''; nameSpan.style.borderBottomColor = dark ? '#6c7086' : '#94a3b8'; });
            nameSpan.addEventListener('click', () => {
                overlay.remove();
                navigateToFeature(f.key);
            });
            labelWrap.appendChild(iconSpan);
            labelWrap.appendChild(nameSpan);

            if (showHelp && f.desc) {
                const helpBtn = document.createElement('span');
                helpBtn.textContent = '❓';
                helpBtn.style.cssText = 'cursor:help;font-size:14px;flex-shrink:0;margin-left:2px;';
                helpBtn.addEventListener('mouseenter', (ev) => showTooltip(ev, f));
                helpBtn.addEventListener('mouseleave', removeTooltip);
                labelWrap.appendChild(helpBtn);
            }

            // Toggle switch
            const toggle = document.createElement('label');
            toggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0; margin-left:8px;';
            const input = document.createElement('input');
            input.type = 'checkbox';
            input.checked = enabled;
            input.style.cssText = 'opacity:0;width:0;height:0;';

            const slider = document.createElement('span');
            slider.style.cssText = `
                position:absolute; inset:0; border-radius:12px; transition:0.2s;
                background: ${enabled ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
            `;
            const knob = document.createElement('span');
            knob.style.cssText = `
                position:absolute; top:2px; left:${enabled ? '22px' : '2px'};
                width:20px; height:20px; border-radius:50%; transition:0.2s;
                background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
            `;
            slider.appendChild(knob);

            input.addEventListener('change', () => {
                _settings[f.key] = input.checked;
                saveSettings(_settings);
                slider.style.background = input.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
                knob.style.left = input.checked ? '22px' : '2px';
                row.style.background = input.checked
                    ? (dark ? '#a6e3a122' : '#f0fdf4')
                    : (dark ? '#f38ba822' : '#fef2f2');
                row.style.borderColor = input.checked
                    ? (dark ? '#a6e3a144' : '#bbf7d0')
                    : (dark ? '#f38ba844' : '#fecaca');
                banner.style.display = 'block';
            });

            toggle.appendChild(input);
            toggle.appendChild(slider);

            row.appendChild(labelWrap);
            row.appendChild(toggle);
            return row;
        }

        // ============ TAB 1: Extensions ============
        const extPanel = document.createElement('div');
        extPanel.style.cssText = 'padding: 12px 20px; display: flex; flex-direction: column; gap: 8px; max-height: 50vh; overflow-y: auto;';

        // Section: Built-in features
        const builtinLabel = document.createElement('div');
        builtinLabel.style.cssText = `font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;color:${dark ? '#6c7086' : '#94a3b8'};margin-bottom:2px;`;
        builtinLabel.textContent = 'Built-in Features';
        extPanel.appendChild(builtinLabel);

        FEATURE_LIST.forEach(f => extPanel.appendChild(buildToggleRow(f, true)));

        // Section: Additional extensions
        const extLabel = document.createElement('div');
        extLabel.style.cssText = `font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;color:${dark ? '#6c7086' : '#94a3b8'};margin-top:8px;margin-bottom:2px;`;
        extLabel.textContent = 'Additional Extensions';
        extPanel.appendChild(extLabel);

        EXTENSION_LIST.forEach(f => extPanel.appendChild(buildToggleRow(f, true)));

        // Sync Ghost With Selected Color (display option)
        const _sgEnabled = !!_settings.showSyncGhostBtn;
        const syncGhostExtRow = document.createElement('div');
        syncGhostExtRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${_sgEnabled ? (dark ? '#a6e3a122' : '#f0fdf4') : (dark ? '#f38ba822' : '#fef2f2')};
            border: 1px solid ${_sgEnabled ? (dark ? '#a6e3a144' : '#bbf7d0') : (dark ? '#f38ba844' : '#fecaca')};
            transition: all 0.2s;
        `;
        const syncGhostExtLabel = document.createElement('div');
        syncGhostExtLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;min-width:0;';
        const syncGhostExtIcon = document.createElement('span');
        syncGhostExtIcon.textContent = '\u267B\uFE0F';
        const syncGhostExtName = document.createElement('span');
        syncGhostExtName.textContent = 'Sync Ghost With Selected Color';
        const syncGhostExtHelp = document.createElement('span');
        syncGhostExtHelp.textContent = '\u2753';
        syncGhostExtHelp.style.cssText = 'cursor:help;font-size:14px;flex-shrink:0;margin-left:2px;';
        syncGhostExtHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'Shows the \u267b\ufe0f Sync Ghost With Selected Color button in the Image Tools (\ud83d\uddbc\ufe0f) dropdown. When toggled on in-game, changing your active paint color automatically enables only that color in the ghost palette and disables all others.'));
        syncGhostExtHelp.addEventListener('mouseleave', removeTooltip);
        syncGhostExtLabel.appendChild(syncGhostExtIcon);
        syncGhostExtLabel.appendChild(syncGhostExtName);
        syncGhostExtLabel.appendChild(syncGhostExtHelp);
        const syncGhostExtToggle = document.createElement('label');
        syncGhostExtToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0; margin-left:8px;';
        const syncGhostExtInput = document.createElement('input');
        syncGhostExtInput.type = 'checkbox';
        syncGhostExtInput.checked = _sgEnabled;
        syncGhostExtInput.style.cssText = 'opacity:0;width:0;height:0;';
        const syncGhostExtSlider = document.createElement('span');
        syncGhostExtSlider.style.cssText = `position:absolute; inset:0; border-radius:12px; transition:0.2s; background: ${_sgEnabled ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};`;
        const syncGhostExtKnob = document.createElement('span');
        syncGhostExtKnob.style.cssText = `position:absolute; top:2px; left:${_sgEnabled ? '22px' : '2px'}; width:20px; height:20px; border-radius:50%; transition:0.2s; background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);`;
        syncGhostExtSlider.appendChild(syncGhostExtKnob);
        syncGhostExtInput.addEventListener('change', () => {
            _settings.showSyncGhostBtn = syncGhostExtInput.checked;
            saveSettings(_settings);
            syncGhostExtSlider.style.background = syncGhostExtInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            syncGhostExtKnob.style.left = syncGhostExtInput.checked ? '22px' : '2px';
            syncGhostExtRow.style.background = syncGhostExtInput.checked ? (dark ? '#a6e3a122' : '#f0fdf4') : (dark ? '#f38ba822' : '#fef2f2');
            syncGhostExtRow.style.borderColor = syncGhostExtInput.checked ? (dark ? '#a6e3a144' : '#bbf7d0') : (dark ? '#f38ba844' : '#fecaca');
            const syncBtn = document.getElementById('gpc-sync-ghost-btn');
            if (syncBtn) syncBtn.classList.toggle('hidden', !syncGhostExtInput.checked);
        });
        syncGhostExtToggle.appendChild(syncGhostExtInput);
        syncGhostExtToggle.appendChild(syncGhostExtSlider);
        syncGhostExtRow.appendChild(syncGhostExtLabel);
        syncGhostExtRow.appendChild(syncGhostExtToggle);
        extPanel.appendChild(syncGhostExtRow);

        tabPanels.push(extPanel);
        modal.appendChild(extPanel);

        // ============ TAB 2: GeoPixelcons++ Settings ============
        const settingsPanel = document.createElement('div');
        settingsPanel.style.cssText = 'padding: 12px 20px; display: none; flex-direction: column; gap: 12px;';

        // Emoji icon toggle
        const emojiRow = document.createElement('div');
        emojiRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
        `;
        const emojiLabel = document.createElement('div');
        emojiLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        emojiLabel.innerHTML = '<span>😢</span><span>Use emoji for menu button</span>';
        const emojiHelp = document.createElement('span');
        emojiHelp.textContent = '❓';
        emojiHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        emojiHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'When enabled, replaces the GeoPixelcons++ button icon with the 😢 emoji.'));
        emojiHelp.addEventListener('mouseleave', removeTooltip);
        emojiLabel.appendChild(emojiHelp);

        const emojiToggle = document.createElement('label');
        emojiToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const emojiInput = document.createElement('input');
        emojiInput.type = 'checkbox';
        emojiInput.checked = !!_settings.useEmojiIcon;
        emojiInput.style.cssText = 'opacity:0;width:0;height:0;';
        const emojiSlider = document.createElement('span');
        emojiSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.useEmojiIcon ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const emojiKnob = document.createElement('span');
        emojiKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.useEmojiIcon ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        emojiSlider.appendChild(emojiKnob);

        emojiInput.addEventListener('change', () => {
            _settings.useEmojiIcon = emojiInput.checked;
            saveSettings(_settings);
            emojiSlider.style.background = emojiInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            emojiKnob.style.left = emojiInput.checked ? '22px' : '2px';
            // Live-update the button
            const mainBtn = document.getElementById('geopixelconsGroupBtn');
            if (mainBtn) {
                if (emojiInput.checked) {
                    mainBtn.style.backgroundImage = 'none';
                    mainBtn.textContent = '😢';
                    mainBtn.style.fontSize = '20px';
                } else {
                    mainBtn.textContent = '';
                    mainBtn.style.fontSize = '';
                    mainBtn.style.backgroundImage = mainBtn.dataset.iconBg || '';
                }
            }
        });

        emojiToggle.appendChild(emojiInput);
        emojiToggle.appendChild(emojiSlider);
        emojiRow.appendChild(emojiLabel);
        emojiRow.appendChild(emojiToggle);
        settingsPanel.appendChild(emojiRow);

        // Compact paint overflow toggle
        const compactRow = document.createElement('div');
        compactRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            margin-top: 4px;
        `;
        const compactLabel = document.createElement('div');
        compactLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        compactLabel.innerHTML = '<span>🖌️</span><span>Compact paint controls</span>';
        const compactHelp = document.createElement('span');
        compactHelp.textContent = '❓';
        compactHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        compactHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'Moves the ✕ close button and 🖌️ brushes button from inside the paint modal to compact icons above it, alongside the collapse/drag controls. Helps on small or zoomed-in screens.'));
        compactHelp.addEventListener('mouseleave', removeTooltip);
        compactLabel.appendChild(compactHelp);

        const compactToggle = document.createElement('label');
        compactToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const compactInput = document.createElement('input');
        compactInput.type = 'checkbox';
        compactInput.checked = !!_settings.compactPaintOverflow;
        compactInput.style.cssText = 'opacity:0;width:0;height:0;';
        const compactSlider = document.createElement('span');
        compactSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.compactPaintOverflow ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const compactKnob = document.createElement('span');
        compactKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.compactPaintOverflow ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        compactSlider.appendChild(compactKnob);

        compactInput.addEventListener('change', () => {
            _settings.compactPaintOverflow = compactInput.checked;
            saveSettings(_settings);
            compactSlider.style.background = compactInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            compactKnob.style.left = compactInput.checked ? '22px' : '2px';
            banner.style.display = 'block';
        });

        compactToggle.appendChild(compactInput);
        compactToggle.appendChild(compactSlider);
        compactRow.appendChild(compactLabel);
        compactRow.appendChild(compactToggle);
        settingsPanel.appendChild(compactRow);

        // Disable Group Noise toggle
        const noiseRow = document.createElement('div');
        noiseRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            margin-top: 4px;
        `;
        const noiseLabel = document.createElement('div');
        noiseLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        noiseLabel.innerHTML = '<span>🚫</span><span>Disable Group Noise</span>';
        const noiseHelp = document.createElement('span');
        noiseHelp.textContent = '❓';
        noiseHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        noiseHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'Permanently unchecks and hides the "Group Noise" toggle in the ghost template modal. Prevents automatic color grouping so every unique color is treated individually.'));
        noiseHelp.addEventListener('mouseleave', removeTooltip);
        noiseLabel.appendChild(noiseHelp);

        const noiseToggle = document.createElement('label');
        noiseToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const noiseInput = document.createElement('input');
        noiseInput.type = 'checkbox';
        noiseInput.checked = !!_settings.disableGroupNoise;
        noiseInput.style.cssText = 'opacity:0;width:0;height:0;';
        const noiseSlider = document.createElement('span');
        noiseSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.disableGroupNoise ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const noiseKnob = document.createElement('span');
        noiseKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.disableGroupNoise ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        noiseSlider.appendChild(noiseKnob);

        noiseInput.addEventListener('change', () => {
            _settings.disableGroupNoise = noiseInput.checked;
            saveSettings(_settings);
            noiseSlider.style.background = noiseInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            noiseKnob.style.left = noiseInput.checked ? '22px' : '2px';
            banner.style.display = 'block';
        });

        noiseToggle.appendChild(noiseInput);
        noiseToggle.appendChild(noiseSlider);
        noiseRow.appendChild(noiseLabel);
        noiseRow.appendChild(noiseToggle);
        settingsPanel.appendChild(noiseRow);

        // Start in Shift Lock toggle
        const shiftRow = document.createElement('div');
        shiftRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            margin-top: 4px;
        `;
        const shiftLabel = document.createElement('div');
        shiftLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        shiftLabel.innerHTML = '<span>\uD83D\uDD12</span><span>Start in Shift Lock</span>';
        const shiftHelp = document.createElement('span');
        shiftHelp.textContent = '❓';
        shiftHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        shiftHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'Automatically enables Shift Lock on page load so you can paint without holding Shift.'));
        shiftHelp.addEventListener('mouseleave', removeTooltip);
        shiftLabel.appendChild(shiftHelp);

        const shiftToggle = document.createElement('label');
        shiftToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const shiftInput = document.createElement('input');
        shiftInput.type = 'checkbox';
        shiftInput.checked = !!_settings.startShiftLock;
        shiftInput.style.cssText = 'opacity:0;width:0;height:0;';
        const shiftSlider = document.createElement('span');
        shiftSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.startShiftLock ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const shiftKnob = document.createElement('span');
        shiftKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.startShiftLock ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        shiftSlider.appendChild(shiftKnob);

        shiftInput.addEventListener('change', () => {
            _settings.startShiftLock = shiftInput.checked;
            saveSettings(_settings);
            shiftSlider.style.background = shiftInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            shiftKnob.style.left = shiftInput.checked ? '22px' : '2px';
        });

        shiftToggle.appendChild(shiftInput);
        shiftToggle.appendChild(shiftSlider);
        shiftRow.appendChild(shiftLabel);
        shiftRow.appendChild(shiftToggle);
        settingsPanel.appendChild(shiftRow);

        // Start in Inspect Mode toggle
        const inspectRow = document.createElement('div');
        inspectRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            margin-top: 4px;
        `;
        const inspectLabel = document.createElement('div');
        inspectLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        inspectLabel.innerHTML = '<span>\uD83D\uDD0D</span><span>Start in Inspect Mode</span>';
        const inspectHelp = document.createElement('span');
        inspectHelp.textContent = '❓';
        inspectHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        inspectHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'Automatically switches to Inspect Mode on page load instead of starting in Paint (Action) mode.'));
        inspectHelp.addEventListener('mouseleave', removeTooltip);
        inspectLabel.appendChild(inspectHelp);

        const inspectToggle = document.createElement('label');
        inspectToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const inspectInput = document.createElement('input');
        inspectInput.type = 'checkbox';
        inspectInput.checked = !!_settings.startInspectMode;
        inspectInput.style.cssText = 'opacity:0;width:0;height:0;';
        const inspectSlider = document.createElement('span');
        inspectSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.startInspectMode ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const inspectKnob = document.createElement('span');
        inspectKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.startInspectMode ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        inspectSlider.appendChild(inspectKnob);

        inspectInput.addEventListener('change', () => {
            _settings.startInspectMode = inspectInput.checked;
            saveSettings(_settings);
            inspectSlider.style.background = inspectInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            inspectKnob.style.left = inspectInput.checked ? '22px' : '2px';
        });

        inspectToggle.appendChild(inspectInput);
        inspectToggle.appendChild(inspectSlider);
        inspectRow.appendChild(inspectLabel);
        inspectRow.appendChild(inspectToggle);
        settingsPanel.appendChild(inspectRow);

        // Smooth Zoom Buttons toggle
        const smoothZoomRow = document.createElement('div');
        smoothZoomRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            margin-top: 4px;
        `;
        const smoothZoomLabel = document.createElement('div');
        smoothZoomLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        smoothZoomLabel.innerHTML = '<span>🔢</span><span>Smooth Zoom Buttons</span>';
        const smoothZoomHelp = document.createElement('span');
        smoothZoomHelp.textContent = '❓';
        smoothZoomHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        smoothZoomHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'Replaces the +/\u2212 zoom buttons with a smooth zoom control: vertical slider, exact value input, hold-to-repeat \u00b1 buttons, and scroll-wheel zoom on the widget.'));
        smoothZoomHelp.addEventListener('mouseleave', removeTooltip);
        smoothZoomLabel.appendChild(smoothZoomHelp);

        const smoothZoomToggle = document.createElement('label');
        smoothZoomToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const smoothZoomInput = document.createElement('input');
        smoothZoomInput.type = 'checkbox';
        smoothZoomInput.checked = !!_settings.smoothZoomButtons;
        smoothZoomInput.style.cssText = 'opacity:0;width:0;height:0;';
        const smoothZoomSlider = document.createElement('span');
        smoothZoomSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.smoothZoomButtons ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const smoothZoomKnob = document.createElement('span');
        smoothZoomKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.smoothZoomButtons ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        smoothZoomSlider.appendChild(smoothZoomKnob);

        smoothZoomInput.addEventListener('change', () => {
            _settings.smoothZoomButtons = smoothZoomInput.checked;
            saveSettings(_settings);
            smoothZoomSlider.style.background = smoothZoomInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            smoothZoomKnob.style.left = smoothZoomInput.checked ? '22px' : '2px';
            banner.style.display = 'block';
        });

        smoothZoomToggle.appendChild(smoothZoomInput);
        smoothZoomToggle.appendChild(smoothZoomSlider);
        smoothZoomRow.appendChild(smoothZoomLabel);
        smoothZoomRow.appendChild(smoothZoomToggle);
        settingsPanel.appendChild(smoothZoomRow);

        // Enable Debugging toggle
        const debugRow = document.createElement('div');
        debugRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            margin-top: 4px;
        `;
        const debugLabel = document.createElement('div');
        debugLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        debugLabel.innerHTML = '<span>🔶</span><span>Enable Debugging</span>';
        const debugHelp = document.createElement('span');
        debugHelp.textContent = '❓';
        debugHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        debugHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'When enabled, errors are recorded in memory and a Debug Logs button appears in the dropdown. Click it to export a log file. Refresh the page after toggling.'));
        debugHelp.addEventListener('mouseleave', removeTooltip);
        debugLabel.appendChild(debugHelp);

        const debugToggle = document.createElement('label');
        debugToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const debugInput = document.createElement('input');
        debugInput.type = 'checkbox';
        debugInput.checked = !!_settings.enableDebug;
        debugInput.style.cssText = 'opacity:0;width:0;height:0;';
        const debugSlider = document.createElement('span');
        debugSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.enableDebug ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const debugKnob = document.createElement('span');
        debugKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.enableDebug ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        debugSlider.appendChild(debugKnob);

        debugInput.addEventListener('change', () => {
            _settings.enableDebug = debugInput.checked;
            saveSettings(_settings);
            debugSlider.style.background = debugInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            debugKnob.style.left = debugInput.checked ? '22px' : '2px';
            banner.style.display = 'block';
        });

        debugToggle.appendChild(debugInput);
        debugToggle.appendChild(debugSlider);
        debugRow.appendChild(debugLabel);
        debugRow.appendChild(debugToggle);
        settingsPanel.appendChild(debugRow);

        // Ghost Menu UI Overhaul toggle (Ghost Template Manager)
        const modernBtnsRow = document.createElement('div');
        modernBtnsRow.style.cssText = `
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px 14px; border-radius: 8px;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            margin-top: 4px;
        `;
        const modernBtnsLabel = document.createElement('div');
        modernBtnsLabel.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;';
        modernBtnsLabel.innerHTML = '<span>🎛️</span><span>Ghost Menu UI Overhaul</span>';
        const modernBtnsHelp = document.createElement('span');
        modernBtnsHelp.textContent = '❓';
        modernBtnsHelp.style.cssText = 'cursor:help;font-size:12px;flex-shrink:0;margin-left:4px;opacity:0.6;';
        modernBtnsHelp.addEventListener('mouseenter', (ev) => showSimpleTooltip(ev, 'Requires Ghost Template Manager. Removes the ✚₊ collapse toggle added by geopixels++, shows all its hidden buttons permanently, and applies consistent flat styling to every action button in the ghost palette panel.'));
        modernBtnsHelp.addEventListener('mouseleave', removeTooltip);
        modernBtnsLabel.appendChild(modernBtnsHelp);

        const modernBtnsToggle = document.createElement('label');
        modernBtnsToggle.style.cssText = 'position:relative; width:44px; height:24px; cursor:pointer; flex-shrink:0;';
        const modernBtnsInput = document.createElement('input');
        modernBtnsInput.type = 'checkbox';
        modernBtnsInput.checked = !!_settings.modernizeGhostPaletteBtns;
        modernBtnsInput.style.cssText = 'opacity:0;width:0;height:0;';
        const modernBtnsSlider = document.createElement('span');
        modernBtnsSlider.style.cssText = `
            position:absolute; inset:0; border-radius:12px; transition:0.2s;
            background: ${_settings.modernizeGhostPaletteBtns ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1')};
        `;
        const modernBtnsKnob = document.createElement('span');
        modernBtnsKnob.style.cssText = `
            position:absolute; top:2px; left:${_settings.modernizeGhostPaletteBtns ? '22px' : '2px'};
            width:20px; height:20px; border-radius:50%; transition:0.2s;
            background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        `;
        modernBtnsSlider.appendChild(modernBtnsKnob);

        modernBtnsInput.addEventListener('change', () => {
            _settings.modernizeGhostPaletteBtns = modernBtnsInput.checked;
            saveSettings(_settings);
            modernBtnsSlider.style.background = modernBtnsInput.checked ? '#22c55e' : (dark ? '#585b70' : '#cbd5e1');
            modernBtnsKnob.style.left = modernBtnsInput.checked ? '22px' : '2px';
            banner.style.display = 'block';
        });

        modernBtnsToggle.appendChild(modernBtnsInput);
        modernBtnsToggle.appendChild(modernBtnsSlider);
        modernBtnsRow.appendChild(modernBtnsLabel);
        modernBtnsRow.appendChild(modernBtnsToggle);
        settingsPanel.appendChild(modernBtnsRow);

        tabPanels.push(settingsPanel);
        modal.appendChild(settingsPanel);

        // Footer
        const footer = document.createElement('div');
        footer.style.cssText = `
            padding: 12px 20px;
            background: ${dark ? '#313244' : '#f8fafc'};
            border-top: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            font-size: 11px;
            color: ${dark ? '#6c7086' : '#94a3b8'};
            text-align: center;
        `;
        footer.textContent = 'GeoPixelcons++ v' + VERSION;
        modal.appendChild(footer);

        overlay.appendChild(modal);
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) { removeTooltip(); overlay.remove(); }
        });
        document.body.appendChild(overlay);
    }

    // ============================================================
    //  UI: CHANGELOG MODAL
    // ============================================================
    const CHANGELOG = [
        {
            version: '1.3.25',
            date: '2026-05-18',
            items: [
                { type: 'added', text: 'Ghost Template Manager: added a draggable horizontal divider between the preview image and the action buttons — drag up/down to freely set the preview height, overriding the default aspect-ratio sizing' },
            ]
        },
        {
            version: '1.3.24',
            date: '2026-05-18',
            items: [
                { type: 'fixed', text: 'Ghost Template Manager: preview image container now uses aspect-ratio (4:3) instead of a fixed 210px height, so the preview scales taller as the user widens the right panel with the splitter' },
            ]
        },
        {
            version: '1.3.23',
            date: '2026-05-18',
            items: [
                { type: 'added', text: 'Ghost Template Manager: added a draggable panel splitter on the left edge of the preview panel — drag left to widen the right panel (giving the image preview more space), drag right to shrink it; width is remembered across collapse/expand cycles' },
            ]
        },
        {
            version: '1.3.22',
            date: '2026-05-17',
            items: [
                { type: 'added', text: 'Ghost Palette Color Search: new ♻️ \'Sync Ghost With Selected Color\' toggle button injected into the Image Tools (🖼️) dropdown — when active (green), changing the selected paint color automatically enables only that color in the ghost palette and disables all others; ignored if the color is not in the template' },
                { type: 'added', text: 'Ghost Palette Color Search: Sync Ghost button is hidden by default; enabled via a new ♻️ Sync Ghost With Selected Color row in the Extensions tab (under Additional Extensions), with the same green/red state styling as other extension rows' },
                { type: 'changed', text: 'Settings modal — Extra Settings tab: replaced all nine per-row description divs with compact inline ❓ tooltip icons; hovering shows the description in a floating tooltip, significantly reducing the tab height' },
                { type: 'changed', text: 'Settings modal — Extensions tab: removed the 🟢/🔴/🟡 status dot from all feature and extension toggle rows; enabled/disabled state is already conveyed by the toggle switch color' },
            ]
        },
        {
            version: '1.3.21',
            date: '2026-05-14',
            items: [
                { type: 'fixed', text: 'Start Shift Lock: the Find Art button now works correctly when shift-lock is active — FindRandomArt is patched to temporarily call toggleShiftDown() around each invocation, since shiftDown is a closure variable that cannot be overridden directly' },
            ]
        },
        {
            version: '1.3.20',
            date: '2026-05-13',
            items: [
                { type: 'fixed', text: 'Ghost Template Manager (overhaul): the bottom-right resize handle now correctly adjusts the modal width while the preview panel is collapsed, updating the collapsed width variable so the CSS rule does not block the resize' },
            ]
        },
        {
            version: '1.3.19',
            date: '2026-05-13',
            items: [
                { type: 'fixed', text: 'Ghost Template Manager (overhaul): rebuilt the Collapse preview / Expand preview animation so the modal and right preview panel resize together using a coordinated CSS state, avoiding the left-panel jump/reflow jank during toggles' },
            ]
        },
        {
            version: '1.3.18',
            date: '2026-05-13',
            items: [
                { type: 'fixed', text: 'Ghost Template Manager (overhaul): action buttons (Toggle All, Set Palette, Match My Palette, Bulk Purchase Colors, Enable Only Owned Ghost Colors, Get/Set Ghost Colors, Enable filtered) no longer wrap text — long labels truncate with ellipsis and the full text appears on hover' },
                { type: 'fixed', text: 'Ghost Template Manager (overhaul): clicking "Collapse preview" now shrinks the entire modal instead of expanding the left panel to fill the freed space; the modal width is restored when the right panel is re-expanded' },
            ]
        },
        {
            version: '1.3.17',
            date: '2026-05-13',
            items: [
                { type: 'fixed', text: 'Ghost Template Manager: importing a JSON history export then immediately re-opening history no longer reverses the order — entries are now inserted oldest-first so the newest-first display order is preserved after import' },
                { type: 'fixed', text: 'Ghost Template Manager: loading a template from the history modal (card click or Load & Go To) now moves that entry to the top of history to reflect recency; the recent-image strip (Ghost Menu UI Overhaul) is also refreshed to stay in sync' },
                { type: 'fixed', text: 'Ghost Template Manager: clicking a recent-image thumbnail now promotes that entry to the top of history and immediately refreshes the recent strip so the order stays accurate' },
            ]
        },
        {
            version: '1.3.16',
            date: '2026-05-12',
            items: [
                { type: 'added', text: 'Ghost Template Manager: new "Ghost Menu UI Overhaul" setting (off by default) — when enabled, removes the ✚₊ collapse toggle injected by geopixels++, makes all its hidden buttons permanently visible, and applies a unified flat slate styling to every action button under the ghost palette container' },
                { type: 'changed', text: 'Ghost Palette Color Search: removed the ✅ emoji from the "Enable filtered" button and moved it out of the search filter box into its own row above the search container, closer to the other ghost color enable/disable buttons' },
                { type: 'added', text: 'Ghost Template Manager: full modal layout overhaul (requires "Ghost Menu UI Overhaul") — widens the modal to a two-column layout with a collapsible right panel; image preview, action buttons (URL / File / History / Save Pos / Place on Map / Clear Image), and recent-image history are moved to the right panel; left panel retains scroll content (progress, filter controls, color palette)' },
                { type: 'added', text: 'Ghost Palette Color Search: "Hide completed colors" checkbox — when checked, hides all color swatches whose geopixels++ progress badge shows 100% (detected from the multiline title set by the Refresh % button); works independently of and combines with the existing "Hide unmatched colors" filter' },
                { type: 'added', text: 'Ghost Template Manager: modal is draggable from the header bar and all 4 edges (grab cursor feedback); resizable from the bottom-right SE corner handle only (guild modal pattern); initial size 800×75vh; conflicting Tailwind classes stripped so resize works without !important fights' },
                { type: 'fixed', text: 'Ghost Template Manager: ghostColorPalette max-height (Tailwind max-h-48) overridden so the palette expands freely with its content instead of capping at 192px' },
                { type: 'fixed', text: 'Ghost Template Manager: recent-image grid rows no longer spread to fill the panel height — align-content:start packs them to the top' },
                { type: 'fixed', text: 'Ghost Template Manager: close button was invisible (bg-white on bg-white header); now theme-aware with transparent background and visible hover state; falls back to a freshly created button if the site\'s onclick selector returns null' },
                { type: 'fixed', text: 'Ghost Template Manager: preview image was not showing — container detection was matching the outermost ancestor div instead of the direct parent of #ghostPreviewImage; fixed to use parentElement of the image itself' },
            ]
        },
        {
            version: '1.3.15',
            date: '2026-05-11',
            items: [
                { type: 'changed', text: 'Source refactored into modular build system (src/ + build.js) — no functional changes for end users; versioned output files are unchanged' },
            ]
        },
        {
            version: '1.3.14',
            date: '2026-05-11',
            items: [
                { type: 'added', text: 'Map Markers: re-added GIF support — upload animated GIFs as map markers; rendered as a live DOM overlay (positioned relative to the map container, using CSS-pixel coordinates to fix the zoom-level skew bug) so animation plays correctly while the canvas handles static images as before' },
            ]
        },
        {
            version: '1.3.13',
            date: '2026-05-10',
            items: [
                { type: 'removed', text: 'Map Markers: removed GIF and MP4/video upload support — file picker now accepts PNG/JPEG/WebP only; the Video URL button has been removed; all GIF/video overlay rendering code has been eliminated' },
            ]
        },
        {
            version: '1.3.12',
            date: '2026-05-10',
            items: [
                { type: 'changed', text: 'Map Markers: compact card view is now the default on open' },
                { type: 'fixed', text: 'Map Markers: drag-to-sort handle in full card view no longer blocks the opacity slider — only the ⠇ grip icon initiates a drag; the rest of the card is fully interactive' },
                { type: 'fixed', text: 'Map Markers: uploaded MP4/WebM video markers now render visibly — the data URL is converted to a blob URL before being set as the video src (browsers silently fail to play large data-URL videos); autoplay is deferred until after the element is in the DOM' },
                { type: 'fixed', text: 'Map Markers: GIF overlays no longer compress horizontally when zooming — removed contain:strict from the overlay container, which was preventing objectFit from applying correctly to img elements inside a CSS containment context' },
            ]
        },
        {
            version: '1.3.11',
            date: '2026-05-10',
            items: [
                { type: 'added', text: 'Ghost Palette Color Search: \u201cEnable filtered\u201d button next to \u201cHide unmatched colors\u201d \u2014 when clicked, every color whose hex matches the current search terms is enabled in the ghost palette and every non-matching color is disabled; also clears the \u201cShow All\u201d (disable filter) toggle if it was active' },
                { type: 'fixed', text: 'Map Markers: YouTube markers no longer use an <iframe> (blocked by site CSP frame-src) — now renders as a clickable thumbnail overlay (thumbnail fetched via GM_xmlhttpRequest to bypass img-src CSP); clicking opens the video in a new tab' },
                { type: 'removed', text: 'Map Markers: YouTube embed markers removed — the site CSP blocks both iframe embedding and direct thumbnail image loading; the workaround was too limited to be useful' },
            ]
        },
        {
            version: '1.3.10',
            date: '2026-05-10',
            items: [
                { type: 'fixed', text: 'Map Markers: image/GIF/video data now stored in IndexedDB instead of GM_setValue — eliminates the "Message exceeded maximum allowed size of 64MiB" Chrome extension runtime error that occurred when many large images were stored, which blocked all other Tampermonkey userscripts from loading' },
                { type: 'added', text: 'Map Markers: automatic one-time migration — existing marker images stored in the old GM_setValue keys are moved to IndexedDB on first load and the old keys are cleaned up' },
            ]
        },
        {
            version: '1.3.9',
            date: '2026-05-10',
            items: [
                { type: 'added', text: 'Map Markers: GIF and MP4 embeds — upload animated GIFs or MP4 videos as map markers; rendered as live DOM overlays so animation/playback is preserved (GIF plays, video autoplays muted/looped)' },
                { type: 'added', text: 'Map Markers: drag-to-sort — grab the ⠿ handle on any marker card and drag it up or down to reorder the marker rendering/list order; new order is persisted immediately' },
                { type: 'added', text: 'Map Markers: compact card view — toggle button in the modal header switches to a condensed single-row view showing thumbnail, name, and coordinates only; clicking any compact card expands it in-place to reveal the full controls' },
            ]
        },
        {
            version: '1.3.8',
            date: '2026-05-10',
            items: [
                { type: 'added', text: 'Smooth Zoom Buttons: new extra setting (on by default) — replaces the native +/− zoom buttons with a smooth zoom control: vertical slider, exact value input box, hold-to-repeat ± buttons, and scroll-wheel zoom on the widget' },
            ]
        },
        {
            version: '1.3.7',
            date: '2026-05-10',
            items: [
                { type: 'added', text: 'Map Markers: new feature \u2014 upload any PNG/JPEG/WebP/GIF image and place it as a persistent sticker on the map canvas; images scale and translate with the map in real time using the same grid coordinate system as Censor rects in GeoPixels++' },
                { type: 'added', text: 'Map Markers: \u201cPlace on Map\u201d button enters placement mode for a specific marker \u2014 hold and drag to define the bounding box; click without dragging is rejected with an instructional message' },
                { type: 'added', text: 'Map Markers: Hold Shift during placement drag to lock the image\u2019s natural aspect ratio; per-marker \ud83d\udd12/\ud83d\udd13 toggle persists the lock preference for future edits' },
                { type: 'added', text: 'Map Markers: \u201cEdit\u201d button shows 8 fixed-size handles (4 corners + 4 edge midpoints) for resizing/stretching; handles are a consistent 6\u202fpx radius regardless of map zoom level; corner drags respect the Shift / lock-aspect setting, edge midpoints stretch freely' },
                { type: 'added', text: 'Map Markers: draggable management modal with per-image thumbnail, name input, opacity slider, visibility toggle, Place / Edit / Remove actions, and placement status' },
                { type: 'added', text: 'Map Markers: images and placement state persisted via GM_setValue \u2014 survive page refreshes; image data stored per-marker under separate keys to keep the metadata payload small' },
            ]
        },
        {
            version: '1.3.6',
            date: '2026-05-09',
            items: [
                { type: 'fixed', text: 'Ghost Template Manager: loading from Image History with encoded coords placed the image at the old position \u2014 root cause: applyCoordinatesToGame called initializeGhostFromStorage before the game\'s FileReader had written ghostImageData to localStorage, so it returned early; fix: write ghostImageCoords immediately, then poll until BOTH the place button is re-enabled AND ghostImageData is present before calling initializeGhostFromStorage' },
                { type: 'fixed', text: 'Ghost Template Manager: \u2018Go To\u2019 button said \u201cNo ghost image template currently set\u201d after loading from History \u2014 ghostImageCoords is now written before the poll starts so it is always available' },
                { type: 'fixed', text: 'Ghost Template Manager: loading a new image with no encoded position cleared ghostImageCoords from localStorage \u2014 if the user had manually placed a previous image the overlay appeared positioned but saving produced no coords; coords are now only cleared by the explicit Clear button' },
                { type: 'fixed', text: 'Ghost Template Manager: \u2018Go To\u2019 button in the panel said \u201cNo ghost image template currently set\u201d after loading from History because ghostImageTopLeft (in-memory, page scope) was never set by the race-losing initializeGhostFromStorage call \u2014 fixed by the same direct-assignment approach above' },
                { type: 'fixed', text: 'Region Screenshot: screenshot filenames now use local time instead of UTC \u2014 falls back to UTC if local time formatting fails' },
            ]
        },
        {
            version: '1.3.5',
            date: '2026-05-08',
            items: [
                { type: 'changed', text: 'Paint Brush Swap: reduced to a single resize handle \u2014 top-right corner when the paint menu is at the bottom (default), bottom-right corner when the paint menu is docked to the top; height drag direction flips accordingly' },
                { type: 'added', text: 'Paint Brush Swap: \u21BA reset-size button in the footer clears the stored width/height and restores the dropdown to its natural size' },
            ]
        },
        {
            version: '1.3.4',
            date: '2026-05-08',
            items: [
                { type: 'fixed', text: 'Paint Brush Swap: collapsed brush dropdown was intercepting canvas clicks after any resize drag \u2014 the div retained its physical footprint despite being visually hidden; fixed with pointer-events:none on the closed state and pointer-events:auto on .open' },
                { type: 'fixed', text: 'Paint Brush Swap: resize drag was laggy because the CSS max-height transition kept firing on every mousemove; transitions are now disabled for the duration of the drag via a .brush-swap-resizing class and restored on mouseup' },
            ]
        },
        {
            version: '1.3.3',
            date: '2026-05-08',
            items: [
                { type: 'fixed', text: 'Paint Brush Swap: resize handles on the brush dropdown were jittery and drifting \u2014 now use capture-phase listeners, a single start-size snapshot, and set explicit width alongside maxWidth/maxHeight to match the guild panel resize behavior' },
            ]
        },
        {
            version: '1.3.2',
            date: '2026-05-08',
            items: [
                { type: 'changed', text: 'Paint Brush Swap \u2014 Export: brush list now shows as a scrollable checklist with checkboxes; Select All / Select None controls; JSON output and Copy/Download actions only include checked brushes; header updates live to show selection count' },
                { type: 'changed', text: 'Paint Brush Swap \u2014 Import: JSON is parsed on input/file-upload and immediately rendered as a scrollable checklist; only checked brushes are committed when clicking \u201cImport Selected\u201d; invalid entries still reported with a count' },
            ]
        },
        {
            version: '1.3.1',
            date: '2026-05-08',
            items: [
                { type: 'changed', text: 'Paint Brush Swap: Export/Import footer buttons now show "📤 Export" and "📥 Import" labels instead of bare arrows — same height, wider to fit the text' },
            ]
        },
        {
            version: '1.3.0',
            date: '2026-05-07',
            items: [
                { type: 'added', text: 'Paint Brush Swap: Export Brushes \u2014 \u2B06 button at the bottom of the brush dropdown opens a modal with the full brush list as JSON; copy to clipboard or download as a .json file' },
                { type: 'added', text: 'Paint Brush Swap: Import Brushes \u2014 \u2B07 button opens a modal where you can paste JSON or upload a .json file; imported brushes are appended to your existing list (no overwrites), invalid entries are skipped with a count' },
            ]
        },
        {
            version: '1.2.2',
            date: '2026-05-07',
            items: [
                { type: 'fixed', text: 'Ghost Template Manager: loading a template from Image History that was saved without coordinates threw "parameter 1 is not of type File" — images retrieved from IndexedDB are Blobs, not Files; when no encoded position is decoded the blob is now wrapped in a File before being passed to DataTransfer' },
            ]
        },
        {
            version: '1.2.1',
            date: '2026-05-06',
            items: [
                { type: 'fixed', text: 'Debug System: applyCoordinatesToGame now logs to the debug file when the place button never becomes enabled (5s timeout) or when initializeGhostFromStorage is missing from page scope — both are silent failures that previously produced no debug output' },
                { type: 'fixed', text: 'Debug System: IndexedDB open failure now rejects with a real Error object (was a bare string) and is captured in the debug log, making DB unavailability visible' },
                { type: 'fixed', text: 'Debug System: HistoryManager.getAll transaction errors now reject (instead of hanging forever) and are captured in the debug log' },
                { type: 'fixed', text: 'Debug System: cleanCanvas.toBlob inside processAndLoadImage now has a 10s timeout — if the browser never calls the callback the hang is caught, logged, and the load fails gracefully instead of stalling silently' },
            ]
        },
        {
            version: '1.2.0',
            date: '2026-05-06',
            items: [
                { type: 'added', text: 'Debug System: new "Enable Debugging" toggle in Settings — when on, a "Debug Logs" button appears in the dropdown that exports a timestamped .txt log of all caught errors with their UI component, message, and stack trace' },
                { type: 'added', text: 'Debug System: all feature initialization catch blocks, Ghost Template Manager operations (processAndLoadImage, URL upload, Save Pos, Auto-Cache, Export, Import history), and card interactions now feed into dbgPush when debugging is enabled' },
            ]
        },
        {
            version: '1.1.9',
            date: '2026-04-26',
            items: [
                { type: 'fixed', text: 'Guild XP Tracker: player markers on map showed numeric user IDs instead of display names — buildPlayerMarkerData was using the dict key (now a numeric ID) as the name; fixed to use data.name with key as fallback' },
                { type: 'added', text: 'Ghost Template Manager: selective export in Image History — ☐ per-card checkbox, Select All / Select None buttons, and "Export Selected" exports only checked templates (same JSON format; compatible with import); "Export All" removed in favour of Select All + Export Selected' },
            ]
        },
        {
            version: '1.1.8',
            date: '2026-04-26',
            items: [
                { type: 'fixed', text: 'Auto-Hover Menus: menuGroupBtn and modGroupBtn have class="... relative ..." on the button element itself — button.closest(\'.relative\') returned the button instead of the wrapper div, so parent/dropdown/isMenuOpen were all wrong and every button→dropdown mousemove re-toggled the menu closed; fixed by using button.closest(\'div.relative\'); also added _gpcOpened flag + wrapper mouseleave guard as a defensive layer against re-clicks on re-hover' },
                { type: 'fixed', text: 'Guild XP Tracker: users who rename no longer appear as LEFT + JOINED — snapshots now key by numeric user ID; display name is stored separately and stays current' },
                { type: 'fixed', text: 'Guild XP Tracker: one-time migration auto-converts old name-keyed snapshots to ID-keyed format so existing history remains valid' },
                { type: 'fixed', text: 'Brush Editor (Opera GX failsafe — pending end-user validation): after panel closes, overlay gets pointer-events:none + visibility:hidden to prevent ghost click-through on browsers that may not apply Tailwind hidden reliably' },
                { type: 'added', text: 'Brush Editor: left-click (or drag) immediately activates cells; right-click (or drag) erases — no toggle delay' },
                { type: 'added', text: 'Brush Editor: "Fill All" button beside "Reset Active Brush" — instantly activates every cell in the grid' },
            ]
        },
        {
            version: '1.1.7',
            date: '2026-04-19',
            items: [
                { type: 'added', text: 'Setting: Disable Group Noise — permanently unchecks and grays out the "Group Noise" toggle in the ghost modal' },
                { type: 'added', text: 'Setting: Start in Shift Lock — auto-enables Shift Lock on page load' },
                { type: 'added', text: 'Setting: Start in Inspect Mode — auto-switches to Inspect Mode on page load' },
                { type: 'added', text: 'Changelog modal accessible from the GeoPixelcons++ dropdown menu' },
            ]
        },
        {
            version: '1.1.6',
            date: '2026-04-19',
            items: [
                { type: 'renamed', text: 'Extension: "Janny Tools" renamed to "Janitor View" throughout' },
                { type: 'added', text: 'Setting: Compact Paint Controls — moves ✕ close and 🖌️ brushes buttons above the paint modal as compact icons' },
                { type: 'added', text: 'Compact brush button forwards scroll-to-swap wheel events' },
                { type: 'added', text: 'Compact brush dropdown repositioned via fixed positioning on document.body to avoid transform issues' },
                { type: 'added', text: 'Compact ✕ button positioned absolutely to the right of the topBar, isolated from centered buttons' },
                { type: 'improved', text: 'Ghost template colors now sorted by pixel count (most used → least used) in both bulk purchase paths' },
                { type: 'added', text: 'Bulk purchase modal and profile queue now show ghost template pixel counts per color (purple labels)' },
                { type: 'added', text: 'Legend in bulk purchase modal: "Purple numbers = pixels used in the loaded ghost template"' },
                { type: 'added', text: 'Ghost palette MutationObserver auto-refreshes queue ghost counts when template is loaded/unloaded' },
            ]
        },
        {
            version: '1.1.4',
            date: '2026-04-18',
            items: [
                { type: 'added', text: 'Extension: Janitor View — reveals the hidden moderation button for janitors/moderators' },
                { type: 'fixed', text: 'VERSION constant now correctly synced with @version header (was stuck at 1.0.3)' },
            ]
        },
        {
            version: '1.1.3',
            date: '2026-04-17',
            items: [
                { type: 'note', text: 'Version bump — identical to 1.0.3 (header version sync)' },
            ]
        },
        {
            version: '1.0.3',
            date: '2026-04-17',
            items: [
                { type: 'fixed', text: 'Guild Overhaul: auto-center panel on open — fixes draggable guild panel getting stuck off-screen' },
                { type: 'improved', text: 'Bulk Purchase: newly purchased colors are now automatically added to the active palette' },
            ]
        },
        {
            version: '1.0.2',
            date: '2026-04-16',
            items: [
                { type: 'added', text: 'Paint Brush Swap: click-to-expand brush preview grids (36×36 → 120×120)' },
                { type: 'added', text: 'Brush preview tooltip on hover ("click to expand")' },
                { type: 'fixed', text: 'Region Screenshot & Regions Highscore: no longer re-enable doubleClickZoom (respects site default)' },
            ]
        },
        {
            version: '1.0.0',
            date: '2026-04-15',
            items: [
                { type: 'note', text: 'Initial release — unified enhancement suite' },
                { type: 'added', text: 'Features: Bulk Purchase Colors, Ghost Palette Search, Ghost Template Manager, Guild Overhaul, Paint Menu Controls, Paint Brush Swap, Region Screenshot, Regions Highscore, Theme Editor' },
                { type: 'added', text: 'Extensions: Auto-open Menus on Hover, Auto-Go to Last Location, Hover Labels' },
                { type: 'added', text: 'Settings modal with per-feature toggles, status indicators, and emoji icon option' },
            ]
        },
    ];

    function showChangelog() {
        const existing = document.getElementById('gpc-changelog-modal');
        if (existing) existing.remove();

        const dark = isDarkMode();
        const overlay = document.createElement('div');
        overlay.id = 'gpc-changelog-modal';
        overlay.style.cssText = 'position:fixed;inset:0;z-index:100001;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.5);';

        const TYPE_COLORS = {
            added:    { bg: dark ? '#052e16' : '#f0fdf4', text: dark ? '#4ade80' : '#166534', label: 'NEW' },
            improved: { bg: dark ? '#1e1b4b' : '#eef2ff', text: dark ? '#818cf8' : '#4338ca', label: 'IMPROVED' },
            fixed:    { bg: dark ? '#422006' : '#fffbeb', text: dark ? '#fbbf24' : '#92400e', label: 'FIX' },
            renamed:  { bg: dark ? '#164e63' : '#ecfeff', text: dark ? '#22d3ee' : '#0e7490', label: 'RENAMED' },
            changed:  { bg: dark ? '#1c1917' : '#fafaf9', text: dark ? '#a8a29e' : '#57534e', label: 'CHANGED' },
            removed:  { bg: dark ? '#450a0a' : '#fff1f2', text: dark ? '#f87171' : '#9f1239', label: 'REMOVED' },
            note:     { bg: dark ? '#1e293b' : '#f8fafc', text: dark ? '#94a3b8' : '#64748b', label: 'NOTE' },
        };

        const modal = document.createElement('div');
        modal.style.cssText = `
            background: ${dark ? '#1e1e2e' : '#ffffff'};
            color: ${dark ? '#cdd6f4' : '#1e293b'};
            border-radius: 16px;
            width: 90%;
            max-width: 560px;
            max-height: 80vh;
            display: flex;
            flex-direction: column;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        `;

        // Header
        const header = document.createElement('div');
        header.style.cssText = `
            padding: 20px 24px 16px;
            border-bottom: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            display: flex;
            align-items: center;
            justify-content: space-between;
            flex-shrink: 0;
        `;
        const title = document.createElement('h2');
        title.style.cssText = 'margin:0;font-size:1.25rem;font-weight:700;';
        title.textContent = '📋 Changelog';
        const closeBtn = document.createElement('button');
        closeBtn.style.cssText = `width:2rem;height:2rem;border-radius:50%;border:none;background:${dark ? '#45475a' : '#f1f5f9'};cursor:pointer;font-size:1rem;display:flex;align-items:center;justify-content:center;color:${dark ? '#cdd6f4' : '#64748b'};`;
        closeBtn.textContent = '✕';
        closeBtn.addEventListener('click', () => overlay.remove());
        header.appendChild(title);
        header.appendChild(closeBtn);
        modal.appendChild(header);

        // ── Filter bar ────────────────────────────────────────────────
        // Collect every type that actually appears in the changelog data
        const allTypes = [...new Set(
            CHANGELOG.flatMap(r => r.items.map(it => it.type))
        )];
        // Canonical display order; unknown types appended at the end
        const typeOrder = ['added','fixed','improved','changed','removed','renamed','note'];
        allTypes.sort((a, b) => {
            const ai = typeOrder.indexOf(a), bi = typeOrder.indexOf(b);
            return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
        });

        const filterBar = document.createElement('div');
        filterBar.style.cssText = `display:flex;flex-wrap:wrap;gap:6px;padding:10px 24px;border-bottom:1px solid ${dark ? '#45475a' : '#e2e8f0'};flex-shrink:0;`;

        // activeTypes tracks which labels are enabled (all on by default)
        const activeTypes = new Set(allTypes);

        function applyFilters() {
            body.querySelectorAll('[data-cl-type]').forEach(row => {
                row.style.display = activeTypes.has(row.dataset.clType) ? '' : 'none';
            });
            // Hide version headers whose every item is hidden
            body.querySelectorAll('[data-cl-section]').forEach(section => {
                const rows = section.querySelectorAll('[data-cl-type]');
                const anyVisible = [...rows].some(r => r.style.display !== 'none');
                section.style.display = anyVisible ? '' : 'none';
            });
        }

        allTypes.forEach(type => {
            const tc = TYPE_COLORS[type] || TYPE_COLORS.note;
            const chip = document.createElement('button');
            chip.style.cssText = `font-size:0.6rem;font-weight:700;padding:3px 8px;border-radius:4px;border:none;cursor:pointer;transition:opacity .15s;background:${tc.text}18;color:${tc.text};`;
            chip.textContent = tc.label;
            chip.title = `Toggle ${tc.label} entries`;
            chip.dataset.active = '1';
            chip.addEventListener('click', () => {
                if (activeTypes.has(type)) {
                    activeTypes.delete(type);
                    chip.dataset.active = '0';
                    chip.style.opacity = '0.35';
                } else {
                    activeTypes.add(type);
                    chip.dataset.active = '1';
                    chip.style.opacity = '1';
                }
                applyFilters();
            });
            filterBar.appendChild(chip);
        });

        modal.appendChild(filterBar);

        // Body (scrollable)
        const body = document.createElement('div');
        body.style.cssText = 'overflow-y:auto;padding:16px 24px 24px;flex:1;';

        CHANGELOG.forEach((release, i) => {
            const section = document.createElement('div');
            section.dataset.clSection = release.version;
            section.style.cssText = `margin-bottom:${i < CHANGELOG.length - 1 ? '20px' : '0'};`;

            const versionHeader = document.createElement('div');
            versionHeader.style.cssText = `display:flex;align-items:center;gap:10px;margin-bottom:10px;`;
            const vBadge = document.createElement('span');
            vBadge.style.cssText = `font-size:0.95rem;font-weight:700;color:${dark ? '#cba6f7' : '#7c3aed'};`;
            vBadge.textContent = 'v' + release.version;
            const vDate = document.createElement('span');
            vDate.style.cssText = `font-size:0.75rem;color:${dark ? '#6c7086' : '#94a3b8'};`;
            vDate.textContent = release.date;
            versionHeader.appendChild(vBadge);
            versionHeader.appendChild(vDate);
            if (i === 0) {
                const currentBadge = document.createElement('span');
                currentBadge.style.cssText = `font-size:0.6rem;font-weight:700;padding:2px 6px;border-radius:4px;background:${dark ? '#22c55e33' : '#dcfce7'};color:${dark ? '#4ade80' : '#166534'};`;
                currentBadge.textContent = 'CURRENT';
                versionHeader.appendChild(currentBadge);
            }
            section.appendChild(versionHeader);

            release.items.forEach(item => {
                const tc = TYPE_COLORS[item.type] || TYPE_COLORS.note;
                const row = document.createElement('div');
                row.dataset.clType = item.type;
                row.style.cssText = `display:flex;align-items:flex-start;gap:8px;padding:5px 8px;margin-bottom:3px;border-radius:6px;background:${tc.bg};`;
                const badge = document.createElement('span');
                badge.style.cssText = `font-size:0.55rem;font-weight:700;padding:2px 5px;border-radius:3px;color:${tc.text};background:${tc.text}18;white-space:nowrap;margin-top:2px;flex-shrink:0;`;
                badge.textContent = tc.label;
                const text = document.createElement('span');
                text.style.cssText = `font-size:0.8rem;color:${dark ? '#bac2de' : '#334155'};line-height:1.4;`;
                text.textContent = item.text;
                row.appendChild(badge);
                row.appendChild(text);
                section.appendChild(row);
            });

            if (i < CHANGELOG.length - 1) {
                const divider = document.createElement('div');
                divider.style.cssText = `height:1px;background:${dark ? '#45475a' : '#e2e8f0'};margin-top:16px;`;
                section.appendChild(divider);
            }

            body.appendChild(section);
        });

        modal.appendChild(body);
        overlay.appendChild(modal);
        overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
        document.body.appendChild(overlay);
    }

    // ============================================================
    //  UI: CONTROLS-LEFT SUBMENU
    // ============================================================
    function waitForControlsLeft(cb) {
        const el = document.getElementById('controls-left');
        if (el) return cb(el);
        setTimeout(() => waitForControlsLeft(cb), 500);
    }

    waitForControlsLeft((controlsLeft) => {
        // Create the group container
        const group = document.createElement('div');
        group.className = 'relative';

        // Main button
        const mainBtn = document.createElement('button');
        mainBtn.id = 'geopixelconsGroupBtn';
        mainBtn.className = 'w-10 h-10 bg-white shadow rounded-full flex items-center justify-center hover:bg-gray-100 cursor-pointer';
        mainBtn.title = 'GeoPixelcons++';
        const _iconBg = 'url(data:image/png;base64,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);background-size:cover;background-position:center;border:none;padding:0;';

        // Apply icon to button
        mainBtn.style.cssText = 'background-image:' + _iconBg;
        mainBtn.dataset.iconBg = _iconBg.substring(0, _iconBg.indexOf(');') + 1);
        if (_settings.useEmojiIcon) {
            mainBtn.style.backgroundImage = 'none';
            mainBtn.textContent = '😢';
            mainBtn.style.fontSize = '20px';
        }

        // Dropdown
        const dropdown = document.createElement('div');
        dropdown.className = 'dropdown-menu absolute left-0 mt-2 flex flex-col gap-2 transition-all duration-150 ease-out opacity-0 scale-95 hidden';

        // ── Theme-aware color tokens for the dropdown UI ──
        // Use CSS custom properties so colors follow GeoPixels++ theme changes live
        const C = {
            pillBg:      'var(--color-white, #fff)',
            pillHover:   'var(--color-gray-100, #f3f4f6)',
            pillText:    'var(--color-gray-700, #374151)',
            flyBg:       'var(--color-white, #fff)',
            flyHover:    'var(--color-gray-100, #f3f4f6)',
            flyText:     'var(--color-gray-700, #374151)',
            flyMuted:    'var(--color-gray-500, #6b7280)',
            inputBg:     'var(--color-white, #fff)',
            inputBorder: 'var(--color-gray-300, #d1d5db)',
            inputText:   'var(--color-gray-900, #111827)',
            shadow:      '0 1px 3px rgba(0,0,0,.12)',
            activeBg:    'var(--color-green-100, #bbf7d0)',
            activeText:  'var(--color-green-800, #166534)',
            teBtnBg:     'var(--color-purple-500, #7c3aed)',
            teBtnText:   'var(--color-purple-50, #f3e8ff)',
            teActiveBg:  'var(--color-purple-400, #c4b5fd)',
            teInactiveBg:'var(--color-white, #fff)',
            teHover:     'var(--color-purple-100, #ede9fe)',
            navBg:       'var(--color-white, #fff)',
            navText:     'var(--color-gray-500, #6b7280)',
        };
        dropdown.id = 'geopixelconsDropdown';

        function openDropdown() {
            // Refresh debug button label with current log count (only when debugging is on)
            if (_settings.enableDebug) {
                const _dbgLabelSpan = document.querySelector('#gpc-debug-dropdown-btn span:nth-child(2)');
                if (_dbgLabelSpan) _dbgLabelSpan.textContent = `Debug Logs (${dbgCount()})`;
            }
            dropdown.classList.remove('hidden');
            setTimeout(() => {
                dropdown.classList.remove('opacity-0', 'scale-95');
            }, 10);
        }
        function closeDropdown() {
            dropdown.classList.add('opacity-0', 'scale-95');
            setTimeout(() => {
                dropdown.classList.add('hidden');
            }, 150);
        }

        mainBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            // Close other dropdowns using the site's function if available
            if (typeof closeAllDropdowns === 'function') {
                closeAllDropdowns();
            } else {
                document.querySelectorAll('.dropdown-menu').forEach(d => {
                    if (d !== dropdown && !d.classList.contains('hidden')) {
                        d.classList.add('opacity-0', 'scale-95');
                        setTimeout(() => d.classList.add('hidden'), 150);
                    }
                });
            }
            const isOpen = !dropdown.classList.contains('hidden');
            if (isOpen) closeDropdown(); else openDropdown();
        });

        function makeSubBtn(icon, label, onClick) {
            const btn = document.createElement('button');
            btn.className = 'gpc-pill-btn';
            btn.title = label;
            btn.style.cssText = 'position:relative;width:40px;height:40px;border-radius:9999px;background:'+C.pillBg+';box-shadow:'+C.shadow+';display:flex;align-items:center;justify-content:flex-start;border:none;cursor:pointer;overflow:hidden;transition:width .25s cubic-bezier(.4,0,.2,1);padding:0;font-size:16px;flex-shrink:0;';
            const iconSpan = document.createElement('span');
            iconSpan.style.cssText = 'width:40px;min-width:40px;text-align:center;flex-shrink:0;line-height:40px;';
            iconSpan.textContent = icon;
            const labelSpan = document.createElement('span');
            labelSpan.style.cssText = 'white-space:nowrap;font-size:12px;font-weight:600;color:'+C.pillText+';opacity:0;transition:opacity .2s .05s;padding-right:12px;pointer-events:none;';
            labelSpan.textContent = label;
            btn.appendChild(iconSpan);
            btn.appendChild(labelSpan);
            btn.addEventListener('mouseenter', () => {
                const textW = labelSpan.scrollWidth + 12;
                btn.style.width = (40 + textW) + 'px';
                labelSpan.style.opacity = '1';
                btn.style.background = C.pillHover;
            });
            btn.addEventListener('mouseleave', () => {
                btn.style.width = '40px';
                labelSpan.style.opacity = '0';
                btn.style.background = C.pillBg;
            });
            btn.addEventListener('click', (e) => {
                e.stopPropagation();
                btn.style.width = '40px';
                labelSpan.style.opacity = '0';
                btn.style.background = C.pillBg;
                closeDropdown();
                onClick();
            });
            return btn;
        }

        // ─── Shared flyout close registry (only one open at a time) ──
        const _flyoutClosers = [];
        function closeAllFlyouts() { for (const fn of _flyoutClosers) fn(); }

        // ─── Shared flyout builder for Screenshot / Highscore ─────────
        function buildFeatureFlyout(opts) {
            // opts: { id, icon, title, featureKey, getModule, color }
            const group = document.createElement('div');
            group.style.cssText = 'position:relative;';

            const mainBtn = document.createElement('button');
            mainBtn.className = 'gpc-pill-btn';
            mainBtn.title = opts.title;
            mainBtn.style.cssText = 'position:relative;width:40px;height:40px;border-radius:9999px;background:'+C.pillBg+';box-shadow:'+C.shadow+';display:flex;align-items:center;justify-content:flex-start;border:none;cursor:pointer;overflow:hidden;transition:width .25s cubic-bezier(.4,0,.2,1);padding:0;font-size:16px;flex-shrink:0;';
            mainBtn.id = 'gpc-' + opts.id + '-sub';
            const mainIcon = document.createElement('span');
            mainIcon.style.cssText = 'width:40px;min-width:40px;text-align:center;flex-shrink:0;line-height:40px;';
            mainIcon.textContent = opts.icon;
            const mainLabel = document.createElement('span');
            mainLabel.style.cssText = 'white-space:nowrap;font-size:12px;font-weight:600;color:'+C.pillText+';opacity:0;transition:opacity .2s .05s;padding-right:12px;pointer-events:none;';
            mainLabel.textContent = opts.title;
            mainBtn.appendChild(mainIcon);
            mainBtn.appendChild(mainLabel);
            mainBtn.addEventListener('mouseenter', () => {
                if (flyoutOpen) return;
                const textW = mainLabel.scrollWidth + 12;
                mainBtn.style.width = (40 + textW) + 'px';
                mainLabel.style.opacity = '1';
                mainBtn.style.background = C.pillHover;
            });
            mainBtn.addEventListener('mouseleave', () => {
                mainBtn.style.width = '40px';
                mainLabel.style.opacity = '0';
                mainBtn.style.background = C.pillBg;
            });
            function collapsePill() {
                mainBtn.style.width = '40px';
                mainLabel.style.opacity = '0';
                mainBtn.style.background = C.pillBg;
            }

            const flyout = document.createElement('div');
            flyout.id = 'gpc-' + opts.id + '-flyout';
            Object.assign(flyout.style, {
                position: 'absolute', left: 'calc(100% + 8px)', top: '0',
                transform: 'scale(0.95)',
                display: 'flex', flexDirection: 'column', gap: '4px',
                transition: 'all 0.15s ease-out',
                opacity: '0', pointerEvents: 'none', zIndex: '21',
            });
            let flyoutOpen = false;

            const flyBtnStyle = 'display:flex;align-items:center;gap:6px;min-width:200px;padding:5px 10px;background:'+C.flyBg+';box-shadow:'+C.shadow+';border-radius:6px;border:none;cursor:pointer;font-size:11px;font-weight:500;color:'+C.flyText+';white-space:nowrap;height:28px;transition:background .12s;';
            const flyBtnActiveStyle = flyBtnStyle.replace('background:'+C.flyBg,'background:'+C.activeBg).replace('color:'+C.flyText,'color:'+C.activeText);

            function makeFlyBtn(label, emoji, onClick, extraId) {
                const btn = document.createElement('button');
                btn.style.cssText = flyBtnStyle;
                btn.innerHTML = emoji + ' ' + label;
                if (extraId) btn.id = extraId;
                btn.addEventListener('mouseenter', () => { if (!btn.dataset.active) btn.style.background = C.flyHover; });
                btn.addEventListener('mouseleave', () => { if (!btn.dataset.active) btn.style.background = C.flyBg; });
                btn.addEventListener('click', (e) => { e.stopPropagation(); closeFly(); closeDropdown(); onClick(); });
                return btn;
            }

            function closeFly() {
                flyoutOpen = false;
                flyout.style.opacity = '0';
                flyout.style.pointerEvents = 'none';
                flyout.style.transform = 'scale(0.95)';
            }

            // ── 1) Select Area (ad-hoc drag) ──
            flyout.appendChild(makeFlyBtn('Select Area', '🔲', () => {
                const triggerBtn = document.getElementById('gpc-' + opts.id + '-trigger');
                if (triggerBtn) triggerBtn.click();
            }));

            // ── 2) Pick Points (click two corners) ──
            flyout.appendChild(makeFlyBtn('Pick Points', '📌', () => {
                startPickPointsMode(opts);
            }));

            // ── 3) Input Coords ──
            const coordForm = document.createElement('div');
            Object.assign(coordForm.style, {
                display: 'flex', flexDirection: 'column', gap: '4px',
                minWidth: '200px', padding: '6px 10px',
                background: C.flyBg, boxShadow: C.shadow,
                borderRadius: '6px', fontSize: '11px',
            });
            const cached = loadCachedCoords();
            const _inputSt = 'width:60px;padding:2px 4px;border:1px solid '+C.inputBorder+';border-radius:4px;font-size:11px;background:'+C.inputBg+';color:'+C.inputText+';';
            coordForm.innerHTML =
                '<div style="font-weight:600;color:'+C.flyText+';margin-bottom:2px;">📝 Input Coords</div>' +
                '<div style="display:flex;gap:4px;align-items:center;">' +
                '  <span style="width:22px;color:'+C.flyMuted+';font-size:10px;">NW</span>' +
                '  <input id="gpc-' + opts.id + '-nw-x" type="number" placeholder="X" style="'+_inputSt+'" value="' + (cached ? cached.minX : '') + '">' +
                '  <input id="gpc-' + opts.id + '-nw-y" type="number" placeholder="Y" style="'+_inputSt+'" value="' + (cached ? cached.maxY : '') + '">' +
                '</div>' +
                '<div style="display:flex;gap:4px;align-items:center;">' +
                '  <span style="width:22px;color:'+C.flyMuted+';font-size:10px;">SE</span>' +
                '  <input id="gpc-' + opts.id + '-se-x" type="number" placeholder="X" style="'+_inputSt+'" value="' + (cached ? cached.maxX : '') + '">' +
                '  <input id="gpc-' + opts.id + '-se-y" type="number" placeholder="Y" style="'+_inputSt+'" value="' + (cached ? cached.minY : '') + '">' +
                '</div>' +
                '<button id="gpc-' + opts.id + '-coord-go" style="margin-top:2px;padding:4px 8px;background:' + (opts.color || '#3b82f6') + ';color:white;border:none;border-radius:4px;font-size:11px;font-weight:600;cursor:pointer;">Go</button>';
            coordForm.addEventListener('click', (e) => e.stopPropagation());
            flyout.appendChild(coordForm);

            // Wire the Go button after appending
            setTimeout(() => {
                const goBtn = document.getElementById('gpc-' + opts.id + '-coord-go');
                if (goBtn) goBtn.addEventListener('click', () => {
                    const minX = parseInt(document.getElementById('gpc-' + opts.id + '-nw-x').value);
                    const maxY = parseInt(document.getElementById('gpc-' + opts.id + '-nw-y').value);
                    const maxX = parseInt(document.getElementById('gpc-' + opts.id + '-se-x').value);
                    const minY = parseInt(document.getElementById('gpc-' + opts.id + '-se-y').value);
                    if ([minX, maxY, maxX, minY].some(isNaN)) return;
                    const bounds = { minX: Math.min(minX, maxX), maxX: Math.max(minX, maxX), minY: Math.min(minY, maxY), maxY: Math.max(minY, maxY) };
                    saveCachedCoords(bounds);
                    closeFly(); closeDropdown();
                    const mod = opts.getModule();
                    if (mod && mod.processWithBounds) mod.processWithBounds(bounds);
                });
            }, 0);

            // ── 4) Auto-screenshot toggle (screenshot only) ──
            if (opts.id === 'screenshot') {
                const autoBtn = document.createElement('button');
                autoBtn.id = 'gpc-auto-screenshot-btn';
                const isOn = isAutoScreenshotEnabled() && loadCachedCoords();
                autoBtn.style.cssText = isOn ? flyBtnActiveStyle : flyBtnStyle;
                autoBtn.innerHTML = '📷 Auto-save on paint';
                if (isOn) autoBtn.dataset.active = '1';
                autoBtn.title = 'Takes a screenshot of the cached area every time you paint. Requires Input Coords.';
                autoBtn.addEventListener('mouseenter', () => { if (!autoBtn.dataset.active) autoBtn.style.background = C.flyHover; });
                autoBtn.addEventListener('mouseleave', () => { if (!autoBtn.dataset.active) autoBtn.style.background = isAutoScreenshotEnabled() && loadCachedCoords() ? C.activeBg : C.flyBg; });
                autoBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const coords = loadCachedCoords();
                    if (!coords) {
                        _gpcNotify('Set coords first (Input Coords or Pick Points).', true);
                        return;
                    }
                    const nowOn = !isAutoScreenshotEnabled();
                    setAutoScreenshot(nowOn);
                    autoBtn.style.cssText = nowOn ? flyBtnActiveStyle : flyBtnStyle;
                    if (nowOn) { autoBtn.dataset.active = '1'; autoBtn.innerHTML = '📷 Auto-save on paint ✅'; }
                    else { delete autoBtn.dataset.active; autoBtn.innerHTML = '📷 Auto-save on paint'; }
                    _gpcNotify(nowOn ? 'Auto-screenshot ON' : 'Auto-screenshot OFF');
                });
                flyout.appendChild(autoBtn);
            }

            _flyoutClosers.push(closeFly);

            mainBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                collapsePill();
                const wasOpen = flyoutOpen;
                closeAllFlyouts();
                if (!wasOpen) {
                    flyoutOpen = true;
                    // Refresh cached coord values in inputs
                    const cc = loadCachedCoords();
                    const setVal = (suffix, val) => { const el = document.getElementById('gpc-' + opts.id + '-' + suffix); if (el) el.value = val ?? ''; };
                    setVal('nw-x', cc?.minX); setVal('nw-y', cc?.maxY); setVal('se-x', cc?.maxX); setVal('se-y', cc?.minY);
                    flyout.style.opacity = '1'; flyout.style.pointerEvents = 'auto'; flyout.style.transform = 'scale(1)';
                    // Refresh auto-screenshot button state
                    const ab = document.getElementById('gpc-auto-screenshot-btn');
                    if (ab) {
                        const isOn = isAutoScreenshotEnabled() && loadCachedCoords();
                        ab.style.cssText = isOn ? flyBtnActiveStyle : flyBtnStyle;
                        ab.innerHTML = isOn ? '📷 Auto-save on paint ✅' : '📷 Auto-save on paint';
                        if (isOn) ab.dataset.active = '1'; else delete ab.dataset.active;
                    }
                }
            });
            document.addEventListener('click', (e) => { if (flyoutOpen && !group.contains(e.target)) closeFly(); });

            group.appendChild(mainBtn);
            group.appendChild(flyout);
            return group;
        }

        // ─── Shared "Pick Points" mode ──────────────────────────────
        let _pickState = null;

        function startPickPointsMode(opts) {
            if (_pickState) cleanupPickPoints();
            const map = _getMapRef();
            if (!map) { _gpcNotify('Map not ready.', true); return; }

            _pickState = { opts, step: 0, markers: [], handler: null, keyHandler: null };
            _gpcNotify('Click top-left corner…');
            document.body.style.cursor = 'crosshair';

            _pickState.handler = function(e) {
                const gSize = (typeof gridSize !== 'undefined') ? gridSize : 25;
                const merc = turf.toMercator([e.lngLat.lng, e.lngLat.lat]);
                const gx = Math.round(merc[0] / gSize);
                const gy = Math.round(merc[1] / gSize);

                if (_pickState.step === 0) {
                    _pickState.p1 = { x: gx, y: gy };
                    // Add marker
                    const el = _createPickMarker('NW', '#ef4444');
                    const marker = new maplibregl.Marker({ element: el }).setLngLat(e.lngLat).addTo(map);
                    _pickState.markers.push(marker);
                    _pickState.step = 1;
                    _gpcNotify('Click bottom-right corner…');
                } else {
                    _pickState.p2 = { x: gx, y: gy };
                    const el = _createPickMarker('SE', '#3b82f6');
                    const marker = new maplibregl.Marker({ element: el }).setLngLat(e.lngLat).addTo(map);
                    _pickState.markers.push(marker);

                    const p1 = _pickState.p1, p2 = _pickState.p2;
                    const bounds = {
                        minX: Math.min(p1.x, p2.x), maxX: Math.max(p1.x, p2.x),
                        minY: Math.min(p1.y, p2.y), maxY: Math.max(p1.y, p2.y),
                    };
                    saveCachedCoords(bounds);
                    cleanupPickPoints();
                    _gpcNotify('Coords applied! (' + bounds.minX + ',' + bounds.maxY + ') → (' + bounds.maxX + ',' + bounds.minY + ')');
                }
            };

            _pickState.keyHandler = function(e) {
                if (e.key === 'Escape') { cleanupPickPoints(); _gpcNotify('Cancelled.'); }
            };

            map.on('click', _pickState.handler);
            document.addEventListener('keydown', _pickState.keyHandler);
        }

        function cleanupPickPoints() {
            if (!_pickState) return;
            const map = _getMapRef();
            if (map) {
                if (_pickState.handler) map.off('click', _pickState.handler);
            }
            for (const m of _pickState.markers) m.remove();
            if (_pickState.keyHandler) document.removeEventListener('keydown', _pickState.keyHandler);
            document.body.style.cursor = '';
            _pickState = null;
        }

        function _createPickMarker(label, color) {
            const el = document.createElement('div');
            el.style.cssText = 'display:flex;flex-direction:column;align-items:center;pointer-events:none;';
            el.innerHTML =
                '<svg width="28" height="40" viewBox="0 0 24 36"><path d="M12 0C5.4 0 0 5.4 0 12c0 9 12 24 12 24s12-15 12-24C24 5.4 18.6 0 12 0z" fill="' + color + '"/><circle cx="12" cy="11" r="4.5" fill="white"/></svg>' +
                '<span style="font-size:10px;font-weight:700;color:' + color + ';text-shadow:0 0 2px white,0 0 2px white;">' + label + '</span>';
            return el;
        }

        function _getMapRef() {
            try { const m = (0, eval)('map'); if (m && typeof m.setStyle === 'function') return m; } catch {}
            if (typeof unsafeWindow !== 'undefined') { try { const m = unsafeWindow.eval('map'); if (m && typeof m.setStyle === 'function') return m; } catch {} }
            return null;
        }

        function _gpcNotify(msg, isError) {
            const existing = document.getElementById('gpc-flyout-toast'); if (existing) existing.remove();
            const toast = document.createElement('div'); toast.id = 'gpc-flyout-toast'; toast.textContent = msg;
            Object.assign(toast.style, { position:'fixed',top:'70px',left:'50%',transform:'translateX(-50%)',background:isError?'#fca5a5':'#bbf7d0',color:isError?'#7f1d1d':'#166534',padding:'8px 18px',borderRadius:'8px',fontSize:'13px',fontWeight:'600',zIndex:'100001',boxShadow:'0 4px 12px rgba(0,0,0,.2)',transition:'opacity .3s',fontFamily:"system-ui,sans-serif" });
            document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 2500);
        }

        // Screenshot button with flyout (only if enabled)
        if (_settings.regionScreenshot) {
            dropdown.appendChild(buildFeatureFlyout({
                id: 'screenshot', icon: '📸', title: 'Region Screenshot',
                featureKey: 'regionScreenshot', color: '#10b981',
                getModule: () => _regionScreenshot,
            }));
        }

        // Highscore button with flyout (only if enabled)
        if (_settings.regionsHighscore) {
            dropdown.appendChild(buildFeatureFlyout({
                id: 'highscore', icon: '🏆', title: 'Region Highscore',
                featureKey: 'regionsHighscore', color: '#3b82f6',
                getModule: () => _regionsHighscore,
            }));
        }

        // Theme Editor button with right-expanding flyout (only if enabled)
        // Note: _themeEditor is populated later by the feature module, so we
        // only check it lazily (on click), not at dropdown-build time.
        if (_settings.themeEditor) {
            const teGroup = document.createElement('div');
            teGroup.style.cssText = 'position:relative;';

            const teBtn = document.createElement('button');
            teBtn.className = 'gpc-pill-btn';
            teBtn.title = 'Theme Editor';
            teBtn.style.cssText = 'position:relative;width:40px;height:40px;border-radius:9999px;background:'+C.pillBg+';box-shadow:'+C.shadow+';display:flex;align-items:center;justify-content:flex-start;border:none;cursor:pointer;overflow:hidden;transition:width .25s cubic-bezier(.4,0,.2,1);padding:0;font-size:16px;flex-shrink:0;';
            teBtn.id = 'gpc-theme-sub';
            const teIcon = document.createElement('span');
            teIcon.style.cssText = 'width:40px;min-width:40px;text-align:center;flex-shrink:0;line-height:40px;';
            teIcon.textContent = '🎨';
            const teLabelSpan = document.createElement('span');
            teLabelSpan.style.cssText = 'white-space:nowrap;font-size:12px;font-weight:600;color:'+C.pillText+';opacity:0;transition:opacity .2s .05s;padding-right:12px;pointer-events:none;';
            teLabelSpan.textContent = 'Theme Editor';
            teBtn.appendChild(teIcon);
            teBtn.appendChild(teLabelSpan);
            teBtn.addEventListener('mouseenter', () => {
                if (teFlyoutOpen) return;
                const textW = teLabelSpan.scrollWidth + 12;
                teBtn.style.width = (40 + textW) + 'px';
                teLabelSpan.style.opacity = '1';
                teBtn.style.background = C.pillHover;
            });
            teBtn.addEventListener('mouseleave', () => {
                teBtn.style.width = '40px';
                teLabelSpan.style.opacity = '0';
                teBtn.style.background = C.pillBg;
            });
            function teCollapsePill() {
                teBtn.style.width = '40px';
                teLabelSpan.style.opacity = '0';
                teBtn.style.background = C.pillBg;
            }

            const teFlyout = document.createElement('div');
            teFlyout.id = 'gpc-theme-flyout';
            Object.assign(teFlyout.style, {
                position: 'absolute', left: 'calc(100% + 8px)', top: '0',
                transform: 'scale(0.95)',
                display: 'flex', flexDirection: 'column', gap: '3px',
                transition: 'all 0.15s ease-out',
                opacity: '0', pointerEvents: 'none', zIndex: '21',
            });

            let teFlyoutOpen = false;
            let teSubPage = 0;
            const TE_PER_PAGE = 4;

            function _escHTML(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }

            function renderThemeFlyout() {
                teFlyout.innerHTML = '';
                if (!_themeEditor) return;
                const themes = _themeEditor.loadThemes();
                const activeName = _themeEditor.getActiveThemeName();
                const allNames = Object.keys(themes).sort((a, b) => {
                    const pa = a === 'Default' ? 0 : a === 'Default Dark' ? 1 : 2;
                    const pb = b === 'Default' ? 0 : b === 'Default Dark' ? 1 : 2;
                    return pa !== pb ? pa - pb : a.localeCompare(b);
                });
                const totalPages = Math.max(1, Math.ceil(allNames.length / TE_PER_PAGE));
                if (teSubPage >= totalPages) teSubPage = 0;
                const start = teSubPage * TE_PER_PAGE;
                const page = allNames.slice(start, start + TE_PER_PAGE);

                for (const name of page) {
                    const theme = themes[name];
                    const isActive = name === activeName;
                    const btn = document.createElement('button');
                    Object.assign(btn.style, {
                        display: 'flex', alignItems: 'center', gap: '6px',
                        minWidth: '130px', maxWidth: '190px', padding: '4px 8px',
                        background: isActive ? C.teActiveBg : C.teInactiveBg,
                        boxShadow: C.shadow,
                        borderRadius: '6px', border: 'none', cursor: 'pointer',
                        fontSize: '11px', fontWeight: isActive ? '700' : '500',
                        color: isActive ? C.teBtnText : C.flyText,
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                        transition: 'background .12s', height: '28px',
                    });
                    const bgColor = theme.overrides?.['background::background-color'] || theme.overrides?.['water::fill-color'] || '#808080';
                    btn.innerHTML = '<span style="width:14px;height:14px;border-radius:3px;border:1px solid rgba(0,0,0,.15);flex-shrink:0;background:' + bgColor + '"></span>' + _escHTML(name);
                    btn.title = name;
                    btn.addEventListener('click', async (e) => {
                        e.stopPropagation();
                        await _themeEditor.applyThemeByName(name);
                        renderThemeFlyout();
                    });
                    btn.addEventListener('mouseenter', () => { if (!isActive) btn.style.background = C.teHover; });
                    btn.addEventListener('mouseleave', () => { if (!isActive) btn.style.background = C.teInactiveBg; });
                    teFlyout.appendChild(btn);
                }

                if (totalPages > 1) {
                    const nav = document.createElement('button');
                    Object.assign(nav.style, {
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        minWidth: '130px', maxWidth: '190px', padding: '3px 8px',
                        background: C.navBg, boxShadow: C.shadow,
                        borderRadius: '6px', border: 'none', cursor: 'pointer',
                        fontSize: '10px', color: C.navText, height: '22px', transition: 'background .12s',
                    });
                    nav.textContent = '▸ ' + (teSubPage + 1) + '/' + totalPages;
                    nav.title = 'Next page';
                    nav.addEventListener('click', (e) => {
                        e.stopPropagation();
                        teSubPage = (teSubPage + 1) % totalPages;
                        renderThemeFlyout();
                    });
                    teFlyout.appendChild(nav);
                }

                // "Editor" button to open full modal
                const editorBtn = document.createElement('button');
                Object.assign(editorBtn.style, {
                    display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '4px',
                    minWidth: '130px', maxWidth: '190px', padding: '4px 8px',
                    background: C.teBtnBg, boxShadow: C.shadow,
                    borderRadius: '6px', border: 'none', cursor: 'pointer',
                    fontSize: '11px', fontWeight: '600', color: C.teBtnText,
                    whiteSpace: 'nowrap', height: '28px', transition: 'filter .12s',
                });
                editorBtn.textContent = '⚙️ Editor';
                editorBtn.title = 'Open full Theme Editor';
                editorBtn.addEventListener('mouseenter', () => { editorBtn.style.filter = 'brightness(1.1)'; });
                editorBtn.addEventListener('mouseleave', () => { editorBtn.style.filter = ''; });
                editorBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    closeDropdown();
                    closeFlyout();
                    if (_themeEditor) _themeEditor.toggleModal();
                });
                teFlyout.appendChild(editorBtn);
            }

            function closeFlyout() {
                teFlyoutOpen = false;
                teFlyout.style.opacity = '0';
                teFlyout.style.pointerEvents = 'none';
                teFlyout.style.transform = 'scale(0.95)';
            }

            _flyoutClosers.push(closeFlyout);

            teBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                teCollapsePill();
                const wasOpen = teFlyoutOpen;
                closeAllFlyouts();
                if (!wasOpen) {
                    teFlyoutOpen = true;
                    renderThemeFlyout();
                    teFlyout.style.opacity = '1';
                    teFlyout.style.pointerEvents = 'auto';
                    teFlyout.style.transform = 'scale(1)';
                }
            });

            document.addEventListener('click', (e) => {
                if (teFlyoutOpen && !teGroup.contains(e.target)) closeFlyout();
            });

            teGroup.appendChild(teBtn);
            teGroup.appendChild(teFlyout);
            dropdown.appendChild(teGroup);
        }

        // Map Markers button (only if enabled)
        if (_settings.mapMarkers) {
            dropdown.appendChild(makeSubBtn('📌', 'Map Markers', () => {
                if (_mapMarkers) _mapMarkers.openModal();
            }));
        }

        // Settings button (always visible)
        dropdown.appendChild(makeSubBtn('⚙️', 'Settings...', createSettingsModal));

        // Changelog button (always visible)
        dropdown.appendChild(makeSubBtn('📋', 'Changelog', showChangelog));

        // Debug Logs button — only shown when Enable Debugging is on
        if (_settings.enableDebug) {
            const _dbgBtn = makeSubBtn('🔶', `Debug Logs (${dbgCount()})`, () => {
                if (_debugLog.length === 0) {
                    alert('[GeoPixelcons++] No debug logs recorded yet. Errors will appear here when they occur.');
                } else {
                    dbgExport();
                }
            });
            _dbgBtn.id = 'gpc-debug-dropdown-btn';
            dropdown.appendChild(_dbgBtn);
        }

        group.appendChild(mainBtn);
        group.appendChild(dropdown);

        // Ensure GeoPixelcons++ is always after GeoPixels++ in controls-left
        function positionAfterGeoPixelsPP() {
            const gppGroup = controlsLeft.querySelector('#geopixels-plusplus')?.closest('.relative');
            if (gppGroup && gppGroup.nextSibling !== group) {
                gppGroup.after(group);
                return true;
            }
            return false;
        }

        controlsLeft.appendChild(group);
        if (!positionAfterGeoPixelsPP()) {
            // GeoPixels++ may not be loaded yet — watch for it
            const obs = new MutationObserver(() => {
                if (positionAfterGeoPixelsPP()) obs.disconnect();
            });
            obs.observe(controlsLeft, { childList: true, subtree: true });
            // Stop watching after 30s to avoid leaks
            setTimeout(() => obs.disconnect(), 30000);
        }
    });

    // ============================================================
    //  FEATURE MODULES
    //  Each wrapped in a try/catch to set status
    // ============================================================


    // ============================================================
    //  FEATURE: Ghost Palette Color Search [ghostPaletteSearch]
    // ============================================================
    if (_settings.ghostPaletteSearch) {
        try {
            (function _init_ghostPaletteSearch() {

    // Wait for the ghostColorPalette to exist
    function waitForElement(selector, callback) {
        const element = document.querySelector(selector);
        if (element) {
            callback(element);
        } else {
            setTimeout(() => waitForElement(selector, callback), 500);
        }
    }

    // Add CSS for the glow effect
    const style = document.createElement('style');
    style.textContent = `
        .color-search-glow {
            box-shadow: 0 0 8px 2px rgba(255, 215, 0, 0.8) !important;
            animation: pulse-glow 1.5s ease-in-out infinite;
        }

        @keyframes pulse-glow {
            0%, 100% {
                box-shadow: 0 0 8px 2px rgba(255, 215, 0, 0.8) !important;
            }
            50% {
                box-shadow: 0 0 12px 3px rgba(255, 215, 0, 1) !important;
            }
        }

        .color-search-container {
            margin-bottom: 12px;
            padding: 12px;
            background: var(--color-gray-200, #f9fafb);
            border-radius: 8px;
            border: 1px solid var(--color-gray-300, #e5e7eb);
        }

        .color-search-input {
            width: 100%;
            padding: 8px 12px;
            border: 2px solid var(--color-gray-400, #d1d5db);
            border-radius: 6px;
            font-size: 14px;
            transition: border-color 0.2s;
            background: var(--color-gray-100, #fff);
            color: var(--color-gray-900, inherit);
        }

        .color-search-input:focus {
            outline: none;
            border-color: #3b82f6;
        }

        .color-search-input::placeholder {
            color: var(--color-gray-600, #9ca3af);
        }

        .hide-unmatched-checkbox {
            display: flex;
            align-items: center;
            gap: 6px;
            margin-top: 8px;
            font-size: 14px;
            color: var(--color-gray-800, #374151);
        }

        .hide-unmatched-checkbox input {
            width: 16px;
            height: 16px;
            cursor: pointer;
        }

        .hide-unmatched-checkbox label {
            cursor: pointer;
            user-select: none;
        }
    `;
    document.head.appendChild(style);

    // ── Sync Ghost With Selected Color ────────────────────────────────
    // State and logic live outside waitForElement so they work independently
    // of whether a ghost image is currently loaded.
    let _syncGhostEnabled = false;

    function applyAutoEnableSelectedColor(targetHex) {
        const paletteDiv = document.getElementById('ghostColorPalette');
        if (!paletteDiv) return;
        const colorButtons = paletteDiv.querySelectorAll('button[data-color-rgba]');
        if (!colorButtons.length) return;
        const normalizedTarget = targetHex.toUpperCase();
        let targetFound = false;
        const toEnable = [], toDisable = [];
        colorButtons.forEach(btn => {
            const rgba = btn.dataset.colorRgba;
            if (!rgba) return;
            let hex = '';
            const firstLine = (btn.getAttribute('title') || '').split(/[\r\n]+/)[0].trim().toUpperCase();
            if (/^#[0-9A-F]{6}$/.test(firstLine)) {
                hex = firstLine;
            } else {
                const m = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
                if (m) hex = '#' + [m[1], m[2], m[3]]
                    .map(n => parseInt(n).toString(16).toUpperCase().padStart(2, '0')).join('');
            }
            const isEnabled = btn.classList.contains('border-blue-500');
            const shouldBeEnabled = hex === normalizedTarget;
            if (shouldBeEnabled) targetFound = true;
            if (shouldBeEnabled && !isEnabled) toEnable.push(rgba);
            else if (!shouldBeEnabled && isEnabled) toDisable.push(rgba);
        });
        if (!targetFound) return;
        if (!toEnable.length && !toDisable.length) return;
        const script = document.createElement('script');
        script.textContent = `(function(en,dis){` +
            `en.forEach(r=>ghostActivePaletteColors.add(r));` +
            `dis.forEach(r=>ghostActivePaletteColors.delete(r));` +
            `if(typeof updateColorPaletteUI==='function')updateColorPaletteUI();` +
            `if(typeof regenerateGhostCanvas==='function')regenerateGhostCanvas();` +
            `})(${JSON.stringify(toEnable)},${JSON.stringify(toDisable)});`;
        document.head.appendChild(script);
        script.remove();
    }

    // Patch changeColor once — fires a custom event because pixelColor is a `let`
    // variable in page scope and is not reachable via window.pixelColor.
    (function patchChangeColor() {
        const script = document.createElement('script');
        script.textContent = `(function(){
if(window.__gpc_colorPatchApplied)return;
window.__gpc_colorPatchApplied=true;
var _orig=window.changeColor;
if(typeof _orig!=='function')return;
window.changeColor=function(color){
    _orig.call(this,color);
    document.dispatchEvent(new CustomEvent('gpc:pixelColorChanged',{detail:color}));
};
})();`;
        document.head.appendChild(script);
        script.remove();
    })();

    document.addEventListener('gpc:pixelColorChanged', (e) => {
        if (!_syncGhostEnabled) return;
        applyAutoEnableSelectedColor(e.detail);
    });

    // ── Inject toggle button into #imageGroupDropdown ─────────────────
    (function injectSyncGhostBtn() {
        const syncBtnStyle = document.createElement('style');
        syncBtnStyle.textContent =
            '#gpc-sync-ghost-btn[data-active="true"]{background:#dcfce7!important;}' +
            '#gpc-sync-ghost-btn[data-active="true"]:hover{background:#bbf7d0!important;}';
        document.head.appendChild(syncBtnStyle);

        function tryInject() {
            const dropdown = document.getElementById('imageGroupDropdown');
            if (!dropdown) { setTimeout(tryInject, 500); return; }
            if (document.getElementById('gpc-sync-ghost-btn')) return;
            const btn = document.createElement('button');
            btn.id = 'gpc-sync-ghost-btn';
            btn.className = 'w-10 h-10 bg-white shadow rounded-full flex items-center justify-center hover:bg-gray-100 cursor-pointer';
            if (!_settings.showSyncGhostBtn) btn.classList.add('hidden');
            btn.title = 'Sync Ghost With Selected Color';
            btn.textContent = '\u267B\uFE0F';
            btn.addEventListener('click', () => {
                _syncGhostEnabled = !_syncGhostEnabled;
                btn.dataset.active = _syncGhostEnabled ? 'true' : 'false';
            });
            dropdown.appendChild(btn);
        }
        tryInject();
    })();

    // Main functionality
    waitForElement('#ghostColorPalette', (paletteDiv) => {
        // Create search container
        const searchContainer = document.createElement('div');
        searchContainer.className = 'color-search-container';

        // Create search input
        const searchInput = document.createElement('input');
        searchInput.type = 'text';
        searchInput.className = 'color-search-input';
        searchInput.placeholder = 'Search color(s) (comma separated)';

        // Create checkbox container
        const checkboxContainer = document.createElement('div');
        checkboxContainer.className = 'hide-unmatched-checkbox';
        Object.assign(checkboxContainer.style, { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' });

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = 'hideUnmatchedColors';

        const checkboxLabel = document.createElement('label');
        checkboxLabel.htmlFor = 'hideUnmatchedColors';
        checkboxLabel.textContent = 'Hide unmatched colors';

        // ── "Hide completed colors" checkbox ──────────────────────────
        const hideCompletedCheckbox = document.createElement('input');
        hideCompletedCheckbox.type = 'checkbox';
        hideCompletedCheckbox.id = 'hideCompletedColors';

        const hideCompletedLabel = document.createElement('label');
        hideCompletedLabel.htmlFor = 'hideCompletedColors';
        hideCompletedLabel.textContent = 'Hide completed colors';

        // A color is "completed" when the geopixels++ refresh badge sets the title
        // to a multiline string whose last non-empty line is exactly "100%".
        function isColorCompleted(btn) {
            const title = btn.getAttribute('title') || '';
            const lines = title.split(/\n/).map(l => l.trim()).filter(l => l.length > 0);
            return lines[lines.length - 1] === '100%';
        }

        // ── "Enable filtered" button ──────────────────────────────────
        const enableFilteredBtn = document.createElement('button');
        enableFilteredBtn.textContent = 'Enable filtered';
        enableFilteredBtn.title = 'Enable colors matching the search and disable all others in the ghost palette';
        Object.assign(enableFilteredBtn.style, {
            padding: '3px 10px',
            background: '#3b82f6', color: '#fff', border: 'none',
            borderRadius: '5px', cursor: 'pointer', fontSize: '12px',
            fontWeight: '700', whiteSpace: 'nowrap',
        });
        enableFilteredBtn.onmouseenter = () => enableFilteredBtn.style.opacity = '0.82';
        enableFilteredBtn.onmouseleave = () => enableFilteredBtn.style.opacity = '1';
        enableFilteredBtn.onclick = () => applyEnabledFilteredColors();

        // Row for "Enable filtered" — sits above the search box, near the other enable/disable buttons
        const filteredRow = document.createElement('div');
        filteredRow.className = 'gpc-filtered-row';
        filteredRow.style.cssText = 'display:flex;justify-content:flex-start;padding:0 0 6px 0;';
        filteredRow.appendChild(enableFilteredBtn);

        checkboxContainer.appendChild(checkbox);
        checkboxContainer.appendChild(checkboxLabel);
        checkboxContainer.appendChild(hideCompletedCheckbox);
        checkboxContainer.appendChild(hideCompletedLabel);

        // Assemble search container
        searchContainer.appendChild(searchInput);
        searchContainer.appendChild(checkboxContainer);

        // Insert before the palette: filteredRow first, then searchContainer, then paletteDiv
        paletteDiv.parentNode.insertBefore(searchContainer, paletteDiv);
        paletteDiv.parentNode.insertBefore(filteredRow, searchContainer);

        // Search and highlight function
        function performSearch() {
            const searchValue = searchInput.value.trim();
            const hideUnmatched = checkbox.checked;
            const hideCompleted = hideCompletedCheckbox.checked;

            // Get all color buttons in the palette
            const colorButtons = paletteDiv.querySelectorAll('[title^="#"]');

            // Clear all previous glows and hidden states
            colorButtons.forEach(btn => {
                btn.classList.remove('color-search-glow');
                btn.classList.remove('hidden');
            });

            // If no active filters, exit early
            if (!searchValue && !hideCompleted) {
                return;
            }

            // Parse search terms (comma separated)
            const searchTerms = searchValue
                .split(',')
                .map(term => term.trim().toUpperCase())
                .filter(term => term.length > 0);

            // Find matching buttons (only relevant when there's a search term)
            const matchingButtons = new Set();

            if (searchTerms.length > 0) {
                colorButtons.forEach(btn => {
                    const colorTitle = btn.getAttribute('title');
                    if (!colorTitle) return;
                    const colorHex = colorTitle.toUpperCase();
                    if (searchTerms.some(term => colorHex.includes(term))) {
                        btn.classList.add('color-search-glow');
                        matchingButtons.add(btn);
                    }
                });
            }

            // Apply visibility filters
            colorButtons.forEach(btn => {
                const unmatchedHide = hideUnmatched && searchTerms.length > 0 && !matchingButtons.has(btn);
                const completedHide = hideCompleted && isColorCompleted(btn);
                if (unmatchedHide || completedHide) {
                    btn.classList.add('hidden');
                }
            });
        }

        // ── Enable filtered: enable matched colors, disable unmatched ──
        function applyEnabledFilteredColors() {
            const colorButtons = paletteDiv.querySelectorAll('button[data-color-rgba]');
            if (!colorButtons.length) {
                (typeof showAlert === 'function' ? showAlert : alert)('No ghost palette colors found', 'Make sure a ghost image is loaded.');
                return;
            }
            const searchValue = searchInput.value.trim();
            if (!searchValue) {
                (typeof showAlert === 'function' ? showAlert : alert)('Enable Filtered', 'Enter a search term first.');
                return;
            }
            const searchTerms = searchValue
                .split(',')
                .map(t => t.trim().toUpperCase())
                .filter(t => t.length > 0);
            if (searchTerms.length === 0) return;

            const w = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;

            // Disable "Show All" if active
            const showAllToggle = document.getElementById('disableColorFilterToggle');
            if (showAllToggle && showAllToggle.checked) {
                showAllToggle.checked = false;
                if (typeof w.handleColorFilterToggle === 'function') w.handleColorFilterToggle();
            }

            // Derive the sets of rgbas to enable and disable from DOM state.
            // ghostActivePaletteColors is a `let` variable — NOT on window — so it cannot
            // be accessed from userscript scope. We collect the desired changes here and
            // then inject a single <script> that runs in page context, mutates the Set
            // directly, and calls regenerateGhostCanvas() exactly once.
            const toEnable = [], toDisable = [];
            colorButtons.forEach(btn => {
                const rgba = btn.dataset.colorRgba;
                if (!rgba) return;
                let hex = '';
                const firstLine = (btn.getAttribute('title') || '').split(/[\r\n]+/)[0].trim().toUpperCase();
                if (/^#[0-9A-F]{6}$/.test(firstLine)) {
                    hex = firstLine;
                } else {
                    const m = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
                    if (m) hex = '#' + [m[1], m[2], m[3]]
                        .map(n => parseInt(n).toString(16).toUpperCase().padStart(2, '0')).join('');
                }
                const shouldBeEnabled = searchTerms.some(t => hex.includes(t));
                const isEnabled = btn.classList.contains('border-blue-500');
                if (shouldBeEnabled && !isEnabled) toEnable.push(rgba);
                else if (!shouldBeEnabled && isEnabled) toDisable.push(rgba);
            });

            if (!toEnable.length && !toDisable.length) return;

            // Single page-context injection: mutate ghostActivePaletteColors and redraw once.
            const script = document.createElement('script');
            script.textContent = `(function(en,dis){` +
                `en.forEach(r=>ghostActivePaletteColors.add(r));` +
                `dis.forEach(r=>ghostActivePaletteColors.delete(r));` +
                `if(typeof updateColorPaletteUI==='function')updateColorPaletteUI();` +
                `if(typeof regenerateGhostCanvas==='function')regenerateGhostCanvas();` +
                `})(${JSON.stringify(toEnable)},${JSON.stringify(toDisable)});`;
            document.head.appendChild(script);
            script.remove();
        }

        // Add event listeners
        searchInput.addEventListener('input', performSearch);
        checkbox.addEventListener('change', performSearch);
        hideCompletedCheckbox.addEventListener('change', performSearch);

        // Track the number of color buttons to detect palette resets
        let previousButtonCount = 0;

        // Also watch for dynamically added buttons
        const observer = new MutationObserver(() => {
            const currentButtonCount = paletteDiv.querySelectorAll('[title^="#"]').length;

            // If the button count changed significantly, it's likely a new image
            // Clear the search field to reset the UI
            if (previousButtonCount > 0 && Math.abs(currentButtonCount - previousButtonCount) > 5) {
                searchInput.value = '';
                checkbox.checked = false;
                hideCompletedCheckbox.checked = false;
            }

            previousButtonCount = currentButtonCount;
            performSearch();
        });

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

        // Initialize the button count
        previousButtonCount = paletteDiv.querySelectorAll('[title^="#"]').length;
    });
            })();
            _featureStatus.ghostPaletteSearch = 'ok';
            console.log('[GeoPixelcons++] ✅ Ghost Palette Color Search loaded');
        } catch (err) {
            _featureStatus.ghostPaletteSearch = 'error';
            dbgPush(`Ghost Palette Color Search init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Ghost Palette Color Search' });
            console.error('[GeoPixelcons++] ❌ Ghost Palette Color Search failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Paint Menu Controls [hidePaintMenu]
    // ============================================================
    if (_settings.hidePaintMenu) {
        try {
            (function _init_hidePaintMenu() {

    const init = () => {
        const bottomControls = document.getElementById('bottomControls');
        const energyDisplay = document.getElementById('currentEnergyDisplay');

        if (!bottomControls || !energyDisplay) {
            console.log('Elements not found, retrying...');
            setTimeout(init, 500);
            return;
        }

        // --- 1. CONFIGURATION & STATE ---
        let isCollapsed = false;
        let isTop = false; // whether panel is docked to top
        let dragOffsetX = 0; // px offset from center (persisted)
        const DRAG_STORAGE_KEY = 'gpc-paint-drag-offset';
        const TOP_STORAGE_KEY = 'gpc-paint-is-top';
        try { dragOffsetX = parseFloat(localStorage.getItem(DRAG_STORAGE_KEY)) || 0; } catch {}
        try { isTop = localStorage.getItem(TOP_STORAGE_KEY) === 'true'; } catch {}

        // --- 2. CONTAINER STYLING ---
        // Remove conflicting Tailwind classes
        bottomControls.classList.remove('-translate-x-1/2');
        bottomControls.classList.remove('left-1/2');

        // Keep the original width behavior but add positioning control
        bottomControls.style.position = 'fixed';
        bottomControls.style.bottom = '1rem';
        bottomControls.style.transition = 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)';
        // Remove any width override to preserve original responsive behavior
        bottomControls.style.width = '';
        bottomControls.style.maxWidth = '';

        // Start centered (preserve original behavior)
        bottomControls.style.left = '50%';
        bottomControls.style.transform = 'translateX(-50%)';

        // --- 3. CREATE UI ELEMENTS ---

        // A. Top bar container (holds drag handle, collapse button, reset button)
        const topBar = document.createElement('div');
        topBar.style.cssText = `
            position: absolute;
            top: -24px;
            left: 0;
            right: 0;
            height: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 20;
            pointer-events: none;
        `;

        // B. Collapse Button (first, on the left)
        const toggleBtn = document.createElement('button');
        toggleBtn.innerHTML = '▼';
        toggleBtn.id = 'gpc-hide-paint-toggle';
        toggleBtn.style.cssText = `
            pointer-events: auto;
            width: 28px;
            height: 24px;
            border-bottom: none;
            border-radius: 8px 8px 0 0;
            cursor: pointer;
            font-size: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
        `;
        toggleBtn.className = 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-500 dark:text-gray-300';

        // C. Drag handle bar (to the right of collapse)
        const dragBar = document.createElement('div');
        dragBar.id = 'gpc-paint-drag-bar';
        dragBar.style.cssText = `
            pointer-events: auto;
            cursor: grab;
            height: 24px;
            width: 28px;
            border-radius: 8px 8px 0 0;
            display: flex;
            align-items: center;
            justify-content: center;
            user-select: none;
            border-bottom: none;
            margin-left: 2px;
        `;
        dragBar.className = 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-400 dark:text-gray-500';
        dragBar.innerHTML = '<span style="font-size:10px;pointer-events:none;">⋮⋮</span>';

        // D. Reset position button
        const resetBtn = document.createElement('button');
        resetBtn.id = 'gpc-paint-reset-pos';
        resetBtn.title = 'Reset position to center';
        resetBtn.innerHTML = '↺';
        resetBtn.style.cssText = `
            pointer-events: auto;
            width: 28px;
            height: 24px;
            border-bottom: none;
            border-radius: 8px 8px 0 0;
            cursor: pointer;
            font-size: 13px;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-left: 2px;
        `;
        resetBtn.className = 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-500 dark:text-gray-300';

        // E. Flip top/bottom button
        const flipBtn = document.createElement('button');
        flipBtn.id = 'gpc-paint-flip-pos';
        flipBtn.title = 'Move to top / bottom';
        flipBtn.innerHTML = isTop ? '⬇' : '⬆';
        flipBtn.style.cssText = `
            pointer-events: auto;
            width: 28px;
            height: 24px;
            border-bottom: none;
            border-radius: 8px 8px 0 0;
            cursor: pointer;
            font-size: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-left: 2px;
        `;
        flipBtn.className = 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-500 dark:text-gray-300';

        // F. Close (switch to inspect mode) button — next to energy display
        const closeBtn = document.createElement('button');
        closeBtn.id = 'gpc-paint-close';
        closeBtn.title = 'Switch to Inspect Mode';
        closeBtn.innerHTML = '✕';
        closeBtn.style.cssText = `
            width: 24px;
            height: 24px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 13px;
            font-weight: 700;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            margin-left: 6px;
            flex-shrink: 0;
            vertical-align: middle;
        `;
        closeBtn.className = 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-500 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600';
        closeBtn.addEventListener('click', () => {
            const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
            if (typeof _pw.togglePrimaryMode === 'function') _pw.togglePrimaryMode();
        });
        energyDisplay.parentElement.style.display = 'flex';
        energyDisplay.parentElement.style.alignItems = 'center';
        energyDisplay.insertAdjacentElement('afterend', closeBtn);

        topBar.appendChild(toggleBtn);
        topBar.appendChild(dragBar);
        topBar.appendChild(resetBtn);
        topBar.appendChild(flipBtn);
        bottomControls.appendChild(topBar);

        // --- G. Compact paint overflow: move close + brushes into topBar ---
        if (_settings.compactPaintOverflow) {
            // Create compact close button for the topBar
            const compactCloseBtn = document.createElement('button');
            compactCloseBtn.id = 'gpc-compact-close';
            compactCloseBtn.title = 'Switch to Inspect Mode';
            compactCloseBtn.innerHTML = '✕';
            compactCloseBtn.style.cssText = `
                pointer-events: auto;
                width: 28px;
                height: 24px;
                border-bottom: none;
                border-radius: 8px 8px 0 0;
                cursor: pointer;
                font-size: 13px;
                font-weight: 700;
                display: flex;
                align-items: center;
                justify-content: center;
                margin-left: 2px;
            `;
            compactCloseBtn.className = 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-500 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600';
            compactCloseBtn.addEventListener('click', () => {
                const _pw2 = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
                if (typeof _pw2.togglePrimaryMode === 'function') _pw2.togglePrimaryMode();
            });

            // Create compact brush button for the topBar
            const compactBrushBtn = document.createElement('button');
            compactBrushBtn.id = 'gpc-compact-brush';
            compactBrushBtn.title = 'Toggle saved brushes';
            compactBrushBtn.innerHTML = '🖌️';
            compactBrushBtn.style.cssText = `
                pointer-events: auto;
                width: 28px;
                height: 24px;
                border-bottom: none;
                border-radius: 8px 8px 0 0;
                cursor: pointer;
                font-size: 13px;
                display: none;
                align-items: center;
                justify-content: center;
                margin-left: 2px;
            `;
            compactBrushBtn.className = 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-500 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600';
            compactBrushBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                const realToggle = document.getElementById('brush-swap-toggle');
                if (realToggle) realToggle.click();
                // Reposition dropdown near this compact button using a rAF so layout is settled
                requestAnimationFrame(() => {
                    const dropdown = document.getElementById('brush-swap-dropdown');
                    if (!dropdown) return;
                    // Move dropdown to body so transforms on ancestors don't affect fixed positioning
                    if (dropdown.parentElement !== document.body) {
                        document.body.appendChild(dropdown);
                    }
                    const btnRect = compactBrushBtn.getBoundingClientRect();
                    const paintIsTop = localStorage.getItem('gpc-paint-is-top') === 'true';
                    dropdown.style.position = 'fixed';
                    dropdown.style.right = 'auto';
                    dropdown.style.margin = '0';
                    // Align dropdown left edge with button left edge
                    dropdown.style.left = btnRect.left + 'px';
                    if (paintIsTop) {
                        dropdown.style.top = (btnRect.bottom + 4) + 'px';
                        dropdown.style.bottom = 'auto';
                    } else {
                        dropdown.style.top = 'auto';
                        dropdown.style.bottom = (window.innerHeight - btnRect.top + 4) + 'px';
                    }
                });
            });

            // Forward scroll-to-swap from compact button to the real toggle
            compactBrushBtn.addEventListener('wheel', (e) => {
                const realToggle = document.getElementById('brush-swap-toggle');
                if (realToggle) {
                    realToggle.dispatchEvent(new WheelEvent('wheel', {
                        deltaY: e.deltaY,
                        clientX: e.clientX,
                        clientY: e.clientY,
                        bubbles: false
                    }));
                }
                e.preventDefault();
            }, { passive: false });

            topBar.appendChild(compactBrushBtn);
            topBar.appendChild(compactCloseBtn);

            // Position X button absolutely to the right so it doesn't shift the centered group
            compactCloseBtn.style.position = 'absolute';
            compactCloseBtn.style.right = '20px';
            compactCloseBtn.style.marginLeft = '0';

            // Hide the inline close button immediately
            closeBtn.style.display = 'none';

            // Hide brush-swap-toggle when it appears, and show compact brush btn
            function hideBrushToggle() {
                const brushToggle = document.getElementById('brush-swap-toggle');
                if (brushToggle) {
                    // Hide the button visually but keep the wrapper layout intact
                    // so the dropdown can still position itself correctly
                    brushToggle.style.visibility = 'hidden';
                    brushToggle.style.width = '0';
                    brushToggle.style.padding = '0';
                    brushToggle.style.margin = '0';
                    brushToggle.style.border = 'none';
                    brushToggle.style.overflow = 'hidden';
                    compactBrushBtn.style.display = 'flex';
                    return true;
                }
                return false;
            }

            // Try immediately, and also watch for it being added later by paintBrushSwap
            if (!hideBrushToggle()) {
                const compactObserver = new MutationObserver(() => {
                    if (hideBrushToggle()) compactObserver.disconnect();
                });
                compactObserver.observe(bottomControls, { childList: true, subtree: true });
                // Safety cleanup
                setTimeout(() => compactObserver.disconnect(), 30000);
            }
        }

        // --- "Paint Here" button injected into hoverInfo ---
        function injectPaintHereButton() {
            if (document.getElementById('gpc-paint-here-btn')) return;
            const hoverInfo = document.getElementById('hoverInfo');
            if (!hoverInfo) return;

            const paintBtn = document.createElement('button');
            paintBtn.id = 'gpc-paint-here-btn';
            paintBtn.className = 'w-full bg-green-500 text-white py-2 px-4 rounded-lg hover:bg-green-600 transition-colors flex items-center justify-center gap-2 cursor-pointer';
            paintBtn.style.marginTop = '8px';
            paintBtn.innerHTML = '🎨 Paint Here';
            paintBtn.addEventListener('click', () => {
                const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
                if (typeof _pw.togglePrimaryMode === 'function') _pw.togglePrimaryMode();
            });

            // Insert after the Share Location button's parent div
            const shareBtn = document.getElementById('shareLocationBtn');
            const shareContainer = shareBtn?.parentElement;
            if (shareContainer) {
                shareContainer.insertAdjacentElement('afterend', paintBtn);
            } else {
                hoverInfo.appendChild(paintBtn);
            }
        }

        // Observe for hoverInfo appearing
        const hoverObserver = new MutationObserver(() => injectPaintHereButton());
        hoverObserver.observe(document.body, { childList: true, subtree: true });
        injectPaintHereButton();

        // Identify the two main content divs for reordering
        // The first child div is the controls row (buttons, energy, etc.)
        // The second is .control-container-colors (color swatches)
        const innerWrapper = bottomControls.querySelector(':scope > div');
        const controlsRow = innerWrapper
            ? innerWrapper.querySelector(':scope > .w-full.flex')
            : null;
        const colorsDiv = innerWrapper
            ? innerWrapper.querySelector(':scope > .control-container-colors')
            : null;
        const swapParent = controlsRow?.parentElement;

        // --- 4. LOGIC ENGINE ---

        const updateState = () => {
            const COLLAPSE_OFFSET = 48;

            // Vertical docking
            if (isTop) {
                bottomControls.style.bottom = 'auto';
                bottomControls.style.top = '1rem';

                // Reorder: colors first, controls second (buttons closer to map edge)
                if (swapParent && colorsDiv && controlsRow) {
                    swapParent.insertBefore(colorsDiv, controlsRow);
                }

                // Button bar goes BELOW the panel when docked top
                topBar.style.top = 'auto';
                topBar.style.bottom = '-24px';
                [toggleBtn, dragBar, resetBtn, flipBtn].forEach(el => {
                    el.style.borderRadius = '0 0 8px 8px';
                    el.style.borderBottom = '';
                    el.style.borderTop = 'none';
                });
                // Also style compact overflow buttons if they exist
                const compactClose1 = document.getElementById('gpc-compact-close');
                const compactBrush1 = document.getElementById('gpc-compact-brush');
                if (compactClose1) { compactClose1.style.borderRadius = '0 0 8px 8px'; compactClose1.style.borderBottom = ''; compactClose1.style.borderTop = 'none'; }
                if (compactBrush1) { compactBrush1.style.borderRadius = '0 0 8px 8px'; compactBrush1.style.borderBottom = ''; compactBrush1.style.borderTop = 'none'; }
            } else {
                bottomControls.style.top = 'auto';
                bottomControls.style.bottom = '1rem';

                // Restore original order: controls first, colors second
                if (swapParent && colorsDiv && controlsRow) {
                    swapParent.insertBefore(controlsRow, colorsDiv);
                }

                // Button bar goes ABOVE the panel when docked bottom
                topBar.style.bottom = 'auto';
                topBar.style.top = '-24px';
                [toggleBtn, dragBar, resetBtn, flipBtn].forEach(el => {
                    el.style.borderRadius = '8px 8px 0 0';
                    el.style.borderBottom = 'none';
                    el.style.borderTop = '';
                });
                // Also style compact overflow buttons if they exist
                const compactClose2 = document.getElementById('gpc-compact-close');
                const compactBrush2 = document.getElementById('gpc-compact-brush');
                if (compactClose2) { compactClose2.style.borderRadius = '8px 8px 0 0'; compactClose2.style.borderBottom = 'none'; compactClose2.style.borderTop = ''; }
                if (compactBrush2) { compactBrush2.style.borderRadius = '8px 8px 0 0'; compactBrush2.style.borderBottom = 'none'; compactBrush2.style.borderTop = ''; }
            }

            const yTransform = isCollapsed
                ? (isTop ? `translateY(calc(-100% + ${COLLAPSE_OFFSET}px))` : `translateY(calc(100% - ${COLLAPSE_OFFSET}px))`)
                : 'translateY(0)';

            bottomControls.style.left = '50%';
            bottomControls.style.right = 'auto';
            bottomControls.style.transform = `translateX(calc(-50% + ${dragOffsetX}px)) ${yTransform}`;
            toggleBtn.innerHTML = isCollapsed
                ? (isTop ? '▼' : '▲')
                : (isTop ? '▲' : '▼');
            flipBtn.innerHTML = isTop ? '⬇' : '⬆';
        };

        // --- 5. DRAG LOGIC ---

        let isDragging = false;
        let dragStartX = 0;
        let dragStartOffset = 0;

        function onDragStart(e) {
            isDragging = true;
            dragStartX = (e.touches ? e.touches[0].clientX : e.clientX);
            dragStartOffset = dragOffsetX;
            dragBar.style.cursor = 'grabbing';
            bottomControls.style.transition = 'none'; // disable animation while dragging
            e.preventDefault();
        }
        function onDragMove(e) {
            if (!isDragging) return;
            const clientX = (e.touches ? e.touches[0].clientX : e.clientX);
            dragOffsetX = dragStartOffset + (clientX - dragStartX);
            // Clamp so the panel stays at least partially on screen
            const halfW = bottomControls.offsetWidth / 2;
            const maxOff = window.innerWidth / 2 - 60;
            dragOffsetX = Math.max(-maxOff, Math.min(maxOff, dragOffsetX));
            updateState();
        }
        function onDragEnd() {
            if (!isDragging) return;
            isDragging = false;
            dragBar.style.cursor = 'grab';
            bottomControls.style.transition = 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)';
            localStorage.setItem(DRAG_STORAGE_KEY, String(dragOffsetX));
        }

        dragBar.addEventListener('mousedown', onDragStart);
        document.addEventListener('mousemove', onDragMove);
        document.addEventListener('mouseup', onDragEnd);
        dragBar.addEventListener('touchstart', onDragStart, { passive: false });
        document.addEventListener('touchmove', onDragMove, { passive: false });
        document.addEventListener('touchend', onDragEnd);

        // --- 6. EVENT LISTENERS ---

        toggleBtn.addEventListener('click', () => {
            isCollapsed = !isCollapsed;
            updateState();
        });

        resetBtn.addEventListener('click', () => {
            dragOffsetX = 0;
            localStorage.removeItem(DRAG_STORAGE_KEY);
            updateState();
        });

        flipBtn.addEventListener('click', () => {
            isTop = !isTop;
            isCollapsed = false; // expand when flipping
            localStorage.setItem(TOP_STORAGE_KEY, String(isTop));
            updateState();
        });

        // Initialize
        updateState();
        console.log('Bottom controls enhanced: properly centered with left/right positioning.');
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
            })();
            _featureStatus.hidePaintMenu = 'ok';
            console.log('[GeoPixelcons++] ✅ Paint Menu Controls loaded');
        } catch (err) {
            _featureStatus.hidePaintMenu = 'error';
            dbgPush(`Paint Menu Controls init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Paint Menu Controls' });
            console.error('[GeoPixelcons++] ❌ Paint Menu Controls failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Ghost Template Manager [ghostTemplateManager]
    // ============================================================
    if (_settings.ghostTemplateManager) {
        try {
            (function _init_ghostTemplateManager() {

    // ========== CONFIGURATION ==========
    const DEBUG_MODE = false;
    const DB_NAME = 'GP_Ghost_History';
    const DB_VERSION = 3;
    const STORE_NAME = 'images';

    // Marker Colors for Encoding
    const MARKER_R = 71;
    const MARKER_G = 80;
    const MARKER_B = 88;
    const POSITION_OFFSET = 2147483648;

    let isInternalUpdate = false;
    let previewActive = false;
    let previewOverlay = null;

    // ========== UTILITIES ==========
    function gpLog(msg, data = null) {
        if (!DEBUG_MODE) return;
        console.log(`%c[GP Manager] ${msg}`, "color: #00ffff; background: #000; padding: 2px 4px;", data || '');
    }

    // Debug: Log environment info on load
    gpLog("Script loaded. Environment check:", {
        hasWindow: typeof window !== 'undefined',
        hasUnsafeWindow: typeof unsafeWindow !== 'undefined',
        windowMap: typeof window !== 'undefined' ? typeof window.map : 'N/A',
        windowTurf: typeof window !== 'undefined' ? typeof window.turf : 'N/A',
        unsafeWindowMap: typeof unsafeWindow !== 'undefined' ? typeof unsafeWindow.map : 'N/A',
        unsafeWindowTurf: typeof unsafeWindow !== 'undefined' ? typeof unsafeWindow.turf : 'N/A'
    });

    /**
     * Safely get a page variable, avoiding DOM element conflicts.
     * In some browsers, accessing unsafeWindow.map returns the <div id="map"> element
     * instead of the JavaScript map variable.
     */
    function getPageVariable(varName) {
        // Try window first (works in Chrome/Vivaldi)
        if (typeof window !== 'undefined' && window[varName] !== undefined) {
            const val = window[varName];
            // Make sure it's not a DOM element when we expect an object with methods
            if (varName === 'map' && val instanceof HTMLElement) {
                gpLog(`window.${varName} is a DOM element, trying unsafeWindow`);
            } else {
                gpLog(`Found ${varName} in window`);
                return val;
            }
        }

        // Try unsafeWindow (needed in Firefox/Brave with @grant permissions)
        if (typeof unsafeWindow !== 'undefined' && unsafeWindow[varName] !== undefined) {
            const val = unsafeWindow[varName];
            // Check if it's a DOM element when we expect the map object
            if (varName === 'map' && val instanceof HTMLElement) {
                gpLog(`unsafeWindow.${varName} is a DOM element, looking for alternatives`);

                // Try to get the map from common Mapbox/Leaflet global patterns
                // The map might be stored in a different variable or we need wrappedJSObject (Firefox)
                if (typeof unsafeWindow.wrappedJSObject !== 'undefined' && unsafeWindow.wrappedJSObject[varName]) {
                    const wrappedVal = unsafeWindow.wrappedJSObject[varName];
                    if (!(wrappedVal instanceof HTMLElement)) {
                        gpLog(`Found ${varName} in wrappedJSObject`);
                        return wrappedVal;
                    }
                }

                // For Brave/Chrome with sandboxing, try accessing via page script injection
                gpLog(`Attempting page context injection for ${varName}`);
                return getPageVariableViaInjection(varName);
            } else {
                gpLog(`Found ${varName} in unsafeWindow`);
                return val;
            }
        }

        // Try wrappedJSObject directly (Firefox)
        if (typeof unsafeWindow !== 'undefined' &&
            typeof unsafeWindow.wrappedJSObject !== 'undefined' &&
            unsafeWindow.wrappedJSObject[varName] !== undefined) {
            gpLog(`Found ${varName} in wrappedJSObject`);
            return unsafeWindow.wrappedJSObject[varName];
        }

        gpLog(`Could not find ${varName} in any scope`);
        return null;
    }

    /**
     * Get a page variable by creating a bridge in the page context.
     * This is needed in Brave when @grant permissions create a sandbox.
     */
    function getPageVariableViaInjection(varName) {
        try {
            // Create a unique ID for this retrieval
            const bridgeId = `__gp_bridge_${varName}_${Date.now()}`;

            // Inject a script that copies the variable to a data attribute
            const script = document.createElement('script');
            script.textContent = `
                (function() {
                    if (typeof ${varName} !== 'undefined' && ${varName}) {
                        // Store a marker that the variable exists
                        document.documentElement.setAttribute('${bridgeId}', 'exists');
                        // For map object, we can't directly transfer it, so we'll access it differently
                        if ('${varName}' === 'map' && typeof ${varName}.project === 'function') {
                            document.documentElement.setAttribute('${bridgeId}_hasProject', 'true');
                        }
                    }
                })();
            `;
            document.documentElement.appendChild(script);
            script.remove();

            // Check if the variable exists
            const exists = document.documentElement.getAttribute(bridgeId);
            document.documentElement.removeAttribute(bridgeId);
            document.documentElement.removeAttribute(`${bridgeId}_hasProject`);

            if (exists === 'exists') {
                gpLog(`${varName} exists in page context, creating proxy`);

                // For the map object specifically, we need to create a proxy that executes in page context
                if (varName === 'map') {
                    return createMapProxy();
                } else if (varName === 'turf') {
                    return createTurfProxy();
                }
            }

            gpLog(`${varName} not found via injection`);
            return null;
        } catch (e) {
            gpLog(`Error in page context injection for ${varName}:`, e.message);
            return null;
        }
    }

    /**
     * Create a proxy object for the map that executes methods in page context
     */
    function createMapProxy() {
        return {
            project: function(lngLat) {
                // Execute in page context and return result
                const script = document.createElement('script');
                const resultId = `__gp_map_result_${Date.now()}`;
                script.textContent = `
                    (function() {
                        try {
                            const result = map.project([${lngLat[0]}, ${lngLat[1]}]);
                            document.documentElement.setAttribute('${resultId}', JSON.stringify({x: result.x, y: result.y}));
                        } catch(e) {
                            document.documentElement.setAttribute('${resultId}_error', e.message);
                        }
                    })();
                `;
                document.documentElement.appendChild(script);
                script.remove();

                const resultStr = document.documentElement.getAttribute(resultId);
                const errorStr = document.documentElement.getAttribute(`${resultId}_error`);
                document.documentElement.removeAttribute(resultId);
                document.documentElement.removeAttribute(`${resultId}_error`);

                if (errorStr) {
                    throw new Error(errorStr);
                }

                return JSON.parse(resultStr);
            },
            on: function(event, handler) {
                gpLog(`Map event listener for ${event} registered (proxy mode)`);
                // Store the handler for later use
                if (!this._handlers) this._handlers = {};
                if (!this._handlers[event]) this._handlers[event] = [];
                this._handlers[event].push(handler);

                // Set up event forwarding via page script
                const listenerId = `__gp_map_listener_${event}_${Date.now()}`;
                const script = document.createElement('script');
                script.textContent = `
                    (function() {
                        if (typeof map !== 'undefined' && map.on) {
                            map.on('${event}', function() {
                                document.documentElement.setAttribute('${listenerId}', Date.now());
                            });
                        }
                    })();
                `;
                document.documentElement.appendChild(script);
                script.remove();

                // Set up mutation observer to detect attribute changes
                const observer = new MutationObserver(() => {
                    const val = document.documentElement.getAttribute(listenerId);
                    if (val) {
                        document.documentElement.removeAttribute(listenerId);
                        handler();
                    }
                });
                observer.observe(document.documentElement, { attributes: true });
            },
            off: function(event, handler) {
                gpLog(`Map event listener for ${event} removed (proxy mode)`);
                // In proxy mode, we can't easily remove specific handlers
                // This is a limitation of the bridge approach
            },
            getContainer: function() {
                return document.getElementById('map');
            }
        };
    }

    /**
     * Create a proxy object for turf that executes methods in page context
     */
    function createTurfProxy() {
        return {
            toWgs84: function(mercCoords) {
                const script = document.createElement('script');
                const resultId = `__gp_turf_result_${Date.now()}`;
                script.textContent = `
                    (function() {
                        try {
                            const result = turf.toWgs84([${mercCoords[0]}, ${mercCoords[1]}]);
                            document.documentElement.setAttribute('${resultId}', JSON.stringify(result));
                        } catch(e) {
                            document.documentElement.setAttribute('${resultId}_error', e.message);
                        }
                    })();
                `;
                document.documentElement.appendChild(script);
                script.remove();

                const resultStr = document.documentElement.getAttribute(resultId);
                const errorStr = document.documentElement.getAttribute(`${resultId}_error`);
                document.documentElement.removeAttribute(resultId);
                document.documentElement.removeAttribute(`${resultId}_error`);

                if (errorStr) {
                    throw new Error(errorStr);
                }

                return JSON.parse(resultStr);
            }
        };
    }

    function notifyUser(title, message) {
        // Use safe helper to get showAlert function
        const showAlert = getPageVariable('showAlert');

        if (typeof showAlert === 'function') {
            showAlert(title, message);
        } else {
            console.log(`[${title}] ${message}`);
            // Fallback alert if site's showAlert is not available
            alert(`${title}: ${message}`);
        }
    }

    function goToTemplateLocation() {
        const savedCoordsStr = localStorage.getItem('ghostImageCoords');
        if (!savedCoordsStr) {
            notifyUser("No Template", "No ghost image template is currently set.");
            return;
        }

        try {
            const coords = JSON.parse(savedCoordsStr);
            if (typeof coords.gridX !== 'number' || typeof coords.gridY !== 'number') {
                notifyUser("Error", "Invalid coordinates in template.");
                return;
            }

            // Get goToGridLocation using safe helper
            const goToGridLocation = getPageVariable('goToGridLocation');

            if (typeof goToGridLocation === 'function') {
                gpLog(`Teleporting to template location: ${coords.gridX}, ${coords.gridY}`);
                goToGridLocation(coords.gridX, coords.gridY);
            } else {
                notifyUser("Error", "Navigation function not available.");
                gpLog("ERROR: goToGridLocation function not found in window or unsafeWindow");
            }
        } catch (e) {
            dbgPush(`goToTemplateLocation failed to parse coordinates: ${e && e.message ? e.message : String(e)}`, { error: e, uiComponent: 'Ghost Template Manager - Go To Location button' });
            console.error("Failed to parse coordinates:", e);
            notifyUser("Error", "Failed to read template coordinates.");
        }
    }

    // Computes a SHA-256 fingerprint of the file content
    async function computeFileHash(blob) {
        const buffer = await blob.arrayBuffer();
        const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    }

    // Computes a templateId from the clean image content (without position encoding)
    // This allows us to identify the same template even if it's been moved to different positions
    async function computeTemplateId(blob) {
        try {
            const img = await loadImageToCanvas(blob);
            const decoded = decodeRobustPosition(img);

            if (decoded && decoded.cleanCanvas) {
                // If position was encoded, use the clean canvas for ID
                const cleanBlob = await new Promise(r => decoded.cleanCanvas.toBlob(r, 'image/png'));
                return await computeFileHash(cleanBlob);
            } else {
                // No position encoding found, use original hash
                return await computeFileHash(blob);
            }
        } catch (e) {
            // On error, fall back to regular hash
            return await computeFileHash(blob);
        }
    }

    // ========== STYLES ==========
    const style = document.createElement('style');
    style.textContent = `
        .gp-to-modal-overlay {
            position: fixed; inset: 0; background: rgba(0, 0, 0, 0.75);
            display: flex; align-items: center; justify-content: center; z-index: 10000;
        }
        .gp-to-modal-panel {
            background: var(--color-gray-100, white); color: var(--color-gray-900, inherit); border-radius: 1rem; padding: 1.5rem;
            width: 95%; max-width: 600px; max-height: 80vh;
            display: flex; flex-direction: column; gap: 1rem;
            box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
        }
        .gp-to-header { display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--color-gray-300, #eee); padding-bottom: 10px; }
        .gp-to-header-row { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
        .gp-to-title { font-size: 1.25rem; font-weight: bold; color: var(--color-gray-900, #1f2937); }

        .gp-to-grid {
            display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
            gap: 10px; overflow-y: auto; padding: 4px;
        }
        .gp-to-card {
            border: 1px solid var(--color-gray-300, #e5e7eb); border-radius: 8px; overflow: hidden;
            position: relative; transition: transform 0.1s, box-shadow 0.1s;
            cursor: pointer; background: var(--color-gray-200, #f9fafb);
        }
        .gp-to-card:hover { transform: translateY(-2px); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); border-color: #3b82f6; }
        .gp-to-card img { width: 100%; height: 100px; object-fit: cover; display: block; }
        .gp-to-card-footer {
            padding: 4px; font-size: 10px; text-align: center;
            background: var(--color-gray-100, #fff); color: var(--color-gray-500, #6b7280); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
        }
        .gp-to-delete-btn {
            position: absolute; top: 2px; right: 2px;
            background: rgba(239, 68, 68, 0.9); color: white;
            border: none; border-radius: 4px; width: 20px; height: 20px;
            display: flex; align-items: center; justify-content: center;
            font-size: 12px; cursor: pointer; z-index: 2;
            opacity: 0; transition: opacity 0.15s;
        }
        .gp-to-card:hover .gp-to-delete-btn { opacity: 1; }
        .gp-to-delete-btn:hover { background: #dc2626; }
        .gp-to-sel-btn {
            position: absolute; top: 2px; left: 2px;
            background: rgba(255,255,255,0.9); color: #374151;
            border: none; border-radius: 4px; width: 20px; height: 20px;
            display: flex; align-items: center; justify-content: center;
            font-size: 13px; cursor: pointer; z-index: 2; line-height: 1;
            opacity: 0; transition: opacity 0.15s;
        }
        .gp-to-card:hover .gp-to-sel-btn { opacity: 1; }
        .gp-to-sel-btn:hover { background: #dbeafe; }
        .gp-to-sel-btn.selected { background: #3b82f6; color: white; opacity: 1; }
        .gp-to-grid.gp-sel-active .gp-to-sel-btn { opacity: 1; }
        .gp-to-goto-btn {
            position: absolute; bottom: 22px; left: 2px;
            background: rgba(251, 146, 60, 0.92); color: white;
            border: none; border-radius: 4px; width: 20px; height: 20px;
            display: flex; align-items: center; justify-content: center;
            font-size: 11px; cursor: pointer; z-index: 2; line-height: 1;
            opacity: 0; transition: opacity 0.15s;
        }
        .gp-to-card:hover .gp-to-goto-btn { opacity: 1; }
        .gp-to-goto-btn:hover { background: rgba(234, 88, 12, 0.95); }
        .gp-to-card.gp-selected { border-color: #3b82f6; box-shadow: 0 0 0 2px #93c5fd; }

        .gp-to-btn {
            padding: 0.5rem 1rem; border-radius: 0.5rem; font-weight: 600; cursor: pointer; border: none;
            display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
            transition: all 0.2s;
        }
        .gp-to-modal-panel .gp-to-btn { font-size: 0.75rem; }
        .gp-to-btn-blue { background-color: #3b82f6; color: white; }
        .gp-to-btn-blue:hover { background-color: #2563eb; }
        .gp-to-btn-green { background-color: #10b981; color: white; }
        .gp-to-btn-green:hover { background-color: #059669; }
        .gp-to-btn-purple { background-color: #8b5cf6; color: white; }
        .gp-to-btn-purple:hover { background-color: #7c3aed; }
        .gp-to-btn-red { background-color: #ef4444; color: white; }
        .gp-to-btn-gray { background-color: var(--color-gray-300, #e5e7eb); color: var(--color-gray-800, #374151); }
        .gp-to-btn-orange { background-color: #f97316; color: white; }
        .gp-to-btn-orange:hover { background-color: #ea580c; }
        .gp-to-btn-cyan { background-color: #06b6d4; color: white; border: 2px solid transparent; }
        .gp-to-btn-cyan:hover { background-color: #0891b2; }
        .gp-to-btn-cyan.active {
            background-color: #0e7490;
            border: 2px solid #fbbf24;
            box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.3);
        }

        .gp-to-preview-overlay {
            position: fixed;
            pointer-events: none;
            z-index: 9999;
            opacity: 0.7;
            transition: opacity 0.2s;
        }
    `;
    document.head.appendChild(style);

    // ========== INDEXED DB (CACHE) ==========

    const dbPromise = new Promise((resolve, reject) => {
        const request = indexedDB.open(DB_NAME, DB_VERSION);

        request.onupgradeneeded = (e) => {
            const db = e.target.result;
            const txn = e.target.transaction;

            let store;
            if (!db.objectStoreNames.contains(STORE_NAME)) {
                store = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
            } else {
                store = txn.objectStore(STORE_NAME);
            }

            if (!store.indexNames.contains('hash')) {
                store.createIndex('hash', 'hash', { unique: false });
            }
            if (!store.indexNames.contains('templateId')) {
                store.createIndex('templateId', 'templateId', { unique: false });
            }
        };

        request.onsuccess = (e) => resolve(e.target.result);
        request.onerror = (e) => {
            const err = new Error(`IndexedDB failed to open '${DB_NAME}' v${DB_VERSION}: ${e.target.error}`);
            dbgPush(`dbPromise rejected — IndexedDB unavailable: ${err.message}`, { error: err, uiComponent: 'Ghost Template Manager - IndexedDB init' });
            reject(err);
        };
    });

    const HistoryManager = {
        async add(blob, filename) {
            const db = await dbPromise;
            const hash = await computeFileHash(blob);
            const templateId = await computeTemplateId(blob);

            return new Promise((resolve, reject) => {
                const tx = db.transaction(STORE_NAME, 'readwrite');
                const store = tx.objectStore(STORE_NAME);
                const templateIndex = store.index('templateId');

                const req = templateIndex.get(templateId);

                req.onsuccess = () => {
                    const existing = req.result;

                    if (existing) {
                        gpLog("Duplicate template detected (same image, possibly different position). Updating entry.");
                        store.delete(existing.id);
                    }

                    const item = {
                        blob: blob,
                        name: filename || `Image_${Date.now()}`,
                        date: Date.now(),
                        hash: hash,
                        templateId: templateId
                    };
                    store.add(item);
                };

                tx.oncomplete = () => resolve();
                tx.onerror = () => reject(tx.error);
            });
        },
        async getAll() {
            const db = await dbPromise;
            return new Promise((resolve, reject) => {
                const tx = db.transaction(STORE_NAME, 'readonly');
                const store = tx.objectStore(STORE_NAME);
                const req = store.getAll();
                req.onsuccess = () => resolve(req.result.reverse());
                tx.onerror = () => {
                    const err = new Error(`HistoryManager.getAll transaction failed: ${tx.error}`);
                    dbgPush(err.message, { error: err, uiComponent: 'Ghost Template Manager - HistoryManager.getAll' });
                    reject(err);
                };
            });
        },
        async delete(id) {
            const db = await dbPromise;
            return new Promise((resolve) => {
                const tx = db.transaction(STORE_NAME, 'readwrite');
                tx.objectStore(STORE_NAME).delete(id);
                tx.oncomplete = () => resolve();
            });
        },
        async clear() {
            const db = await dbPromise;
            return new Promise((resolve) => {
                const tx = db.transaction(STORE_NAME, 'readwrite');
                tx.objectStore(STORE_NAME).clear();
                tx.oncomplete = () => resolve();
            });
        }
    };

    // ========== IMPORT/EXPORT FUNCTIONS ==========

    // Helper function to convert blob to base64
    function blobToBase64(blob) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result.split(',')[1]);
            reader.onerror = reject;
            reader.readAsDataURL(blob);
        });
    }

    async function exportToZip() {
        gpLog("exportToZip: Starting export...");
        const images = await HistoryManager.getAll();
        gpLog(`exportToZip: Retrieved ${images.length} images`);

        if (images.length === 0) {
            notifyUser("Info", "No images to export.");
            return;
        }

        // JSZip doesn't work in Tampermonkey sandbox - use JSON bundle instead
        gpLog("exportToZip: Using JSON bundle export (JSZip incompatible with this environment)");

        try {
            const exportData = {
                version: "3.4",
                exportDate: new Date().toISOString(),
                images: []
            };

            for (let i = 0; i < images.length; i++) {
                const imgData = images[i];
                gpLog(`Encoding image ${i+1}/${images.length}: ${imgData.name}`);

                const base64 = await blobToBase64(imgData.blob);

                exportData.images.push({
                    id: imgData.id,
                    name: imgData.name,
                    date: imgData.date,
                    hash: imgData.hash,
                    templateId: imgData.templateId,
                    imageData: base64,
                    mimeType: imgData.blob.type || 'image/png'
                });
            }

            gpLog(`exportToZip: Creating download...`);

            const jsonStr = JSON.stringify(exportData);
            const blob = new Blob([jsonStr], { type: 'application/json' });

            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `GeoPixels_History_${Date.now()}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);

            gpLog("exportToZip: Export complete");
            notifyUser("Success", `Exported ${images.length} images to JSON bundle.`);
        } catch (error) {
            dbgPush(`exportToZip failed: ${error && error.message ? error.message : String(error)}`, { error: error, uiComponent: 'Image History - Export All' });
            console.error("exportToZip failed:", error);
            gpLog(`exportToZip: ERROR - ${error.message}`);
            notifyUser("Error", "Failed to export: " + error.message);
        }
    }

    async function exportSelectedToJson(selectedIds) {
        if (selectedIds.size === 0) {
            notifyUser("Info", "No images selected.");
            return;
        }
        const images = await HistoryManager.getAll();
        const filtered = images.filter(img => selectedIds.has(img.id));
        if (filtered.length === 0) {
            notifyUser("Info", "No matching images found.");
            return;
        }
        try {
            const exportData = {
                version: "3.4",
                exportDate: new Date().toISOString(),
                images: []
            };
            for (let i = 0; i < filtered.length; i++) {
                const imgData = filtered[i];
                const base64 = await blobToBase64(imgData.blob);
                exportData.images.push({
                    id: imgData.id,
                    name: imgData.name,
                    date: imgData.date,
                    hash: imgData.hash,
                    templateId: imgData.templateId,
                    imageData: base64,
                    mimeType: imgData.blob.type || 'image/png'
                });
            }
            const jsonStr = JSON.stringify(exportData);
            const blob = new Blob([jsonStr], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `GeoPixels_Selected_${Date.now()}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            notifyUser("Success", `Exported ${filtered.length} image${filtered.length !== 1 ? 's' : ''}.`);
        } catch (error) {
            dbgPush(`exportSelectedToJson failed: ${error && error.message ? error.message : String(error)}`, { error: error, uiComponent: 'Image History - Export Selected' });
            console.error("exportSelectedToJson failed:", error);
            notifyUser("Error", "Failed to export: " + error.message);
        }
    }

    async function importFromZip(file) {
        try {
            gpLog(`importFromZip: Starting import of ${file.name}`);

            // Check if it's a JSON file (new format)
            if (file.name.endsWith('.json')) {
                gpLog("importFromZip: Detected JSON bundle format");
                const text = await file.text();
                const data = JSON.parse(text);

                if (!data.images || !Array.isArray(data.images)) {
                    notifyUser("Error", "Invalid JSON: 'images' array not found.");
                    return;
                }

                let imported = 0;
                for (const imgEntry of [...data.images].reverse()) {
                    // Convert base64 back to blob
                    const byteCharacters = atob(imgEntry.imageData);
                    const byteNumbers = new Array(byteCharacters.length);
                    for (let i = 0; i < byteCharacters.length; i++) {
                        byteNumbers[i] = byteCharacters.charCodeAt(i);
                    }
                    const byteArray = new Uint8Array(byteNumbers);
                    const blob = new Blob([byteArray], { type: imgEntry.mimeType || 'image/png' });

                    // Check for duplicate
                    const existingImages = await HistoryManager.getAll();
                    const isDuplicate = existingImages.some(img => img.hash === imgEntry.hash);

                    if (!isDuplicate) {
                        await HistoryManager.add(blob, imgEntry.name, imgEntry.hash);
                        imported++;
                        gpLog(`Imported: ${imgEntry.name}`);
                    } else {
                        gpLog(`Skipped duplicate: ${imgEntry.name}`);
                    }
                }

                notifyUser("Success", `Imported ${imported} images from JSON bundle.`);
                return;
            }

            // Try ZIP format (legacy) - may not work
            gpLog("importFromZip: Attempting ZIP format (may fail)");
            const zip = await JSZip.loadAsync(file);
            const metadataFile = zip.file('metadata.json');

            if (!metadataFile) {
                notifyUser("Error", "Invalid ZIP: metadata.json not found.");
                return;
            }

            const metadataText = await metadataFile.async('text');
            const metadata = JSON.parse(metadataText);

            let imported = 0;
            for (const item of metadata) {
                const imageFile = zip.file(item.filename);
                if (imageFile) {
                    const blob = await imageFile.async('blob');
                    await HistoryManager.add(blob, item.name);
                    imported++;
                }
            }

            notifyUser("Success", `Imported ${imported} images from ZIP.`);
            return true;
        } catch (e) {
            console.error(e);
            notifyUser("Error", "Failed to import file.");
            return false;
        }
    }

    // ========== ALGORITHM (ENCODE/DECODE) ==========

    function encodeRobustPosition(originalCanvas, gridX, gridY) {
        const width = originalCanvas.width;
        const height = originalCanvas.height;
        const newCanvas = document.createElement('canvas');
        newCanvas.width = width;
        newCanvas.height = height + 1;
        const ctx = newCanvas.getContext('2d', { willReadFrequently: true });
        ctx.drawImage(originalCanvas, 0, 1);
        const headerImage = ctx.getImageData(0, 0, width, 1);
        const data = headerImage.data;
        const valX = (gridX + POSITION_OFFSET) >>> 0;
        const valY = (gridY + POSITION_OFFSET) >>> 0;
        const packetSize = 5;
        const maxPackets = Math.floor(width / packetSize);
        for (let i = 0; i < maxPackets; i++) {
            const base = (i * packetSize) * 4;
            data[base] = MARKER_R; data[base + 1] = MARKER_G; data[base + 2] = MARKER_B; data[base + 3] = 255;
            data[base + 4] = (valX >>> 24) & 0xFF; data[base + 5] = (valX >>> 16) & 0xFF; data[base + 6] = 0; data[base + 7] = 255;
            data[base + 8] = (valX >>> 8) & 0xFF; data[base + 9] = valX & 0xFF; data[base + 10] = 0; data[base + 11] = 255;
            data[base + 12] = (valY >>> 24) & 0xFF; data[base + 13] = (valY >>> 16) & 0xFF; data[base + 14] = 0; data[base + 15] = 255;
            data[base + 16] = (valY >>> 8) & 0xFF; data[base + 17] = valY & 0xFF; data[base + 18] = 0; data[base + 19] = 255;
        }
        ctx.putImageData(headerImage, 0, 0);
        return newCanvas;
    }

    function decodeRobustPosition(img) {
        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        const ctx = canvas.getContext('2d', { willReadFrequently: true });
        ctx.drawImage(img, 0, 0);
        const headerData = ctx.getImageData(0, 0, img.width, 1).data;
        const votesX = new Map();
        const votesY = new Map();
        let validPackets = 0;
        const packetSize = 5;
        const maxPackets = Math.floor(img.width / packetSize);
        for (let i = 0; i < maxPackets; i++) {
            const base = (i * packetSize) * 4;
            if (headerData[base] === MARKER_R && headerData[base + 1] === MARKER_G && headerData[base + 2] === MARKER_B && headerData[base + 3] === 255) {
                const xVal = ((headerData[base + 4] << 24) | (headerData[base + 5] << 16) | (headerData[base + 8] << 8) | headerData[base + 9]) >>> 0;
                const yVal = ((headerData[base + 12] << 24) | (headerData[base + 13] << 16) | (headerData[base + 16] << 8) | headerData[base + 17]) >>> 0;
                votesX.set(xVal, (votesX.get(xVal) || 0) + 1);
                votesY.set(yVal, (votesY.get(yVal) || 0) + 1);
                validPackets++;
            }
        }
        if (validPackets === 0) return null;
        const getWinner = (map) => [...map.entries()].reduce((a, b) => b[1] > a[1] ? b : a)[0];
        const gridX = getWinner(votesX) - POSITION_OFFSET;
        const gridY = getWinner(votesY) - POSITION_OFFSET;
        const cleanCanvas = document.createElement('canvas');
        cleanCanvas.width = img.width;
        cleanCanvas.height = img.height - 1;
        const cleanCtx = cleanCanvas.getContext('2d');
        cleanCtx.drawImage(canvas, 0, 1, img.width, img.height - 1, 0, 0, img.width, img.height - 1);
        return { gridX, gridY, cleanCanvas };
    }

    // ========== PREVIEW FUNCTIONALITY ==========

    let previewImageCache = null;
    let previewRenderHandler = null;

    function drawPreviewImageOnCanvas() {
        gpLog("drawPreviewImageOnCanvas called");

        if (!previewOverlay) {
            gpLog("No preview overlay, returning");
            return;
        }

        if (!previewActive) {
            gpLog("Preview not active, returning");
            return;
        }

        const savedImageData = localStorage.getItem('ghostImageData');
        const savedCoordsStr = localStorage.getItem('ghostImageCoords');

        if (!savedCoordsStr || !savedImageData) {
            gpLog("Missing ghost image data or coords in localStorage");
            return;
        }

        const coords = JSON.parse(savedCoordsStr);
        gpLog("Ghost coords", coords);

        // Use cached image to avoid reloading
        if (!previewImageCache || previewImageCache.src !== savedImageData) {
            previewImageCache = new Image();
            previewImageCache.src = savedImageData;
            gpLog("Loading new preview image");
        }

        const img = previewImageCache;
        if (!img.complete) {
            gpLog("Image not loaded yet, waiting...");
            img.onload = () => {
                gpLog("Image loaded, redrawing");
                drawPreviewImageOnCanvas();
            };
            return;
        }

        gpLog("Image loaded, dimensions:", { width: img.width, height: img.height });

        // Get required game variables
        const pixelCanvas = document.getElementById('pixel-canvas');
        if (!pixelCanvas) {
            gpLog("ERROR: pixel-canvas not found");
            return;
        }

        // Match canvas size to pixel canvas
        if (previewOverlay.width !== pixelCanvas.width || previewOverlay.height !== pixelCanvas.height) {
            previewOverlay.width = pixelCanvas.width;
            previewOverlay.height = pixelCanvas.height;
            gpLog("Resized preview canvas to", { width: pixelCanvas.width, height: pixelCanvas.height });
        }

        const ctx = previewOverlay.getContext('2d');
        const { width, height } = previewOverlay;
        ctx.clearRect(0, 0, width, height);
        gpLog("Cleared canvas");

        // Get map and turf using safe helper to avoid DOM element conflicts
        const map = getPageVariable('map');
        const turf = getPageVariable('turf');

        // gridSize is often 25 (standard grid size for geopixels)
        // Try to get from page, fallback to defaults
        let gridSize = getPageVariable('gridSize') || 25;
        let halfSize = getPageVariable('halfSize') || (gridSize / 2);
        let offsetMetersX = getPageVariable('offsetMetersX') || 0;
        let offsetMetersY = getPageVariable('offsetMetersY') || 0;

        gpLog("Grid values:", { gridSize, halfSize, offsetMetersX, offsetMetersY });

        if (!map || !turf) {
            gpLog("ERROR: Missing required variables", {
                hasMap: !!map,
                hasTurf: !!turf,
                gridSize: gridSize
            });
            return;
        }

        if (typeof map.project !== 'function') {
            gpLog("ERROR: map.project is not a function", { mapType: typeof map });
            return;
        }

        // Calculate corners using the SAME method as the game's drawGhostImageOnCanvas
        // Top-left pixel center
        const tl_pixel_center_x = coords.gridX * gridSize;
        const tl_pixel_center_y = coords.gridY * gridSize;

        // Top-left mercator edge
        const tl_merc_edge = [
            tl_pixel_center_x - halfSize + offsetMetersX,
            tl_pixel_center_y + halfSize + offsetMetersY
        ];

        // Bottom-right grid coordinates
        const br_pixel_gridX = coords.gridX + img.width - 1;
        const br_pixel_gridY = coords.gridY - img.height + 1;

        const br_pixel_center_x = br_pixel_gridX * gridSize;
        const br_pixel_center_y = br_pixel_gridY * gridSize;

        // Bottom-right mercator edge
        const br_merc_edge = [
            br_pixel_center_x + halfSize + offsetMetersX,
            br_pixel_center_y - halfSize + offsetMetersY
        ];

        gpLog("Mercator coords (ghost method)", { tl_merc_edge, br_merc_edge });

        // Convert to WGS84 and then project to screen
        const topLeftScreen = map.project(turf.toWgs84(tl_merc_edge));
        const bottomRightScreen = map.project(turf.toWgs84(br_merc_edge));

        gpLog("Screen coords", { topLeftScreen, bottomRightScreen });

        const drawX = topLeftScreen.x;
        const drawY = topLeftScreen.y;
        const screenWidth = bottomRightScreen.x - drawX;
        const screenHeight = bottomRightScreen.y - drawY;

        gpLog("Draw position and dimensions", { drawX, drawY, screenWidth, screenHeight });

        // Check if visible
        if (drawX + screenWidth < 0 ||
            drawX > width ||
            drawY + screenHeight < 0 ||
            drawY > height) {
            gpLog("Image not in viewport, skipping draw");
            return;
        }

        // Draw fully opaque
        ctx.imageSmoothingEnabled = false;
        ctx.drawImage(img, drawX, drawY, screenWidth, screenHeight);

        gpLog("Drew preview image successfully");
    }

    function togglePreview(button) {
        gpLog("togglePreview called, current state:", previewActive);
        gpLog("Button click - environment check:", {
            windowExists: typeof window !== 'undefined',
            unsafeWindowExists: typeof unsafeWindow !== 'undefined',
            windowKeys: typeof window !== 'undefined' ? Object.keys(window).filter(k => k.includes('map') || k.includes('turf')).slice(0, 10) : [],
            unsafeWindowKeys: typeof unsafeWindow !== 'undefined' ? Object.keys(unsafeWindow).filter(k => k.includes('map') || k.includes('turf')).slice(0, 10) : []
        });

        if (previewActive) {
            // Deactivate preview
            gpLog("Deactivating preview");

            if (previewOverlay && previewOverlay.parentNode) {
                previewOverlay.parentNode.removeChild(previewOverlay);
                gpLog("Removed preview overlay from DOM");
            }

            // Unhook from map events
            if (previewRenderHandler) {
                const map = getPageVariable('map');
                if (map && typeof map.off === 'function') {
                    try {
                        map.off('move', previewRenderHandler);
                        map.off('zoom', previewRenderHandler);
                        map.off('rotate', previewRenderHandler);
                        gpLog("Removed map event listeners");
                    } catch (e) {
                        gpLog("Error removing map listeners", e);
                    }
                }
            }

            previewOverlay = null;
            previewImageCache = null;
            previewRenderHandler = null;
            previewActive = false;
            button.innerHTML = '👁️ Preview';
            button.classList.remove('active');
            gpLog("Preview deactivated");
        } else {
            // Activate preview
            gpLog("Activating preview");

            const savedImageData = localStorage.getItem('ghostImageData');
            const savedCoordsStr = localStorage.getItem('ghostImageCoords');

            if (!savedImageData || !savedCoordsStr) {
                gpLog("ERROR: No ghost image data in localStorage");
                notifyUser("Error", "No ghost image on map to preview.");
                return;
            }

            gpLog("Found ghost data in localStorage");

            // Find the pixel canvas to match its size
            const pixelCanvas = document.getElementById('pixel-canvas');
            if (!pixelCanvas) {
                gpLog("ERROR: pixel-canvas not found");
                notifyUser("Error", "Pixel canvas not found. Make sure you're on the map view.");
                return;
            }

            gpLog("Found pixel canvas", { width: pixelCanvas.width, height: pixelCanvas.height });

            // Verify map exists
            const map = getPageVariable('map');
            if (!map) {
                gpLog("ERROR: map not found in any scope");
                notifyUser("Error", "Map not initialized yet. Please wait a moment and try again.");
                return;
            }

            gpLog("Map object found", {
                mapType: typeof map,
                hasProject: typeof map.project,
                isHTMLElement: map instanceof HTMLElement,
                constructor: map.constructor ? map.constructor.name : 'unknown'
            });

            if (typeof map.project !== 'function') {
                gpLog("ERROR: map.project is not a function", {
                    mapType: typeof map,
                    projectType: typeof map.project,
                    mapKeys: Object.keys(map).slice(0, 20),
                    mapConstructor: map.constructor ? map.constructor.name : 'unknown'
                });
                notifyUser("Error", "Map projection not available. Page may not be fully loaded.");
                return;
            }

            gpLog("map.project verified as function");

            // Verify turf exists
            const turf = getPageVariable('turf');
            if (!turf) {
                gpLog("ERROR: turf not found in any scope");
                notifyUser("Error", "Turf.js library not loaded. Page may not be fully loaded.");
                return;
            }

            gpLog("Turf object found", { turfType: typeof turf, hasToWgs84: typeof turf.toWgs84 });

            if (typeof turf.toWgs84 !== 'function') {
                gpLog("ERROR: turf.toWgs84 is not a function", {
                    turfType: typeof turf,
                    toWgs84Type: typeof turf.toWgs84,
                    turfKeys: Object.keys(turf).slice(0, 20)
                });
                notifyUser("Error", "Map projection not available. Page may not be fully loaded.");
                return;
            }

            gpLog("turf.toWgs84 verified as function");

            gpLog("Map and turf are ready with required functions");

            // Create preview canvas
            previewOverlay = document.createElement('canvas');
            previewOverlay.id = 'gp-preview-canvas';
            previewOverlay.className = 'pixel-perfect';
            previewOverlay.width = pixelCanvas.width;
            previewOverlay.height = pixelCanvas.height;
            previewOverlay.style.cssText = 'display: block; image-rendering: pixelated; position: absolute; top: 0; left: 0; pointer-events: none; z-index: 5;';

            gpLog("Created preview canvas element");

            // Insert into DOM - find the map container
            const mapContainer = map.getContainer ? map.getContainer() : document.getElementById('map');
            if (mapContainer) {
                mapContainer.appendChild(previewOverlay);
                gpLog("Appended preview canvas to map container");
            } else {
                document.body.appendChild(previewOverlay);
                gpLog("Appended preview canvas to body (fallback)");
            }

            previewActive = true;
            button.innerHTML = '👁️ Hide Preview';
            button.classList.add('active');

            // Create render handler
            previewRenderHandler = () => {
                gpLog("Map event triggered, redrawing preview");
                drawPreviewImageOnCanvas();
            };

            // Hook into map events (same as geopixels++)
            try {
                map.on('move', previewRenderHandler);
                map.on('zoom', previewRenderHandler);
                map.on('rotate', previewRenderHandler);
                gpLog("Attached to map events");
            } catch (e) {
                gpLog("ERROR attaching map listeners", e);
            }

            // Render once immediately
            gpLog("Drawing initial preview");
            drawPreviewImageOnCanvas();

            gpLog("Preview activated successfully");
        }
    }

    /**
     * Replicates the logic of the 'Save Pos' button to cache the currently placed ghost image.
     * This function is available globally but is no longer called automatically.
     */
    async function cacheCurrentGhostPosition() {
        const savedCoordsStr = localStorage.getItem('ghostImageCoords');
        const savedImageData = localStorage.getItem('ghostImageData');
        if (!savedCoordsStr || !savedImageData) {
            gpLog("Auto-Cache: No ghost image on map or coordinates found.");
            return;
        }
        gpLog("Auto-Cache: Starting cache process.");

        const coords = JSON.parse(savedCoordsStr);
        const img = new Image();
        img.src = savedImageData;
        await new Promise(r => img.onload = r);

        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = img.width; tempCanvas.height = img.height;
        tempCanvas.getContext('2d').drawImage(img, 0, 0);

        const encodedCanvas = encodeRobustPosition(tempCanvas, coords.gridX, coords.gridY);
        encodedCanvas.toBlob(async (blob) => {
            if(!blob) return;

            // Save to History (Cache)
            try {
                await HistoryManager.add(blob, `Backup_${coords.gridX}_${coords.gridY}`);
                gpLog("Auto-Cache: Cached image with position data.");
                notifyUser("Auto-Cache", `Ghost image position ${coords.gridX}, ${coords.gridY} auto-cached.`);
            } catch (e) {
                dbgPush(`cacheCurrentGhostPosition HistoryManager.add failed: ${e && e.message ? e.message : String(e)}`, { error: e, uiComponent: 'Ghost Template Manager - Auto-Cache' });
                console.error("Auto-Cache failed", e);
                notifyUser("Auto-Cache Error", "Failed to auto-cache the image position.");
            }
        }, 'image/png');
    }
    // Expose for direct use if needed, but primarily used internally now
    window.cacheCurrentGhostPosition = cacheCurrentGhostPosition;


    // ========== GAME INTEGRATION ==========

    function applyCoordinatesToGame(coords) {
        gpLog("Applying coordinates...", coords);

        // Write coords to localStorage immediately so goToTemplateLocation and
        // any other readers have them right away — before the FileReader finishes.
        localStorage.setItem('ghostImageCoords', JSON.stringify(coords));

        // Poll until BOTH conditions are true:
        //   1. The place button is re-enabled (game's FileReader finished processing)
        //   2. ghostImageData is in localStorage (FileReader wrote it)
        // Only then call initializeGhostFromStorage — which reads the closure-scoped
        // `let ghostImageTopLeft` variable inside ghost22.js and is the only way to
        // reliably update it. Direct window property assignment does not reach it.
        let attempts = 0;
        const interval = setInterval(() => {
            const placeBtn = document.getElementById('initiatePlaceGhostBtn');
            const hasImageData = !!localStorage.getItem('ghostImageData');

            if (placeBtn && !placeBtn.disabled && hasImageData) {
                clearInterval(interval);

                const initializeGhostFromStorage = getPageVariable('initializeGhostFromStorage');
                if (typeof initializeGhostFromStorage === 'function') {
                    gpLog("Calling initializeGhostFromStorage (ghostImageData confirmed present)");
                    initializeGhostFromStorage();
                    notifyUser("Auto-Place", `Position detected: ${coords.gridX}, ${coords.gridY}`);
                } else {
                    const msg = `initializeGhostFromStorage not found in page scope — template loaded but coordinates not applied (${coords.gridX}, ${coords.gridY})`;
                    dbgPush(msg, { uiComponent: 'Ghost Template Manager - applyCoordinatesToGame' });
                    gpLog("ERROR: " + msg);
                    notifyUser("Warning", `Position set to ${coords.gridX}, ${coords.gridY} but auto-place failed. Click 'Place on Map' manually.`);
                }
            }

            if (++attempts > 100) {
                clearInterval(interval);
                const msg = `applyCoordinatesToGame timed out after 10s waiting for place button + ghostImageData (coords: ${coords.gridX}, ${coords.gridY}).`;
                dbgPush(msg, { uiComponent: 'Ghost Template Manager - applyCoordinatesToGame' });
                gpLog("Timeout: " + msg);
                notifyUser("Warning", `Position set to ${coords.gridX}, ${coords.gridY} but image may still be loading. Try clicking 'Place on Map' manually.`);
            }
        }, 100);
    }

    async function loadImageToCanvas(blob) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(img);
            img.onerror = reject;
            img.src = URL.createObjectURL(blob);
        });
    }

    // ========== PROCESSING LOGIC ==========

    async function processAndLoadImage(file, saveToHistory = true, uiComponent = null) {
        gpLog("Processing image...");
        const placeBtn = document.getElementById('initiatePlaceGhostBtn');
        if (placeBtn) { placeBtn.innerText = "Analyzing..."; placeBtn.disabled = true; }

        try {
            const img = await loadImageToCanvas(file);
            const decoded = decodeRobustPosition(img);

            let finalFile = file;
            let coords = null;

            if (decoded) {
                gpLog("Found encoded position.", { gridX: decoded.gridX, gridY: decoded.gridY });
                coords = { gridX: decoded.gridX, gridY: decoded.gridY };
                const cleanBlob = await new Promise((resolve, reject) => {
                    const toBlobTimeout = setTimeout(() => {
                        const err = new Error('cleanCanvas.toBlob timed out after 10s — canvas may be invalid or too large');
                        dbgPush(err.message, { error: err, uiComponent: uiComponent || 'Ghost Template Manager - processAndLoadImage' });
                        reject(err);
                    }, 10000);
                    decoded.cleanCanvas.toBlob(blob => {
                        clearTimeout(toBlobTimeout);
                        resolve(blob);
                    }, 'image/png');
                });
                finalFile = new File([cleanBlob], file.name || "ghost.png", { type: "image/png" });
            } else {
                gpLog("No encoded position found in image");
                // Ensure finalFile is a File (not a raw Blob from IndexedDB) —
                // DataTransferItemList.add() requires a File, not a Blob.
                if (!(finalFile instanceof File)) {
                    finalFile = new File([finalFile], 'ghost.png', { type: finalFile.type || 'image/png' });
                }
            }

            if (saveToHistory) {
                await HistoryManager.add(file, file.name);
            }

            const input = document.getElementById('ghostImageInput');
            const dt = new DataTransfer();
            dt.items.add(finalFile);
            input.files = dt.files;

            isInternalUpdate = true;
            input.dispatchEvent(new Event('change', { bubbles: true }));
            isInternalUpdate = false;

            // Wait for the game to process the image first
            await new Promise(resolve => setTimeout(resolve, 100));

            if (coords) {
                gpLog("Applying coordinates to game", coords);
                applyCoordinatesToGame(coords);
            } else {
                // No encoded position in this image — preserve any existing placement.
                // The user may have already manually placed a previous image; loading a
                // new image without coords should not silently erase that position.
                // Coords are only cleared by the explicit Clear button (clearGhostImage).
                gpLog("No encoded position found — existing coords preserved");
            }

        } catch (e) {
            dbgPush(`processAndLoadImage failed: ${e && e.message ? e.message : String(e)}`, { error: e, uiComponent: uiComponent || 'Ghost Template Manager' });
            console.error(e);
            notifyUser("Error", "Failed to process image.");
        } finally {
            if (placeBtn) placeBtn.innerText = "Place on Map";
        }
    }

    // ========== INTERCEPTOR ==========

    function setupNativeInterceptor() {
        const input = document.getElementById('ghostImageInput');
        if (!input) return;

        // 3. Add .zip to the file input's accepted types
        input.setAttribute('accept', 'image/png, image/jpeg, image/webp, image/gif, application/zip, .zip');

        input.addEventListener('change', async (e) => {
            if (isInternalUpdate) return;
            const file = e.target.files[0];
            if (!file) return;
            e.stopImmediatePropagation();
            e.preventDefault();

            // Check if it's a ZIP file
            if (file.type === 'application/zip' || file.type === 'application/x-zip-compressed' || file.name.toLowerCase().endsWith('.zip')) {
                gpLog("Detected ZIP file upload");
                const success = await importFromZip(file);
                if (success) {
                    // Clear the input so same file can be uploaded again
                    input.value = '';
                }
                return;
            }

            // Otherwise process as image
            processAndLoadImage(file, false, 'Ghost Image Input - file picker');
        }, true);
    }

    // ========== UI HANDLERS ==========

    async function handleUrlUpload() {
        const url = prompt("Enter Image or ZIP URL:");
        if (!url) return;

        try {
            // Use GM_xmlhttpRequest to bypass CSP restrictions
            const blob = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: url,
                    responseType: 'blob',
                    onload: (response) => {
                        if (response.status >= 200 && response.status < 300) {
                            resolve(response.response);
                        } else {
                            reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
                        }
                    },
                    onerror: (error) => {
                        reject(new Error('Failed to fetch URL'));
                    },
                    ontimeout: () => {
                        reject(new Error('Request timed out'));
                    }
                });
            });

            // Check if it's a ZIP file
            if (blob.type === 'application/zip' || blob.type === 'application/x-zip-compressed' || url.toLowerCase().endsWith('.zip')) {
                gpLog("Detected ZIP file from URL");
                await importFromZip(blob);
                notifyUser("Success", "Imported cache from URL!");
                return;
            }

            // Otherwise treat as image
            if (!blob.type.startsWith('image/')) throw new Error("Invalid image");
            processAndLoadImage(new File([blob], "url_upload.png", { type: blob.type }), false, 'Ghost Template Manager - URL Upload');
        } catch (e) {
            dbgPush(`handleUrlUpload failed: ${e && e.message ? e.message : String(e)}`, { error: e, uiComponent: 'Ghost Template Manager - URL Upload' });
            console.error(e);
            notifyUser("Error", "Could not load file from URL: " + e.message);
        }
    }

    async function downloadWithPos() {
        const savedImageData = localStorage.getItem('ghostImageData');
        if (!savedImageData) {
            notifyUser("Error", "No ghost image loaded.");
            return;
        }

        const savedCoordsStr = localStorage.getItem('ghostImageCoords');
        const img = new Image();
        img.src = savedImageData;
        await new Promise(r => img.onload = r);

        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = img.width; tempCanvas.height = img.height;
        tempCanvas.getContext('2d').drawImage(img, 0, 0);

        if (savedCoordsStr) {
            // If coordinates exist, encode them and save
            const coords = JSON.parse(savedCoordsStr);
            const encodedCanvas = encodeRobustPosition(tempCanvas, coords.gridX, coords.gridY);
            encodedCanvas.toBlob(async (blob) => {
                if(!blob) return;

                // Save to History (Cache)
                try {
                    await HistoryManager.add(blob, `Backup_${coords.gridX}_${coords.gridY}`);
                    gpLog("Cached image with position data");
                    notifyUser("Success", "Template saved to history!");
                } catch (e) {
                    dbgPush(`downloadWithPos (with coords) HistoryManager.add failed: ${e && e.message ? e.message : String(e)}`, { error: e, uiComponent: 'Ghost Template Manager - Save Pos button' });
                    console.error("Cache failed", e);
                    notifyUser("Error", "Failed to save template");
                }
            }, 'image/png');
        } else {
            // No coordinates: just save the image as-is
            tempCanvas.toBlob(async (blob) => {
                if(!blob) return;

                try {
                    await HistoryManager.add(blob, `Image_${Date.now()}`);
                    gpLog("Cached image without position data");
                    notifyUser("Success", "Template saved to history!");
                } catch (e) {
                    dbgPush(`downloadWithPos (no coords) HistoryManager.add failed: ${e && e.message ? e.message : String(e)}`, { error: e, uiComponent: 'Ghost Template Manager - Save Pos button' });
                    console.error("Cache failed", e);
                    notifyUser("Error", "Failed to save template");
                }
            }, 'image/png');
        }
    }

    async function openHistoryModal() {
        const existing = document.getElementById('gp-history-modal');
        if (existing) existing.remove();

        const images = await HistoryManager.getAll();
        const modal = document.createElement('div');
        modal.id = 'gp-history-modal';
        modal.className = 'gp-to-modal-overlay';
        modal.innerHTML = `
            <div class="gp-to-modal-panel">
                <div class="gp-to-header">
                    <div class="gp-to-header-row">
                        <span class="gp-to-title">Image History (${images.length})</span>
                        <button id="gp-close-hist" class="gp-to-btn gp-to-btn-gray">Close</button>
                    </div>
                    <div class="gp-to-header-row">
                        <button id="gp-select-all" class="gp-to-btn gp-to-btn-blue">☑ Select All</button>
                        <button id="gp-select-none" class="gp-to-btn gp-to-btn-gray">☐ Select None</button>
                        <button id="gp-export-selected" class="gp-to-btn gp-to-btn-blue" disabled style="opacity:0.5">📤 Export Selected</button>
                        <button id="gp-import-zip" class="gp-to-btn gp-to-btn-green">📁 Import JSON</button>
                    </div>
                </div>
                <div class="gp-to-grid" id="gp-history-grid">
                    ${images.length === 0 ? '<p class="p-4 text-gray-500 col-span-full text-center">No images found.</p>' : ''}
                </div>
                <div style="border-top: 1px solid var(--color-gray-300, #eee); padding-top: 10px; display: flex; justify-content: flex-end;">
                    <button id="gp-clear-all" class="gp-to-btn gp-to-btn-red">🗑️ Clear All</button>
                </div>
            </div>
        `;
        document.body.appendChild(modal);

        const grid = modal.querySelector('#gp-history-grid');
        const selectedIds = new Set();

        function updateExportSelBtn() {
            const btn = modal.querySelector('#gp-export-selected');
            if (!btn) return;
            if (selectedIds.size > 0) {
                btn.textContent = `📤 Export Selected (${selectedIds.size})`;
                btn.disabled = false;
                btn.style.opacity = '1';
                grid.classList.add('gp-sel-active');
            } else {
                btn.textContent = '📤 Export Selected';
                btn.disabled = true;
                btn.style.opacity = '0.5';
                grid.classList.remove('gp-sel-active');
            }
        }

        images.forEach(imgData => {
            const card = document.createElement('div');
            card.className = 'gp-to-card';
            card.dataset.imgId = imgData.id;
            card.innerHTML = `
                <button class="gp-to-delete-btn" title="Delete">✖</button>
                <button class="gp-to-sel-btn" title="Select for export">☐</button>
                <button class="gp-to-goto-btn" title="Load &amp; Go To">🎯</button>
                <img src="${URL.createObjectURL(imgData.blob)}" />
                <div class="gp-to-card-footer">${new Date(imgData.date).toLocaleTimeString()} - ${imgData.name.substring(0,12)}</div>
            `;
            card.onclick = async (e) => {
                if (e.target.closest('.gp-to-delete-btn')) return;
                if (e.target.closest('.gp-to-sel-btn')) return;
                if (e.target.closest('.gp-to-goto-btn')) return;
                await processAndLoadImage(imgData.blob, false, 'Image History - card click');
                await HistoryManager.add(imgData.blob, imgData.name);
                if (_settings.modernizeGhostPaletteBtns) loadRecentImages();
                modal.remove();
            };
            card.querySelector('.gp-to-delete-btn').onclick = async () => {
                await HistoryManager.delete(imgData.id);
                selectedIds.delete(imgData.id);
                updateExportSelBtn();
                card.remove();
            };
            card.querySelector('.gp-to-sel-btn').onclick = (e) => {
                e.stopPropagation();
                const selBtn = card.querySelector('.gp-to-sel-btn');
                if (selectedIds.has(imgData.id)) {
                    selectedIds.delete(imgData.id);
                    selBtn.textContent = '☐';
                    selBtn.classList.remove('selected');
                    card.classList.remove('gp-selected');
                } else {
                    selectedIds.add(imgData.id);
                    selBtn.textContent = '☑';
                    selBtn.classList.add('selected');
                    card.classList.add('gp-selected');
                }
                updateExportSelBtn();
            };
            card.querySelector('.gp-to-goto-btn').onclick = async (e) => {
                e.stopPropagation();
                // Decode coords from the image NOW before anything async happens,
                // so we have them in hand regardless of applyCoordinatesToGame's
                // setInterval timing.
                let coords = null;
                try {
                    const img = await loadImageToCanvas(imgData.blob);
                    const decoded = decodeRobustPosition(img);
                    if (decoded) coords = { gridX: decoded.gridX, gridY: decoded.gridY };
                } catch (_) {
                    dbgPush('Image History Load & Go To: failed to decode position from blob', { error: _, uiComponent: 'Image History - Load & Go To' });
                }
                modal.remove();
                await processAndLoadImage(imgData.blob, false, 'Image History - Load & Go To');
                await HistoryManager.add(imgData.blob, imgData.name);
                if (_settings.modernizeGhostPaletteBtns) loadRecentImages();
                if (coords) {
                    const goToGridLocation = getPageVariable('goToGridLocation');
                    if (typeof goToGridLocation === 'function') {
                        goToGridLocation(coords.gridX, coords.gridY);
                    }
                } else {
                    notifyUser("Load & Go", "No encoded coordinates found in this template.");
                }
            };
            grid.appendChild(card);
        });

        modal.querySelector('#gp-select-all').onclick = () => {
            images.forEach(imgData => {
                if (!selectedIds.has(imgData.id)) {
                    selectedIds.add(imgData.id);
                    const card = grid.querySelector(`[data-img-id="${imgData.id}"]`);
                    if (card) {
                        card.querySelector('.gp-to-sel-btn').textContent = '☑';
                        card.querySelector('.gp-to-sel-btn').classList.add('selected');
                        card.classList.add('gp-selected');
                    }
                }
            });
            updateExportSelBtn();
        };

        modal.querySelector('#gp-select-none').onclick = () => {
            selectedIds.clear();
            grid.querySelectorAll('.gp-to-card').forEach(card => {
                card.querySelector('.gp-to-sel-btn').textContent = '☐';
                card.querySelector('.gp-to-sel-btn').classList.remove('selected');
                card.classList.remove('gp-selected');
            });
            updateExportSelBtn();
        };

        modal.querySelector('#gp-export-selected').onclick = async () => {
            await exportSelectedToJson(new Set(selectedIds));
        };

        modal.querySelector('#gp-import-zip').onclick = () => {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.json, .zip, application/json, application/zip'; // Accept JSON (new) and ZIP (legacy)
            input.onchange = async (e) => {
                const file = e.target.files[0];
                if (file) {
                    await importFromZip(file);
                    modal.remove();
                    openHistoryModal(); // Refresh the modal
                }
            };
            input.click();
        };

        modal.querySelector('#gp-clear-all').onclick = async () => {
            if(confirm("Clear all cached images?")) {
                await HistoryManager.clear();
                modal.remove();
            }
        };
        modal.querySelector('#gp-close-hist').onclick = () => modal.remove();
    }

    // ========== INJECTION ==========

    /**
     * Watches the document for the coordinate-setting success message
     * and triggers the auto-cache function.
     * This addresses issue #2.
     */
    function setupAlertBodyObserver() {
        const targetNode = document.getElementById('alertBody');
        if (!targetNode) {
             gpLog("Could not find alertBody for position observer.");
             return;
        }

        const observer = new MutationObserver((mutationsList, observer) => {
            for(const mutation of mutationsList) {
                if (mutation.type === 'childList' || mutation.type === 'characterData') {
                    const textContent = targetNode.textContent;
                    if (textContent && textContent.includes("Ghost image position set")) {
                        gpLog("Detected 'Ghost image position set'. Triggering auto-cache.");
                        cacheCurrentGhostPosition();
                        // Disconnect after first success to avoid spamming the cache,
                        // as a new observer will be created when the modal is opened next.
                        observer.disconnect();
                        break;
                    }
                }
            }
        });

        // Start observing the target node for configured mutations
        const config = { childList: true, subtree: true, characterData: true };
        observer.observe(targetNode, config);
    }

    function injectControls() {
        const modal = document.getElementById('ghostImageModal');
        if (!modal) return;
        const container = modal.querySelector('.flex.flex-wrap.items-center.justify-center.gap-3');
        if (!container || container.dataset.gpInjected) return;
        container.dataset.gpInjected = "true";

        // 1. Remove the 'hidden' class from the hexDisplay span
        const hexDisplay = document.getElementById('hexDisplay');
        if (hexDisplay) {
            hexDisplay.classList.remove('hidden');
            gpLog("Removed 'hidden' class from hexDisplay.");
        }

        setupNativeInterceptor();

        const btnUrl = document.createElement('button');
        btnUrl.innerHTML = '🔗 URL'; btnUrl.className = 'gp-to-btn gp-to-btn-blue shadow';
        btnUrl.title = 'Load from URL (Image or ZIP)';
        btnUrl.onclick = handleUrlUpload;

        const btnLocal = document.createElement('button');
        btnLocal.innerHTML = '📂 File'; btnLocal.className = 'gp-to-btn gp-to-btn-green shadow';
        btnLocal.title = 'Upload Image or ZIP';
        // Note: The click handler for this just triggers the native input, which we intercept.
        btnLocal.onclick = () => document.getElementById('ghostImageInput').click();

        const btnHist = document.createElement('button');
        btnHist.innerHTML = '📜 History'; btnHist.className = 'gp-to-btn gp-to-btn-purple shadow';
        btnHist.onclick = openHistoryModal;

        const btnDL = document.createElement('button');
        btnDL.innerHTML = '💾 Save'; btnDL.className = 'gp-to-btn gp-to-btn-gray shadow';
        btnDL.onclick = downloadWithPos;

        const btnPreview = document.createElement('button');
        btnPreview.innerHTML = '👁️ Preview';
        btnPreview.className = 'gp-to-btn gp-to-btn-cyan shadow';
        btnPreview.title = 'Toggle image preview overlay';
        btnPreview.onclick = () => togglePreview(btnPreview);

        const btnGoTo = document.createElement('button');
        btnGoTo.innerHTML = '🎯 Go To';
        btnGoTo.className = 'gp-to-btn gp-to-btn-orange shadow';
        btnGoTo.title = 'Teleport to template location';
        btnGoTo.onclick = goToTemplateLocation;

        container.prepend(btnGoTo);
        container.prepend(btnPreview);
        container.prepend(btnDL);
        container.prepend(btnHist);
        container.prepend(btnLocal);
        container.prepend(btnUrl);

        // Auto-caching disabled - user must manually press Save Pos button
        // setupAlertBodyObserver();
    }

    const observer = new MutationObserver(() => injectControls());
    observer.observe(document.body, { childList: true, subtree: true });

    document.querySelector('label[for="ghostImageInput"]')?.classList.add('hidden');

    // ── Ghost Menu UI Overhaul ──────────────────────────────────────
    if (_settings.modernizeGhostPaletteBtns) {
        const MODERN_BTN_ORDER = [
            'Toggle All',
            'Set Palette',
            'Match My Palette',
            'Bulk Purchase Colors',
            'Enable Only Owned Ghost Colors',
            'Get Ghost Colors',
            'Get Enabled Ghost Colors',
            'Set Enabled Ghost Colors',
            'Enable filtered',
        ];

        const BTN_COLORS = {
            'Toggle All':                      { bg: '#bfdbfe', hover: '#93c5fd', fg: '#1e3a8a' },
            'Set Palette':                     { bg: '#bbf7d0', hover: '#86efac', fg: '#14532d' },
            'Match My Palette':                { bg: '#bbf7d0', hover: '#86efac', fg: '#14532d' },
            'Bulk Purchase Colors':            { bg: '#e9d5ff', hover: '#d8b4fe', fg: '#581c87' },
            'Enable Only Owned Ghost Colors':  { bg: '#fbcfe8', hover: '#f9a8d4', fg: '#831843' },
            'Get Ghost Colors':                { bg: '#fbcfe8', hover: '#f9a8d4', fg: '#831843' },
            'Get Enabled Ghost Colors':        { bg: '#fbcfe8', hover: '#f9a8d4', fg: '#831843' },
            'Set Enabled Ghost Colors':        { bg: '#fbcfe8', hover: '#f9a8d4', fg: '#831843' },
            'Enable filtered':                 { bg: '#fed7aa', hover: '#fdba74', fg: '#7c2d12' },
        };

        function styleModernBtn(btn, label) {
            if (btn.dataset.gpcStyled) return;
            btn.dataset.gpcStyled = '1';
            btn.className = '';
            const c = BTN_COLORS[label] || { bg: '#e2e8f0', hover: '#cbd5e1', fg: '#334155' };
            Object.assign(btn.style, {
                fontSize: '12px', fontWeight: '600',
                padding: '6px 8px', borderRadius: '6px',
                border: 'none', cursor: 'pointer',
                background: c.bg, color: c.fg,
                transition: 'background 0.15s',
                whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                lineHeight: '1.3',
                boxShadow: 'none', width: '100%',
                textAlign: 'center',
            });
            if (!btn.title) btn.title = label;
            btn.addEventListener('mouseenter', () => { btn.style.background = c.hover; });
            btn.addEventListener('mouseleave', () => { btn.style.background = c.bg; });
        }

        // ── Inject CSS overrides for gp-to-btn colors (one-time) ─────
        if (!document.getElementById('gpc-modern-gp-btn-style')) {
            const s = document.createElement('style');
            s.id = 'gpc-modern-gp-btn-style';
            s.textContent = `
                [data-gp-injected] button, #gpc-modern-gp-grid button {
                    font-size:12px!important; font-weight:600!important;
                    padding:6px 8px!important; border-radius:6px!important;
                    box-shadow:none!important; width:100%!important;
                    white-space:nowrap!important; overflow:hidden!important;
                    text-overflow:ellipsis!important; line-height:1.3!important;
                    text-align:center!important;
                }
                .gp-to-btn-blue   { background-color:#bfdbfe!important; color:#1e3a8a!important; }
                .gp-to-btn-blue:hover { background-color:#93c5fd!important; }
                .gp-to-btn-green  { background-color:#bbf7d0!important; color:#14532d!important; }
                .gp-to-btn-green:hover { background-color:#86efac!important; }
                .gp-to-btn-purple { background-color:#e9d5ff!important; color:#581c87!important; }
                .gp-to-btn-purple:hover { background-color:#d8b4fe!important; }
                .gp-to-btn-gray   { background-color:#f1f5f9!important; color:#475569!important; }
                .gp-to-btn-gray:hover { background-color:#e2e8f0!important; }
                .gp-to-btn-orange { background-color:#fed7aa!important; color:#7c2d12!important; }
                .gp-to-btn-orange:hover { background-color:#fdba74!important; }
                .gp-to-btn-cyan   { background-color:#a5f3fc!important; color:#164e63!important; border:none!important; }
                .gp-to-btn-cyan:hover { background-color:#67e8f9!important; }
                .gp-to-btn-cyan.active { background-color:#0e7490!important; color:#ecfeff!important; border:2px solid #fbbf24!important; box-shadow:0 0 0 3px rgba(251,191,36,.3)!important; }
                .gp-to-btn-red    { background-color:#fecaca!important; color:#991b1b!important; }
                .gp-to-btn-red:hover { background-color:#fca5a5!important; }
                #initiatePlaceGhostBtn { background-color:#bbf7d0!important; color:#14532d!important; }
                #initiatePlaceGhostBtn:hover:not(:disabled) { background-color:#86efac!important; }
                #clearGhostImageBtn    { background-color:#fecaca!important; color:#991b1b!important; }
                #clearGhostImageBtn:hover:not(:disabled) { background-color:#fca5a5!important; }
                #_z_dock_btn { background-color:#c7d2fe!important; color:#312e81!important; }
                #_z_dock_btn:hover { background-color:#a5b4fc!important; }
            `;
            document.head.appendChild(s);
        }

        function applyModernPaletteButtons() {
            const container = document.getElementById('ghostColorPaletteContainer');
            if (!container) return;

            // Remove the ✚₊ collapse toggle and unhide all siblings each time it appears
            container.querySelectorAll('button[data-expanded]').forEach(toggleBtn => {
                let sib = toggleBtn.nextElementSibling;
                while (sib) {
                    if (sib.tagName === 'BUTTON') sib.style.removeProperty('display');
                    sib = sib.nextElementSibling;
                }
                toggleBtn.remove();
            });

            // Collect all action buttons — exclude swatches and search input area,
            // but include the Enable filtered button from .gpc-filtered-row
            const allBtns = Array.from(container.querySelectorAll('button')).filter(btn => {
                if (btn.closest('#ghostColorPalette')) return false;
                if (btn.closest('.color-search-container')) return false;
                if (btn.id === 'gpc-modern-btn-grid') return false;
                return true;
            });

            // Skip if nothing has changed since last run
            const sig = allBtns.map(b => b.textContent.trim()).sort().join('|');
            if (container.dataset.gpcSig === sig) return;
            container.dataset.gpcSig = sig;

            // Build label → button map
            const btnMap = new Map();
            allBtns.forEach(btn => btnMap.set(btn.textContent.trim(), btn));

            // Find or create the 2-column grid
            let grid = document.getElementById('gpc-modern-btn-grid');
            if (!grid) {
                grid = document.createElement('div');
                grid.id = 'gpc-modern-btn-grid';
                grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:0 0 8px 0;';
                const h3 = container.querySelector('h3');
                if (h3) h3.insertAdjacentElement('afterend', grid);
                else container.prepend(grid);
            }

            // Move buttons into the grid in the specified order
            MODERN_BTN_ORDER.forEach(label => {
                const btn = btnMap.get(label);
                if (!btn) return;
                styleModernBtn(btn, label);
                grid.appendChild(btn);
            });

            // Hide the now-empty .gpc-filtered-row (its button moved to the grid)
            const filteredRow = container.querySelector('.gpc-filtered-row');
            if (filteredRow && !filteredRow.querySelector('button')) {
                filteredRow.style.display = 'none';
            }

            // Remove now-empty wrapper divs left by the site's layout.
            // Protect any div with an id (ghostColorPalette, etc.) and the search/filter UI.
            Array.from(container.querySelectorAll(':scope > div')).forEach(div => {
                if (div.id) return; // never remove a named element
                if (div.closest('.color-search-container') || div.closest('.gpc-filtered-row')) return;
                if (div.querySelector('button, input, label, select')) return;
                div.remove();
            });
        }

        // ── 4-column grid for the gp-injected action buttons ─────────
        function applyModernGpButtons() {
            const injectedDiv = document.querySelector('[data-gp-injected="true"]');
            if (!injectedDiv) return;

            const allBtns = Array.from(injectedDiv.querySelectorAll('button'));
            if (!allBtns.length) return;

            const sig = allBtns.map(b => (b.id || '') + b.textContent.trim()).sort().join('|');
            if (injectedDiv.dataset.gpcGpSig === sig) return;
            injectedDiv.dataset.gpcGpSig = sig;

            let grid = document.getElementById('gpc-modern-gp-grid');
            if (!grid) {
                grid = document.createElement('div');
                grid.id = 'gpc-modern-gp-grid';
                grid.style.cssText = 'display:grid;grid-template-columns:repeat(4,1fr);gap:6px;padding:0 0 8px 0;';
                injectedDiv.parentNode.insertBefore(grid, injectedDiv);
            }

            allBtns.forEach(btn => grid.appendChild(btn));
            injectedDiv.style.display = 'none';
        }

        const paletteContainerObs = new MutationObserver(() => {
            applyModernPaletteButtons();
            applyModernGpButtons();
            applyModernGhostModalLayout();
        });
        paletteContainerObs.observe(document.body, { childList: true, subtree: true });
        applyModernPaletteButtons();
        applyModernGpButtons();
        applyModernGhostModalLayout();
    }

    // ── Full Ghost Modal Layout Overhaul ─────────────────────────────────
    // (runs independently of modernizeGhostPaletteBtns / Ghost Menu UI Overhaul; called above when that setting is on)
    let _recentBlobUrls = [];

    function applyModernGhostModalLayout() {
        const modal = document.getElementById('ghostImageModal');
        if (!modal || modal.dataset.gpcModernModal) return;
        // Wait until injectControls has injected the buttons
        if (!modal.querySelector('[data-gp-injected]')) return;
        modal.dataset.gpcModernModal = '1';

        const dark = () => document.body.classList.contains('dark');

        // ── CSS for children only — no width/height rules on the modal itself here.
        //   The modal's own dimensions are set via inline styles below (after stripping
        //   the conflicting Tailwind classes). This way the resize handler can simply
        //   write modal.style.width / modal.style.height with no !important fights.
        if (!document.getElementById('gpc-modal-layout-style')) {
            const s = document.createElement('style');
            s.id = 'gpc-modal-layout-style';
            s.textContent = `
                #gpc-modal-left {
                    flex: 1 1 0;
                    min-width: 0;
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                }
                #gpc-modal-left-scroll {
                    flex: 1 1 0;
                    min-height: 0;
                    overflow-y: auto;
                    padding: 10px 14px;
                }
                #gpc-modal-right {
                    width: 280px;
                    flex-shrink: 0;
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                    transition: width 0.22s ease, opacity 0.18s ease;
                }
                #ghostImageModal.gpc-preview-animating {
                    transition: width 0.22s ease;
                    overflow: hidden;
                }
                #ghostImageModal.gpc-preview-collapsed {
                    width: var(--gpc-collapsed-width) !important;
                }
                #ghostImageModal.gpc-preview-expanded {
                    width: var(--gpc-expanded-width) !important;
                }
                #gpc-modal-right.gpc-collapsed {
                    width: 34px !important;
                    opacity: 0.95;
                }
                #gpc-modal-right.gpc-collapsed #gpc-modal-right-content {
                    opacity: 0;
                    pointer-events: none;
                }
                #gpc-modal-right.gpc-collapsed .gpc-collapse-label { display: none !important; }
                #gpc-modal-right-content {
                    flex: 1 1 0;
                    min-height: 0;
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                    opacity: 1;
                    transition: opacity 0.12s ease;
                }
                #gpc-recent-grid {
                    display: grid;
                    grid-template-columns: repeat(5,1fr);
                    gap: 3px;
                    padding: 4px 8px 8px;
                    overflow-y: auto;
                    flex: 1 1 0;
                    min-height: 0;
                    align-content: start;
                }
                /* Remove the palette's hardcoded max-height so it expands with content */
                #ghostColorPalette {
                    max-height: none !important;
                    overflow-y: visible !important;
                }
                /* Action buttons grid inside the right panel — force 2-col minimum */
                #gpc-modal-right #gpc-modern-gp-grid {
                    display: grid !important;
                    grid-template-columns: repeat(2, 1fr) !important;
                    gap: 4px !important;
                    padding: 4px 0 !important;
                }
                /* Edge drag strips */
                .gpc-edge-drag {
                    position: absolute;
                    z-index: 102;
                    pointer-events: auto;
                    cursor: grab;
                }
                .gpc-edge-drag:active { cursor: grabbing; }
                .gpc-edge-drag.top    { top: 0;    left: 12px; right: 12px; height: 12px; }
                .gpc-edge-drag.bottom { bottom: 0; left: 12px; right: 30px; height: 12px; }
                .gpc-edge-drag.left   { left: 0;   top: 12px;  bottom: 12px; width: 12px; }
                /* .right is a child of #gpc-modal-right, not modal — see JS below */
                .gpc-edge-drag.right  { right: 0;  top: 0; bottom: 30px; width: 12px; z-index: 50; }
                /* Panel splitter — left edge of #gpc-modal-right, drag to resize the split */
                .gpc-panel-splitter {
                    position: absolute;
                    left: -4px; top: 0; bottom: 0;
                    width: 8px;
                    cursor: ew-resize;
                    z-index: 106;
                    background: transparent;
                    transition: background 0.15s;
                }
                .gpc-panel-splitter:hover,
                .gpc-panel-splitter.gpc-ps-dragging {
                    background: rgba(99,102,241,0.25);
                }
                #gpc-modal-right.gpc-collapsed .gpc-panel-splitter { display: none; }
                /* Horizontal preview-height resize strip */
                .gpc-preview-hresize {
                    flex-shrink: 0;
                    height: 8px;
                    cursor: ns-resize;
                    position: relative;
                    z-index: 10;
                    background: transparent;
                    transition: background 0.15s;
                }
                .gpc-preview-hresize::after {
                    content: '';
                    position: absolute;
                    left: 12px; right: 12px;
                    top: 50%; transform: translateY(-50%);
                    height: 1px;
                    background: var(--color-gray-200, #e5e7eb);
                    transition: background 0.15s;
                }
                .gpc-preview-hresize:hover,
                .gpc-preview-hresize.gpc-ps-dragging {
                    background: rgba(99,102,241,0.15);
                }
                .gpc-preview-hresize:hover::after,
                .gpc-preview-hresize.gpc-ps-dragging::after {
                    background: rgba(99,102,241,0.6);
                }
                /* SE resize grip — bottom-right corner only, like guild modal */
                .gpc-resize-se {
                    position: absolute;
                    bottom: 0; right: 0;
                    width: 20px; height: 20px;
                    cursor: nwse-resize;
                    z-index: 101;
                    border-radius: 0 0 1rem 0;
                    background: linear-gradient(135deg, transparent 50%, #94a3b8 50%);
                    opacity: 0.5;
                    pointer-events: auto;
                    transition: opacity 0.15s;
                }
                .gpc-resize-se:hover { opacity: 1; }
            `;
            document.head.appendChild(s);
        }

        // Strip the Tailwind classes that fight with our layout so plain inline styles win.
        ['w-[90%]', 'max-w-lg', 'p-6', 'max-h-[90vh]', 'cursor-move'].forEach(c => modal.classList.remove(c));
        // Set initial modal dimensions as plain inline styles (no !important needed now).
        Object.assign(modal.style, {
            width:        'min(92vw, 800px)',
            height:       '75vh',
            maxWidth:     'none',
            maxHeight:    'none',
            minWidth:     '480px',
            minHeight:    '320px',
            flexDirection:'row',
            gap:          '0',
            padding:      '0',
            alignItems:   'stretch',
        });

        // ── Locate existing DOM nodes ─────────────────────────────
        const closeBtn   = modal.querySelector('button[onclick*="toggleGhostModal"]');
        const h2         = modal.querySelector('h2');
        const scrollArea = modal.querySelector('.flex-grow.overflow-y-auto, .overflow-y-auto');

        // Find the preview container: get the DIRECT parent of #ghostPreviewImage,
        // not just any ancestor (querySelectorAll picks the outermost wrapper first).
        let previewContainer = null;
        if (scrollArea) {
            const _pImg = scrollArea.querySelector('#ghostPreviewImage');
            if (_pImg) {
                previewContainer = _pImg.parentElement;
            } else {
                previewContainer = scrollArea.querySelector('[class*="border-dashed"]');
            }
        }
        if (previewContainer?.parentNode) previewContainer.parentNode.removeChild(previewContainer);

        // Extract the buttons grid.
        // applyModernGpButtons() runs first and moves all buttons from [data-gp-injected]
        // into #gpc-modern-gp-grid, leaving the original div hidden. Grab the grid;
        // fall back to the raw injected div if the grid hasn't been created yet.
        const buttonsRow = scrollArea
            ? (scrollArea.querySelector('#gpc-modern-gp-grid') || scrollArea.querySelector('[data-gp-injected]'))
            : null;
        if (buttonsRow?.parentNode) buttonsRow.parentNode.removeChild(buttonsRow);
        // Also detach the now-empty (hidden) injected wrapper so it doesn't linger in scrollArea.
        const _injectedShell = scrollArea ? scrollArea.querySelector('[data-gp-injected]') : null;
        if (_injectedShell?.parentNode) _injectedShell.parentNode.removeChild(_injectedShell);

        // ── Left panel ───────────────────────────────────────────────
        const leftPanel = document.createElement('div');
        leftPanel.id = 'gpc-modal-left';

        // Header — also the drag handle.
        // IMPORTANT: makeDraggable(modal) in the site source uses `if (e.target !== elmnt) return`
        // which means the site drag ONLY fires when clicking the bare modal surface (impossible
        // once it's fully covered by children). We implement our own drag here instead.
        const leftHeader = document.createElement('div');
        leftHeader.id = 'gpc-modal-left-header';
        leftHeader.className = 'flex items-center justify-between px-4 py-3 border-b border-gray-100 dark:border-gray-700 cursor-move select-none flex-shrink-0 bg-white dark:bg-gray-800';
        if (h2) { h2.style.cssText = 'margin:0;font-size:1.05rem;font-weight:700;'; leftHeader.appendChild(h2); }
        // Restyle the close button for the header context.
        // Strip absolute/shadow/bg-white classes (bg-white on white header = invisible);
        // give it a visible, theme-aware appearance instead.
        const effectiveCloseBtn = closeBtn || (() => {
            const b = document.createElement('button');
            b.innerHTML = '&#10005;';
            b.onclick = () => { if (typeof toggleGhostModal === 'function') toggleGhostModal(false); };
            return b;
        })();
        effectiveCloseBtn.className = 'flex-shrink-0 rounded-full cursor-pointer flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100';
        effectiveCloseBtn.style.cssText = 'position:static;border:none;background:transparent;width:2rem;height:2rem;font-size:1.1rem;line-height:1;flex-shrink:0;';
        leftHeader.appendChild(effectiveCloseBtn);
        leftPanel.appendChild(leftHeader);

        // ── Shared drag logic — used by header and all 4 edge strips ────
        function _startDrag(e) {
            if (e.button !== 0 || e.target.closest('button')) return;
            e.preventDefault();
            const r = modal.getBoundingClientRect();
            let st = { mx: e.clientX, my: e.clientY, l: r.left, t: r.top };
            const prevCursor = document.body.style.cursor;
            document.body.style.cursor = 'grabbing';
            const onMove = ev => {
                modal.style.left      = (st.l + ev.clientX - st.mx) + 'px';
                modal.style.top       = (st.t + ev.clientY - st.my) + 'px';
                modal.style.transform = 'none';
            };
            const onUp = () => {
                document.body.style.cursor = prevCursor;
                document.removeEventListener('mousemove', onMove, true);
                document.removeEventListener('mouseup',   onUp,   true);
            };
            document.addEventListener('mousemove', onMove, true);
            document.addEventListener('mouseup',   onUp,   true);
        }
        leftHeader.style.cursor = 'grab';
        leftHeader.addEventListener('mousedown', _startDrag);

        if (scrollArea) {
            scrollArea.id = 'gpc-modal-left-scroll';
            // Strip the old Tailwind overflow/grow classes that conflict with the new layout
            scrollArea.className = scrollArea.className
                .replace(/\bflex-grow\b|\boverflow-y-auto\b|\bpr-\S+\b|\b-mr-\S+\b/g, '').trim();
            leftPanel.appendChild(scrollArea);
        }

        // ── Right panel ──────────────────────────────────────────────
        const rightPanel = document.createElement('div');
        rightPanel.id = 'gpc-modal-right';
        // position:relative so the right edge strip can be absolutely positioned inside it
        rightPanel.style.position = 'relative';
        // Use Tailwind dark: variants so colors track the site's active theme
        rightPanel.className = 'border-l border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800';

        // Collapse bar
        const collapseBar = document.createElement('div');
        collapseBar.className = 'flex items-center gap-1 px-2 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 select-none';
        collapseBar.style.minHeight = '34px';

        const collapseBtn = document.createElement('button');
        collapseBtn.className = 'rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-600 dark:text-gray-300 flex-shrink-0 flex items-center justify-center border-none cursor-pointer';
        collapseBtn.style.cssText = 'width:20px;height:20px;font-size:8px;padding:0;line-height:1;';
        collapseBtn.textContent = '◀';
        collapseBtn.title = 'Collapse preview';

        const collapseLabel = document.createElement('span');
        collapseLabel.className = 'gpc-collapse-label text-xs font-bold text-gray-500 dark:text-gray-400';
        collapseLabel.textContent = '🖼 Preview';

        collapseBar.appendChild(collapseBtn);
        collapseBar.appendChild(collapseLabel);
        rightPanel.appendChild(collapseBar);

        const rightContent = document.createElement('div');
        rightContent.id = 'gpc-modal-right-content';

        // Preview image
        if (previewContainer) {
            previewContainer.className = '';
            previewContainer.style.cssText = 'flex-shrink:0;margin:10px;border-radius:8px;overflow:hidden;background:var(--color-gray-100,#f3f4f6);border:1px solid var(--color-gray-200,#e5e7eb);display:flex;align-items:center;justify-content:center;aspect-ratio:4/3;max-height:340px;position:relative;';
            const pImg = previewContainer.querySelector('#ghostPreviewImage');
            if (pImg) pImg.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;object-fit:contain;image-rendering:pixelated;image-rendering:crisp-edges;';
            const pTxt = previewContainer.querySelector('#ghostPreviewText');
            if (pTxt) { pTxt.className = 'text-gray-400 dark:text-gray-500 text-xs text-center'; pTxt.style.cssText = 'position:relative;z-index:1;padding:8px;'; }
            rightContent.appendChild(previewContainer);

            // Horizontal resize strip — drag to set preview height freely
            const previewHResize = document.createElement('div');
            previewHResize.className = 'gpc-preview-hresize';
            previewHResize.title = 'Drag to resize preview height';
            previewHResize.addEventListener('mousedown', e => {
                if (e.button !== 0) return;
                e.preventDefault(); e.stopPropagation();
                const startY = e.clientY;
                const startH = previewContainer.getBoundingClientRect().height;
                // Switch to explicit height so the user can go above the aspect-ratio default
                previewContainer.style.height = startH + 'px';
                previewContainer.style.maxHeight = 'none';
                previewHResize.classList.add('gpc-ps-dragging');
                document.body.style.cursor = 'ns-resize';
                const onMove = ev => {
                    const newH = Math.max(60, startH + ev.clientY - startY);
                    previewContainer.style.height = newH + 'px';
                };
                const onUp = () => {
                    previewHResize.classList.remove('gpc-ps-dragging');
                    document.body.style.cursor = '';
                    document.removeEventListener('mousemove', onMove, true);
                    document.removeEventListener('mouseup',   onUp,   true);
                };
                document.addEventListener('mousemove', onMove, true);
                document.addEventListener('mouseup',   onUp,   true);
            });
            rightContent.appendChild(previewHResize);
        }

        // Buttons row (URL, File, History, Save Pos, Place on Map, Clear Image …)
        if (buttonsRow) {
            const buttonsWrap = document.createElement('div');
            buttonsWrap.className = 'flex-shrink-0 px-2 py-2';
            // Preserve the grid layout applyModernGpButtons set; just ensure it shows.
            buttonsRow.style.display = '';
            buttonsWrap.appendChild(buttonsRow);
            rightContent.appendChild(buttonsWrap);
        }

        // Recent images section
        const recentWrap = document.createElement('div');
        recentWrap.className = 'border-t border-gray-200 dark:border-gray-700';
        recentWrap.style.cssText = 'flex:1 1 0;min-height:0;display:flex;flex-direction:column;overflow:hidden;';

        const recentHdr = document.createElement('div');
        recentHdr.className = 'flex items-center px-2 gap-1 select-none cursor-pointer flex-shrink-0 text-xs font-bold text-gray-500 dark:text-gray-400';
        recentHdr.style.minHeight = '28px';

        const recentArrow = document.createElement('span');
        recentArrow.textContent = '▾';
        recentArrow.style.cssText = 'transition:transform 0.15s;flex-shrink:0;';

        const recentTitle = document.createElement('span');
        recentTitle.textContent = '🕐 Recent';

        const recentRefreshBtn = document.createElement('button');
        recentRefreshBtn.textContent = '↺';
        recentRefreshBtn.title = 'Refresh';
        recentRefreshBtn.className = 'ml-auto border-none bg-transparent cursor-pointer text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300';
        recentRefreshBtn.style.cssText = 'font-size:13px;padding:0 2px;line-height:1;';
        recentRefreshBtn.addEventListener('click', e => { e.stopPropagation(); loadRecentImages(); });

        recentHdr.appendChild(recentArrow);
        recentHdr.appendChild(recentTitle);
        recentHdr.appendChild(recentRefreshBtn);

        const recentGrid = document.createElement('div');
        recentGrid.id = 'gpc-recent-grid';

        let recentOpen = true;
        recentHdr.addEventListener('click', () => {
            recentOpen = !recentOpen;
            recentGrid.style.display = recentOpen ? 'grid' : 'none';
            recentArrow.style.transform = recentOpen ? '' : 'rotate(-90deg)';
        });

        recentWrap.appendChild(recentHdr);
        recentWrap.appendChild(recentGrid);
        rightContent.appendChild(recentWrap);
        rightPanel.appendChild(rightContent);

        // Collapse toggle
        let rightCollapsed = false;
        let _expandedModalWidth = null;
        collapseBtn.addEventListener('click', e => {
            e.stopPropagation();
            rightCollapsed = !rightCollapsed;
            if (rightCollapsed) {
                modal.classList.remove('gpc-preview-expanded');
                const currentW = Math.round(modal.getBoundingClientRect().width);
                const rightW = Math.round(rightPanel.getBoundingClientRect().width);
                const collapsedW = Math.max(320, currentW - rightW + 34);
                _expandedModalWidth = `${currentW}px`;
                modal.style.setProperty('--gpc-expanded-width', _expandedModalWidth);
                modal.style.setProperty('--gpc-collapsed-width', `${collapsedW}px`);
                modal.classList.add('gpc-preview-animating', 'gpc-preview-collapsed');
                rightPanel.classList.add('gpc-collapsed');
            } else {
                modal.classList.remove('gpc-preview-collapsed');
                modal.style.setProperty('--gpc-expanded-width', _expandedModalWidth || 'min(92vw, 800px)');
                modal.classList.add('gpc-preview-animating', 'gpc-preview-expanded');
                rightPanel.classList.remove('gpc-collapsed');
                _expandedModalWidth = null;
            }
            collapseBtn.textContent = rightCollapsed ? '▶' : '◀';
            collapseBtn.title = rightCollapsed ? 'Expand preview' : 'Collapse preview';
        });

        modal.addEventListener('transitionend', e => {
            if (e.target !== modal || e.propertyName !== 'width') return;
            if (!rightCollapsed) {
                modal.classList.remove('gpc-preview-expanded');
                modal.style.width = modal.style.getPropertyValue('--gpc-expanded-width') || 'min(92vw, 800px)';
            } else {
                modal.style.width = modal.style.getPropertyValue('--gpc-collapsed-width');
            }
            modal.classList.remove('gpc-preview-animating');
        });

        // ── SE resize handle (bottom-right only — guild modal pattern) ──────────
        const resizeHandle = document.createElement('div');
        resizeHandle.className = 'gpc-resize-se';

        let _rs = null;
        resizeHandle.addEventListener('mousedown', e => {
            e.preventDefault(); e.stopPropagation();
            const r = modal.getBoundingClientRect();
            _rs = { mx: e.clientX, my: e.clientY, w: r.width, h: r.height };
            // Disable any active transition so resize is instant
            modal.classList.remove('gpc-preview-animating', 'gpc-preview-expanded');
            modal.style.userSelect = 'none';
            const onRsMove = ev => {
                if (!_rs) return;
                const minW = rightCollapsed ? 320 : 480;
                const nextW = Math.max(minW, _rs.w + ev.clientX - _rs.mx) + 'px';
                modal.style.width = nextW;
                // Keep the active CSS variable in sync so !important rules don't fight the resize
                if (rightCollapsed) {
                    modal.style.setProperty('--gpc-collapsed-width', nextW);
                } else {
                    _expandedModalWidth = nextW;
                    modal.style.setProperty('--gpc-expanded-width', nextW);
                }
                modal.style.height = Math.max(320, _rs.h + ev.clientY - _rs.my) + 'px';
            };
            const onRsUp = () => {
                _rs = null;
                modal.style.userSelect = '';
                document.removeEventListener('mousemove', onRsMove, true);
                document.removeEventListener('mouseup',   onRsUp,   true);
            };
            document.addEventListener('mousemove', onRsMove, true);
            document.addEventListener('mouseup',   onRsUp,   true);
        });

        // ── Assemble ──────────────────────────────────────────────────
        while (modal.firstChild) modal.removeChild(modal.firstChild);
        modal.appendChild(leftPanel);
        modal.appendChild(rightPanel);
        // Top / bottom / left edge strips are children of modal (absolute vs modal)
        ['top', 'bottom', 'left'].forEach(side => {
            const strip = document.createElement('div');
            strip.className = `gpc-edge-drag ${side}`;
            strip.style.cursor = 'grab';  // inline beats CSS class specificity
            strip.addEventListener('mousedown', _startDrag);
            modal.appendChild(strip);
        });
        // Right edge strip must be a child of rightPanel to beat its stacking context
        const rightStrip = document.createElement('div');
        rightStrip.className = 'gpc-edge-drag right';
        rightStrip.style.cursor = 'grab';
        rightStrip.addEventListener('mousedown', _startDrag);
        rightPanel.appendChild(rightStrip);

        // ── Panel splitter (left edge of rightPanel — drag to resize split ratio) ──
        const panelSplitter = document.createElement('div');
        panelSplitter.className = 'gpc-panel-splitter';
        panelSplitter.title = 'Drag to resize preview panel';
        panelSplitter.addEventListener('mousedown', e => {
            if (e.button !== 0 || rightCollapsed) return;
            e.preventDefault(); e.stopPropagation();
            const startX = e.clientX;
            const startW = rightPanel.getBoundingClientRect().width;
            rightPanel.style.transition = 'none';
            panelSplitter.classList.add('gpc-ps-dragging');
            document.body.style.cursor = 'ew-resize';
            const onMove = ev => {
                const delta = startX - ev.clientX; // drag left → wider right panel
                const modalW = modal.getBoundingClientRect().width;
                const newW = Math.min(Math.max(160, startW + delta), Math.floor(modalW * 0.7));
                rightPanel.style.width = newW + 'px';
            };
            const onUp = () => {
                panelSplitter.classList.remove('gpc-ps-dragging');
                document.body.style.cursor = '';
                rightPanel.style.transition = '';
                document.removeEventListener('mousemove', onMove, true);
                document.removeEventListener('mouseup',   onUp,   true);
            };
            document.addEventListener('mousemove', onMove, true);
            document.addEventListener('mouseup',   onUp,   true);
        });
        rightPanel.appendChild(panelSplitter);

        modal.appendChild(resizeHandle);

        loadRecentImages();
    }

    async function loadRecentImages() {
        const grid = document.getElementById('gpc-recent-grid');
        if (!grid) return;
        _recentBlobUrls.forEach(u => URL.revokeObjectURL(u));
        _recentBlobUrls = [];
        const isDark = document.body.classList.contains('dark');
        const loadingColor = isDark ? '#6b7280' : '#94a3b8';
        grid.innerHTML = `<span style="font-size:10px;color:${loadingColor};padding:4px;grid-column:span 5">Loading…</span>`;
        try {
            const images = (await HistoryManager.getAll()).slice(0, 10);
            grid.innerHTML = '';
            if (!images.length) {
                grid.innerHTML = `<span style="font-size:10px;color:${loadingColor};padding:4px;grid-column:span 5">No history yet</span>`;
                return;
            }
            images.forEach(imgData => {
                const blobUrl = URL.createObjectURL(imgData.blob);
                _recentBlobUrls.push(blobUrl);
                const thumb = document.createElement('div');
                // Use Tailwind dark: variants — same approach as the rest of the modal
                thumb.className = 'rounded bg-gray-200 dark:bg-gray-700 cursor-pointer';
                thumb.style.cssText = 'aspect-ratio:1/1;overflow:hidden;border:2px solid transparent;transition:border-color 0.15s;';
                thumb.title = imgData.name;
                const img = document.createElement('img');
                img.src = blobUrl;
                img.style.cssText = 'width:100%;height:100%;object-fit:cover;image-rendering:pixelated;display:block;';
                thumb.appendChild(img);
                thumb.addEventListener('mouseenter', () => { thumb.style.borderColor = '#3b82f6'; });
                thumb.addEventListener('mouseleave', () => { thumb.style.borderColor = 'transparent'; });
                thumb.addEventListener('click', async () => {
                    await processAndLoadImage(imgData.blob, false, 'Recent thumbnails');
                    await HistoryManager.add(imgData.blob, imgData.name);
                    loadRecentImages();
                });
                grid.appendChild(thumb);
            });
        } catch(e) {
            grid.innerHTML = '<span style="font-size:10px;color:#ef4444;padding:4px;grid-column:span 5">Failed to load</span>';
        }
    }

    gpLog("GeoPixels Ultimate Ghost Template Manager v3.4 Loaded (with uint8array ZIP fix)");

            })();
            _featureStatus.ghostTemplateManager = 'ok';
            console.log('[GeoPixelcons++] ✅ Ghost Template Manager loaded');
        } catch (err) {
            _featureStatus.ghostTemplateManager = 'error';
            dbgPush(`Ghost Template Manager init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Ghost Template Manager' });
            console.error('[GeoPixelcons++] ❌ Ghost Template Manager failed:', err);
        }
    }


    // ============================================================
    //  SETTING: Disable Group Noise [disableGroupNoise]
    // ============================================================
    if (_settings.disableGroupNoise) {
        try {
            function enforceNoGroupNoise() {
                const toggle = document.getElementById('groupNoiseToggle');
                if (!toggle) return false;

                // Uncheck it
                if (toggle.checked) {
                    toggle.checked = false;
                    // Fire change event so ghost22.js re-processes if a template is loaded
                    if (typeof handleGroupingToggle === 'function') {
                        handleGroupingToggle();
                    }
                }

                // Gray out the toggle and its label, add "disabled" text
                toggle.disabled = true;
                toggle.style.opacity = '0.4';
                toggle.style.cursor = 'not-allowed';
                const container = toggle.closest('label') || toggle.parentElement;
                if (container) {
                    container.style.opacity = '0.4';
                    container.style.cursor = 'not-allowed';
                    // Add "disabled" badge if not already present
                    if (!container.querySelector('.gpc-noise-disabled-badge')) {
                        const badge = document.createElement('span');
                        badge.className = 'gpc-noise-disabled-badge';
                        badge.style.cssText = 'font-size:10px;font-weight:600;color:#ef4444;margin-left:6px;vertical-align:middle;';
                        badge.textContent = '(disabled by GeoPixelcons++)';
                        container.appendChild(badge);
                    }
                }

                // Intercept any programmatic re-checking via a property override
                Object.defineProperty(toggle, 'checked', {
                    get() { return false; },
                    set() { /* no-op: group noise is permanently disabled */ },
                    configurable: true
                });

                return true;
            }

            // Try immediately, then observe for the ghost modal being opened
            if (!enforceNoGroupNoise()) {
                const noiseObserver = new MutationObserver(() => {
                    if (enforceNoGroupNoise()) noiseObserver.disconnect();
                });
                noiseObserver.observe(document.body, { childList: true, subtree: true });
                setTimeout(() => noiseObserver.disconnect(), 60000);
            }

            _featureStatus.disableGroupNoise = 'ok';
            console.log('[GeoPixelcons++] ✅ Disable Group Noise loaded');
        } catch (err) {
            _featureStatus.disableGroupNoise = 'error';
            dbgPush(`Disable Group Noise init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Disable Group Noise' });
            console.error('[GeoPixelcons++] ❌ Disable Group Noise failed:', err);
        }
    }

    // ============================================================
    //  SETTING: Start in Shift Lock [startShiftLock]
    // ============================================================
    if (_settings.startShiftLock) {
        try {
            const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;

            // `shiftDown` is a closure variable inside the site's script — NOT a
            // window property (window.shiftDown is always undefined). We cannot read
            // or write it directly. Instead we must go through toggleShiftDown() to
            // actually flip it. We wrap toggleShiftDown first so we can track the
            // current lock state ourselves, then use that in the FindRandomArt patch.
            let _lockOn = false;

            if (typeof _pw.toggleShiftDown === 'function') {
                const _origToggle = _pw.toggleShiftDown;
                _pw.toggleShiftDown = function () {
                    _lockOn = !_lockOn;
                    dbgPush('toggleShiftDown: _lockOn \u2192 ' + _lockOn);
                    return _origToggle.apply(this, arguments);
                };
            }

            // Enable lock on startup (goes through our wrapper, so _lockOn = true).
            if (typeof _pw.toggleShiftDown === 'function') {
                _pw.toggleShiftDown();
                console.log('[GeoPixelcons++] \u2705 Shift Lock enabled on startup');
                dbgPush('startShiftLock: enabled on startup, _lockOn=' + _lockOn);
            }

            // Patch FindRandomArt: temporarily call toggleShiftDown() to flip the
            // closure shiftDown to false before the call, then restore it after.
            if (typeof _pw.FindRandomArt === 'function') {
                const _origFRA = _pw.FindRandomArt;
                _pw.FindRandomArt = async function () {
                    const _wasLocked = _lockOn;
                    if (_wasLocked && typeof _pw.toggleShiftDown === 'function') {
                        _pw.toggleShiftDown(); // _lockOn=false, closure shiftDown=false
                    }
                    try {
                        return await _origFRA.apply(this, arguments);
                    } finally {
                        if (_wasLocked && typeof _pw.toggleShiftDown === 'function') {
                            _pw.toggleShiftDown(); // _lockOn=true, closure shiftDown=true
                        }
                    }
                };
                dbgPush('startShiftLock: FindRandomArt patched');
            }

            _featureStatus.startShiftLock = 'ok';
        } catch (err) {
            _featureStatus.startShiftLock = 'error';
            dbgPush(`Start in Shift Lock init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Start in Shift Lock' });
            console.error('[GeoPixelcons++] \u274C Start in Shift Lock failed:', err);
        }
    }

    // ============================================================
    //  SETTING: Start in Inspect Mode [startInspectMode]
    // ============================================================
    if (_settings.startInspectMode) {
        try {
            const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;

            // Wait for the map to be fully ready (canvas contexts exist)
            // before toggling mode — prevents white-screen on load
            function waitForMapReady(cb, maxWait) {
                maxWait = maxWait || 20000;
                const start = Date.now();
                function check() {
                    if (_pw.map && typeof _pw.map.getCanvas === 'function' &&
                        document.getElementById('pixel-canvas') &&
                        document.getElementById('pixel-canvas').getContext) {
                        cb(); return;
                    }
                    if (Date.now() - start >= maxWait) return;
                    setTimeout(check, 300);
                }
                check();
            }

            // Wait for the togglePrimaryModeBtn to exist and its title to
            // stabilize (stop changing) before reading the current mode
            function waitForEl(id, cb) {
                const el = document.getElementById(id);
                if (el) { cb(el); return; }
                const obs = new MutationObserver(() => {
                    const found = document.getElementById(id);
                    if (found) { obs.disconnect(); cb(found); }
                });
                obs.observe(document.body, { childList: true, subtree: true });
                setTimeout(() => obs.disconnect(), 15000);
            }

            function waitForStableTitle(el, cb) {
                let timer = null;
                let done = false;
                function fire() {
                    if (done) return;
                    done = true;
                    clearTimeout(timer);
                    titleObs.disconnect();
                    cb(el);
                }
                const titleObs = new MutationObserver(() => {
                    clearTimeout(timer);
                    timer = setTimeout(fire, 500);
                });
                titleObs.observe(el, { attributes: true, attributeFilter: ['title'] });
                timer = setTimeout(fire, 500);
                setTimeout(fire, 6000);
            }

            waitForEl('togglePrimaryModeBtn', (btn) => {
                waitForMapReady(() => {
                    waitForStableTitle(btn, () => {
                        // title "Switch to Action Mode" = currently inspect
                        // title "Switch to Inspect Mode" = currently action
                        const currentMode = (btn.title || '').toLowerCase().includes('action') ? 'inspect' : 'action';
                        if (currentMode === 'action') {
                            try {
                                if (typeof _pw.togglePrimaryMode === 'function') _pw.togglePrimaryMode();
                                else btn.click();
                            } catch (_) { try { btn.click(); } catch (__) {} }
                            console.log('[GeoPixelcons++] \u2705 Switched to Inspect Mode on startup');
                        }
                    });
                });
            });

            _featureStatus.startInspectMode = 'ok';
        } catch (err) {
            _featureStatus.startInspectMode = 'error';
            dbgPush(`Start in Inspect Mode init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Start in Inspect Mode' });
            console.error('[GeoPixelcons++] \u274C Start in Inspect Mode failed:', err);
        }
    }

    // ============================================================
    //  SETTING: Smooth Zoom Buttons [smoothZoomButtons]
    // ============================================================
    if (_settings.smoothZoomButtons) {
        try {
            const _pw2 = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
            const SZ_MIN = 0.5;
            const SZ_MAX = 22;
            const SZ_STEP = 0.01;
            const SZ_WHEEL_STEP = 0.1;
            const SZ_HOLD_DELAY = 400;
            const SZ_HOLD_INTERVAL = 50;

            // Inject styles — only non-Tailwind-expressible rules (writing-mode, spinner removal)
            const szStyle = document.createElement('style');
            szStyle.textContent = `
                #gpc-smooth-zoom input[type=range] {
                    writing-mode: vertical-lr;
                    direction: rtl;
                    width: 8px;
                    height: 94px;
                    cursor: pointer;
                    accent-color: #22c55e;
                    margin: 0;
                }
                #gpc-smooth-zoom input[type=number]::-webkit-outer-spin-button,
                #gpc-smooth-zoom input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
                #gpc-smooth-zoom input[type=number] { -moz-appearance: textfield; }
            `;
            document.head.appendChild(szStyle);

            function _sz_buildWidget(zoomInBtn, zoomOutBtn, controlsRight) {
                zoomInBtn.style.display = 'none';
                zoomOutBtn.style.display = 'none';

                const wrapper = document.createElement('div');
                wrapper.id = 'gpc-smooth-zoom';
                wrapper.style.cssText = 'display:flex;flex-direction:column;align-items:flex-end;gap:2px;';

                function makeCircleBtn(label, title) {
                    const btn = document.createElement('button');
                    btn.className = 'gpc-sz-btn w-10 h-10 bg-white dark:bg-gray-700 shadow rounded-full flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer select-none flex-shrink-0 text-gray-700 dark:text-gray-200 border-0';
                    btn.textContent = label;
                    btn.title = title;
                    btn.style.cssText = 'font-size:20px;line-height:1;';
                    return btn;
                }

                const plusBtn = makeCircleBtn('+', 'Zoom In');
                const minusBtn = makeCircleBtn('\u2212', 'Zoom Out');

                const sliderWrap = document.createElement('div');
                sliderWrap.className = 'bg-white dark:bg-gray-700 shadow flex items-center justify-center';
                sliderWrap.style.cssText = 'width:40px;height:120px;border-radius:20px;';
                const slider = document.createElement('input');
                slider.type = 'range';
                slider.min = SZ_MIN;
                slider.max = SZ_MAX;
                slider.step = SZ_STEP;
                sliderWrap.appendChild(slider);

                const valueBox = document.createElement('input');
                valueBox.type = 'number';
                valueBox.min = SZ_MIN;
                valueBox.max = SZ_MAX;
                valueBox.step = 0.01;
                valueBox.className = 'text-center text-xs font-semibold border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-lg shadow';
                valueBox.style.cssText = 'width:40px;height:28px;padding:0 4px;box-sizing:border-box;';

                wrapper.appendChild(plusBtn);
                wrapper.appendChild(sliderWrap);
                wrapper.appendChild(minusBtn);
                wrapper.appendChild(valueBox);
                controlsRight.insertBefore(wrapper, zoomInBtn);

                // Wait for map instance then wire up events
                function _sz_waitForMap(cb) {
                    if (_pw2.map && typeof _pw2.map.getZoom === 'function') { cb(_pw2.map); return; }
                    const t = setInterval(() => {
                        if (_pw2.map && typeof _pw2.map.getZoom === 'function') { clearInterval(t); cb(_pw2.map); }
                    }, 200);
                    setTimeout(() => clearInterval(t), 20000);
                }

                _sz_waitForMap((map) => {
                    function getZ() { return map.getZoom(); }
                    function setZ(z) {
                        const clamped = Math.max(SZ_MIN, Math.min(SZ_MAX, Math.round(parseFloat(z) * 100) / 100));
                        map.setZoom(clamped);
                    }
                    function syncUI() {
                        const z = getZ();
                        slider.value = z;
                        valueBox.value = z.toFixed(2);
                    }
                    syncUI();
                    map.on('zoom', syncUI);

                    slider.addEventListener('input', () => setZ(parseFloat(slider.value)));

                    valueBox.addEventListener('change', () => {
                        const val = parseFloat(valueBox.value);
                        if (!isNaN(val)) setZ(val);
                        syncUI();
                    });
                    valueBox.addEventListener('keydown', (e) => { if (e.key === 'Enter') valueBox.blur(); });

                    let _szHoldTimer = null, _szHoldInterval = null;
                    function startHold(step) {
                        setZ(getZ() + step);
                        _szHoldTimer = setTimeout(() => {
                            _szHoldInterval = setInterval(() => setZ(getZ() + step), SZ_HOLD_INTERVAL);
                        }, SZ_HOLD_DELAY);
                    }
                    function stopHold() {
                        clearTimeout(_szHoldTimer);
                        clearInterval(_szHoldInterval);
                        _szHoldTimer = null;
                        _szHoldInterval = null;
                    }

                    [[plusBtn, SZ_STEP], [minusBtn, -SZ_STEP]].forEach(([btn, step]) => {
                        btn.addEventListener('mousedown', () => startHold(step));
                        btn.addEventListener('mouseup', stopHold);
                        btn.addEventListener('mouseleave', stopHold);
                        btn.addEventListener('touchstart', (e) => { e.preventDefault(); startHold(step); }, { passive: false });
                        btn.addEventListener('touchend', stopHold);
                    });

                    wrapper.addEventListener('wheel', (e) => {
                        e.preventDefault();
                        setZ(getZ() + (e.deltaY < 0 ? SZ_WHEEL_STEP : -SZ_WHEEL_STEP));
                    }, { passive: false });
                });
            }

            function _sz_waitForEls(cb) {
                const zi = document.getElementById('zoomIn');
                const zo = document.getElementById('zoomOut');
                const cr = document.getElementById('controls-right');
                if (zi && zo && cr) { cb(zi, zo, cr); return; }
                const obs = new MutationObserver(() => {
                    const zi2 = document.getElementById('zoomIn');
                    const zo2 = document.getElementById('zoomOut');
                    const cr2 = document.getElementById('controls-right');
                    if (zi2 && zo2 && cr2) { obs.disconnect(); cb(zi2, zo2, cr2); }
                });
                obs.observe(document.body, { childList: true, subtree: true });
                setTimeout(() => obs.disconnect(), 15000);
            }

            _sz_waitForEls(_sz_buildWidget);

            _featureStatus.smoothZoomButtons = 'ok';
            console.log('[GeoPixelcons++] \u2705 Smooth Zoom Buttons loaded');
        } catch (err) {
            _featureStatus.smoothZoomButtons = 'error';
            dbgPush(`Smooth Zoom Buttons init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Smooth Zoom Buttons' });
            console.error('[GeoPixelcons++] \u274C Smooth Zoom Buttons failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Guild Overhaul [guildOverhaul]
    // ============================================================
    if (_settings.guildOverhaul) {
        try {
            (function _init_guildOverhaul() {

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

    const SNAPSHOT_INTERVALS = {
        HOURLY: 60 * 60 * 1000,
        TWELVE_HOURS: 12 * 60 * 60 * 1000,
        TWENTY_FOUR_HOURS: 24 * 60 * 60 * 1000
    };

    const sessionState = {
        visitedCoords: new Set()
    };

    // --- One-time migration: convert old name-keyed snapshots to ID-keyed ---
    // Prior to 1.1.8, members were stored as { "PlayerName#12345": { xp, coords } }.
    // Now they are stored as { "12345": { xp, coords, name: "PlayerName#12345" } }.
    // This runs once at startup and is transparent to the user.
    (function _migrateGuildXPHistory() {
        try {
            const history = GM_getValue('guild_xp_history', []);
            if (!history.length) return;
            let changed = false;
            history.forEach(entry => {
                if (!entry.members) return;
                const oldMembers = entry.members;
                const newMembers = {};
                for (const [key, val] of Object.entries(oldMembers)) {
                    // Already migrated? (pure numeric key)
                    if (/^\d+$/.test(key)) {
                        newMembers[key] = val;
                        continue;
                    }
                    // Legacy format: full name key like "PlayerName#12345"
                    const idMatch = key.match(/#(\d+)$/);
                    if (idMatch) {
                        const id = idMatch[1];
                        if (typeof val === 'number') {
                            newMembers[id] = { xp: val, coords: null, name: key };
                        } else {
                            newMembers[id] = { ...val, name: val.name || key };
                        }
                        changed = true;
                    } else {
                        // No ID found — keep as-is (can't migrate)
                        newMembers[key] = val;
                    }
                }
                entry.members = newMembers;
            });
            if (changed) {
                GM_setValue('guild_xp_history', history);
                console.log('[Guild XP] Migrated old name-keyed snapshots to ID keys');
            }
        } catch (e) {
            console.warn('[Guild XP] Migration failed:', e);
        }
    })();

    // --- Territory Overlay State ---
    const TERRITORY_STORAGE_KEY = 'guildOverhaul_territorySettings';
    let territoryCanvas = null;
    let territoryVisible = false;
    let territoryRects = []; // Array of { gridX, gridY, width, height, label }
    let territoryActivityMap = {}; // Map of rect.index → boolean (has active players)
    let territorySettings = loadTerritorySettings();

    // --- Player Markers Overlay State ---
    const PLAYER_STORAGE_KEY = 'guildOverhaul_playerSettings';
    let playersContainer = null;
    let playersVisible = false;
    let playerMarkerData = []; // Array of { name, gridX, gridY, element, inTerritory }
    let playersShowNames = false;
    let playersColorByTerritory = true;
    let playersShowInTerritory = true;
    let playersShowOutsideTerritory = true;
    let playerSettings = loadPlayerSettings();

    function loadPlayerSettings() {
        try {
            const stored = GM_getValue(PLAYER_STORAGE_KEY, null);
            if (stored) return stored;
        } catch (e) {}
        return {
            markerSize: 28,
            labelFontSize: 11,
            defaultColor: '#ef4444',
            territoryColor: '#3b82f6'
        };
    }

    function savePlayerSettings() {
        GM_setValue(PLAYER_STORAGE_KEY, playerSettings);
    }

    function loadTerritorySettings() {
        try {
            const stored = GM_getValue(TERRITORY_STORAGE_KEY, null);
            if (stored) return stored;
        } catch (e) {}
        return {
            borderColor: '#3b82f6',
            borderThickness: 2,
            showLabels: true,
            labelFontSize: 12,
            showFill: true,
            fillColor: '#3b82f6',
            fillAlpha: 0.15,
            colorByActivity: false,
            activeBorderColor: '#22c55e',
            activeFillColor: '#22c55e',
            abandonedBorderColor: '#6b7280',
            abandonedFillColor: '#6b7280'
        };
    }

    function saveTerritorySettings() {
        GM_setValue(TERRITORY_STORAGE_KEY, territorySettings);
    }

    // --- CSS Styles ---
    // --- CSS Styles (Tailwind-compatible for geopixels++ dark theme) ---
    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;
        }

        /* Use Tailwind CSS variables for dark mode compatibility */
        .guild-message-section {
            border: 1px solid var(--color-gray-200, #e5e7eb);
            border-radius: 0.5rem;
            overflow: hidden;
            background-color: var(--color-white, #fff);
        }

        .guild-message-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0.75rem;
            background-color: var(--color-gray-50, #f9fafb);
            cursor: pointer;
            user-select: none;
            color: var(--color-gray-900, #111827);
        }

        .guild-message-header:hover {
            background-color: var(--color-gray-100, #f3f4f6);
        }

        .guild-message-toggle {
            display: inline-block;
            width: 20px;
            height: 20px;
            text-align: center;
            line-height: 20px;
            font-weight: bold;
            color: var(--color-gray-500, #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;
            background-color: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
        }

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

        @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; }
        }

        #infoTab.message-collapsed > .grid { display: block; }
        #infoTab.message-collapsed #guildMembersContainer { margin-top: 1rem; }

        .guild-find-btn.visited { background-color: var(--color-purple-500, #a855f7) !important; }
        .guild-find-btn.visited:hover { background-color: var(--color-purple-600, #9333ea) !important; }

        .xp-changes-section {
            margin-top: 1.5rem;
            border: 1px solid var(--color-gray-200, #e5e7eb);
            border-radius: 0.5rem;
            overflow: hidden;
            width: 100%;
            background-color: var(--color-white, #fff);
        }

        .xp-changes-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0.75rem;
            background-color: var(--color-gray-100, #f1f5f9);
            cursor: pointer;
            user-select: none;
            font-weight: 600;
            color: var(--color-gray-700, #334155);
        }
        .xp-changes-header:hover { background-color: var(--color-gray-200, #e2e8f0); }

        .xp-changes-content {
            padding: 1rem;
            background-color: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
            display: block;
        }
        .xp-changes-content.hidden { display: none; }

        .daily-brief-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
            color: var(--color-gray-900, #111827);
        }
        .daily-brief-table th, .daily-brief-table td {
            border: 1px solid var(--color-gray-300, #d1d5db);
            padding: 8px;
            text-align: left;
        }
        .daily-brief-table th {
            background-color: var(--color-gray-100, #f2f2f2);
            color: var(--color-gray-900, #111827);
        }
        .daily-brief-table td {
            background-color: var(--color-white, #fff);
        }

        .xp-gain { color: var(--color-green-500, #22c55e); }
        .xp-loss { color: var(--color-red-500, #ef4444); }
        .xp-neutral { color: var(--color-gray-500, #94a3b8); }

        .user-cell-content { display: flex; flex-direction: column; gap: 2px; }
        .user-name { font-weight: 500; color: var(--color-gray-900, #111827); }
        .user-coords { font-size: 13px; }

        .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: var(--color-gray-100, rgba(0,0,0,0.05)); }
        .discord-icon { color: #5865F2; }
        .map-icon { color: var(--color-sky-500, #0ea5e9); }
        .map-icon.out-of-territory { color: var(--color-red-500, #ef4444); }
        .map-icon.visited { color: var(--color-purple-500, #a855f7); }

        .control-button {
            padding: 6px 12px;
            border: 1px solid var(--color-gray-300, #d1d5db);
            background: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            transition: background-color 0.2s;
        }
        .control-button:hover { background-color: var(--color-gray-100, #f0f0f0); }
        .control-button.active {
            background-color: var(--color-blue-500, #3b82f6);
            color: var(--color-white, #fff);
            border-color: var(--color-blue-500, #3b82f6);
        }

        .trash-btn {
            background: none; border: none;
            color: var(--color-red-500, #ef4444);
            cursor: pointer;
            padding: 2px 4px; font-size: 12px;
        }
        .trash-btn:hover { color: var(--color-red-600, #dc2626); }

        .tooltip-popup {
            position: fixed;
            background: var(--color-gray-800, #333);
            color: var(--color-gray-100, #fff);
            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; }

        #snapshotIntervalSelect {
            background-color: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
        }
        #snapshotIntervalSelect:hover {
            border-color: var(--color-blue-500, #3b82f6) !important;
            box-shadow: 0 0 4px rgba(59, 130, 246, 0.2) !important;
        }

        #snapshotIntervalSelect:focus {
            border-color: var(--color-blue-500, #3b82f6) !important;
            box-shadow: 0 0 6px rgba(59, 130, 246, 0.3) !important;
            outline: none !important;
        }

        /* --- Player Markers Overlay Styles --- */
        #players-container {
            position: absolute;
            inset: 0;
            pointer-events: none;
            z-index: 5;
            overflow: hidden;
        }

        .player-marker {
            position: absolute;
            pointer-events: auto;
            cursor: pointer;
            transform: translate(-50%, -100%);
            transition: transform 0.15s ease;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.35));
        }

        .player-marker:hover {
            transform: translate(-50%, -100%) scale(1.25);
            z-index: 10;
        }

        .player-marker-tooltip {
            position: absolute;
            bottom: 100%;
            left: 50%;
            transform: translateX(-50%);
            margin-bottom: 6px;
            background: var(--color-gray-800, #1f2937);
            color: var(--color-gray-100, #f3f4f6);
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: 600;
            white-space: nowrap;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.15s ease;
            box-shadow: 0 2px 6px rgba(0,0,0,0.25);
        }

        .player-marker-tooltip::after {
            content: '';
            position: absolute;
            top: 100%;
            left: 50%;
            transform: translateX(-50%);
            border: 5px solid transparent;
            border-top-color: var(--color-gray-800, #1f2937);
        }

        .player-marker:hover .player-marker-tooltip {
            opacity: 1;
        }

        .player-marker.show-label .player-marker-tooltip {
            opacity: 1;
        }

        .player-marker-options {
            display: flex;
            align-items: center;
            gap: 14px;
            padding: 4px 0;
        }

        .player-marker-options label {
            display: flex;
            align-items: center;
            gap: 5px;
            font-size: 12px;
            font-weight: 500;
            color: var(--color-gray-600, #4b5563);
            cursor: pointer;
            user-select: none;
        }

        .player-marker-options input[type="checkbox"] {
            width: 14px;
            height: 14px;
            cursor: pointer;
            accent-color: var(--color-blue-500, #3b82f6);
        }

        /* --- Territory Overlay Styles --- */
        #territory-canvas {
            position: absolute;
            inset: 0;
            pointer-events: none;
            z-index: 5;
        }

        /* Territory Controls Container */
        #territoryControlsContainer {
            background-color: var(--color-gray-100, #f0f9ff);
            border: 1px solid var(--color-gray-300, #bae6fd);
        }

        .territory-setting-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 12px;
        }

        .territory-setting-row label {
            font-size: 13px;
            font-weight: 500;
            color: var(--color-gray-700, #374151);
        }

        .territory-settings-collapsible {
            width: 100%;
            border: 1px solid var(--color-gray-300, #d1d5db);
            border-radius: 8px;
            overflow: hidden;
            background: var(--color-white, #fff);
        }

        .territory-settings-toggle {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 8px 12px;
            background: var(--color-gray-100, #f3f4f6);
            cursor: pointer;
            user-select: none;
            font-size: 13px;
            font-weight: 600;
            color: var(--color-blue-500, #3b82f6);
            border: none;
            width: 100%;
        }

        .territory-settings-toggle:hover {
            background: var(--color-gray-200, #e5e7eb);
        }

        .territory-settings-toggle .toggle-arrow {
            transition: transform 0.2s ease;
            font-size: 11px;
        }

        .territory-settings-toggle .toggle-arrow.collapsed {
            transform: rotate(-90deg);
        }

        .territory-settings-content {
            padding: 12px;
            display: flex;
            flex-direction: column;
            gap: 10px;
            border-top: 1px solid var(--color-gray-200, #e5e7eb);
            background: var(--color-white, #fff);
        }

        .territory-settings-content.collapsed {
            display: none;
        }

        .territory-section-divider {
            border-top: 1px solid var(--color-gray-200, #e5e7eb);
            margin: 2px 0;
            padding-top: 4px;
            font-size: 11px;
            font-weight: 600;
            color: var(--color-gray-500, #6b7280);
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .territory-toggle-btn {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            padding: 8px 16px;
            border-radius: 8px;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
            border: none;
            transition: all 0.2s;
        }

        .territory-toggle-btn.active {
            background: var(--color-blue-500, #3b82f6);
            color: var(--color-white, #fff);
            box-shadow: 0 2px 8px rgba(59,130,246,0.3);
        }

        .territory-toggle-btn.inactive {
            background: var(--color-gray-200, #e5e7eb);
            color: var(--color-gray-700, #374151);
        }

        .territory-toggle-btn.inactive:hover {
            background: var(--color-gray-300, #d1d5db);
        }

        /* Territory settings inputs */
        .territory-settings-content input[type="color"] {
            border: 2px solid var(--color-gray-300, #d1d5db);
        }
        .territory-settings-content select {
            background-color: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
            border: 1px solid var(--color-gray-300, #d1d5db);
        }
        .territory-settings-content input[type="range"] {
            accent-color: var(--color-blue-500, #3b82f6);
        }

        /* Info text in territory controls */
        .territory-info-text {
            font-size: 12px;
            color: var(--color-gray-500, #64748b);
        }

        /* --- Modal Styling for Dark Mode Compatibility --- */
        .gmi-modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9999;
        }

        .gmi-modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: var(--color-white, #fff);
            border: 2px solid var(--color-blue-500, #3b82f6);
            border-radius: 8px;
            padding: 20px;
            z-index: 10000;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
            color: var(--color-gray-900, #111827);
        }

        .gmi-modal h3 {
            margin: 0 0 15px 0;
            font-size: 18px;
            font-weight: bold;
            color: var(--color-gray-900, #111827);
        }

        .gmi-modal-btn {
            display: block;
            width: 100%;
            padding: 10px;
            margin: 8px 0;
            border: 1px solid var(--color-gray-300, #d1d5db);
            border-radius: 4px;
            background: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.2s;
            text-align: left;
        }

        .gmi-modal-btn:hover {
            background-color: var(--color-gray-100, #f3f4f6);
        }

        .gmi-modal-btn.danger {
            color: var(--color-red-500, #ef4444);
        }

        .gmi-modal-btn.danger:hover {
            background-color: rgba(239, 68, 68, 0.1);
        }

        .gmi-modal-btn.warning {
            color: var(--color-yellow-500, #f59e0b);
        }

        .gmi-modal-btn.warning:hover {
            background-color: rgba(245, 158, 11, 0.1);
        }

        .gmi-modal-btn.primary {
            color: var(--color-blue-500, #3b82f6);
        }

        .gmi-modal-btn.primary:hover {
            background-color: rgba(59, 130, 246, 0.1);
        }

        .gmi-modal-section {
            padding: 10px;
            background: var(--color-gray-50, #f9fafb);
            border-radius: 4px;
            border: 1px solid var(--color-gray-200, #e5e7eb);
            margin-bottom: 15px;
        }

        .gmi-modal-select {
            padding: 6px 10px;
            border: 1px solid var(--color-gray-300, #d1d5db);
            border-radius: 4px;
            background: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
            font-size: 12px;
            cursor: pointer;
        }

        .gmi-modal-label {
            font-weight: 600;
            font-size: 12px;
            color: var(--color-gray-700, #374151);
            user-select: none;
        }

        .gmi-checkbox-option {
            display: flex;
            align-items: center;
            padding: 8px;
            border: 2px solid var(--color-gray-300, #d1d5db);
            border-radius: 4px;
            background: var(--color-white, #fff);
            cursor: pointer;
            font-size: 12px;
            transition: all 0.2s;
        }

        .gmi-checkbox-option:hover {
            border-color: var(--color-gray-400, #9ca3af);
        }

        .gmi-checkbox-option input[type="checkbox"] {
            width: 16px;
            height: 16px;
            margin-right: 8px;
            cursor: pointer;
            accent-color: var(--color-blue-500, #3b82f6);
        }

        .gmi-snapshot-list {
            flex: 1;
            overflow-y: auto;
            border: 1px solid var(--color-gray-200, #e5e7eb);
            border-radius: 4px;
            padding: 10px;
            margin-bottom: 15px;
            background: var(--color-gray-50, #f9fafb);
        }

        .gmi-snapshot-item {
            display: flex;
            align-items: center;
            padding: 8px;
            margin: 4px 0;
            background: var(--color-white, #fff);
            border-radius: 4px;
            border: 1px solid var(--color-gray-200, #e5e7eb);
            transition: background-color 0.2s, border-color 0.2s;
        }

        .gmi-snapshot-item.selected {
            background: rgba(239, 68, 68, 0.1);
            border-color: var(--color-red-300, #fca5a5);
        }

        .gmi-snapshot-item label {
            flex: 1;
            cursor: pointer;
            font-size: 12px;
            color: var(--color-gray-700, #374151);
        }

        .gmi-snapshot-item.selected label {
            color: var(--color-red-700, #b91c1c);
            text-decoration: line-through;
        }

        .gmi-action-btn {
            flex: 1;
            min-width: 120px;
            padding: 12px 16px;
            border: none;
            border-radius: 6px;
            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;
        }

        .gmi-action-btn.danger {
            background: var(--color-red-500, #dc2626);
            color: var(--color-white, #fff);
        }

        .gmi-action-btn.danger:hover {
            background: var(--color-red-600, #b91c1c);
        }

        .gmi-action-btn.primary {
            background: var(--color-blue-500, #3b82f6);
            color: var(--color-white, #fff);
        }

        .gmi-action-btn.primary:hover {
            background: var(--color-blue-600, #2563eb);
        }

        .gmi-action-btn.success {
            background: var(--color-green-500, #10b981);
            color: var(--color-white, #fff);
        }

        .gmi-action-btn.success:hover {
            background: var(--color-green-600, #059669);
        }

        .gmi-action-btn.neutral {
            background: var(--color-gray-100, #f3f4f6);
            color: var(--color-gray-600, #6b7280);
        }

        .gmi-action-btn.neutral:hover {
            background: var(--color-gray-200, #e5e7eb);
        }

        /* Progress popup */
        .gmi-progress-popup {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
            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;
        }

        .gmi-progress-popup p {
            color: var(--color-gray-700, #374151);
        }

        .gmi-progress-bar-container {
            width: 100%;
            height: 20px;
            background: var(--color-gray-200, #e5e7eb);
            border-radius: 4px;
            margin-top: 10px;
            overflow: hidden;
        }

        .gmi-progress-bar {
            height: 100%;
            background: var(--color-blue-500, #3b82f6);
            transition: width 0.3s;
        }

        /* CSV Modal */
        .gmi-csv-modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: var(--color-white, #fff);
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.2);
            z-index: 10002;
            width: 500px;
            max-width: 90%;
            display: flex;
            flex-direction: column;
            gap: 10px;
        }

        .gmi-csv-modal h3 {
            margin: 0 0 10px 0;
            color: var(--color-gray-800, #1e293b);
            font-size: 1.25rem;
            font-weight: 600;
        }

        .gmi-csv-modal textarea {
            width: 100%;
            height: 300px;
            font-family: monospace;
            font-size: 12px;
            border: 1px solid var(--color-gray-300, #ccc);
            border-radius: 4px;
            resize: vertical;
            background: var(--color-white, #fff);
            color: var(--color-gray-900, #111827);
        }
    `;
    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}`);
            return await response.json();
        } 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$/);

                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);
                    // Key by numeric user ID extracted from trailing #12345 so name changes
                    // don't cause false LEFT/JOINED events.
                    const idMatch = fullName.match(/#(\d+)$/);
                    const key = idMatch ? idMatch[1] : fullName;
                    members[key] = { xp, coords, name: fullName };
                }
            }
        });
        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;

        const progressDiv = document.createElement('div');
        progressDiv.className = 'gmi-progress-popup';
        progressDiv.innerHTML = `
            <p style="font-weight: bold; margin-bottom: 10px;">Fetching guild member data...</p>
            <p id="progressText" style="font-size: 14px;">0/${memberNames.length}</p>
            <div class="gmi-progress-bar-container">
                <div id="progressBar" class="gmi-progress-bar" style="width: 0%;"></div>
            </div>
        `;
        document.body.appendChild(progressDiv);

        for (let i = 0; i < memberNames.length; i++) {
            const key = memberNames[i];
            // keys are now numeric userId strings (from parseGuildMembers); fall back to
            // legacy #ID extraction for old snapshots that still use full-name keys
            const userId = /^\d+$/.test(key) ? key : (() => {
                const m = key.match(/#(\d+)$/);
                return m ? m[1] : null;
            })();
            if (userId) {
                const data = await fetchUserProfile(userId);
                if (data) { allUsersData.push(data); successCount++; }
                else failCount++;
            } else {
                failCount++;
            }

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

        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);
            const oldName = oldVal && typeof oldVal === 'object' ? (oldVal.name || id) : id;
            if (newMembers.hasOwnProperty(id)) {
                const newVal = newMembers[id];
                const newXp = getXp(newVal);
                const diff = newXp - oldXp;
                const coords = getCoords(newVal) || getCoords(oldVal);
                const name = (newVal && typeof newVal === 'object' ? newVal.name : null) || oldName;
                changes.push({ type: 'gain', id, name, diff, oldXp, newXp, coords });
            } else {
                const coords = getCoords(oldVal);
                changes.push({ type: 'left', id, name: oldName, oldXp, coords });
            }
        }
        for (const [id, newVal] of Object.entries(newMembers)) {
            if (!oldMembers.hasOwnProperty(id)) {
                const newXp = getXp(newVal);
                const coords = getCoords(newVal);
                const name = newVal && typeof newVal === 'object' ? (newVal.name || id) : id;
                changes.push({ type: 'join', id, name, newXp, coords });
            }
        }
        return changes;
    }

    function getCoordinateColor(coords) {
        if (!coords || coords.length < 2) return { bg: '#f3f4f6', text: '#1f2937' };
        const x = coords[0];
        const y = coords[1];
        const distance = Math.sqrt(x * x + y * y);
        const distanceBand = Math.floor(distance / 25000);

        let baseColor;
        if (x >= 0 && y >= 0) {
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(120, 50%, ${97 - intensity}%)`;
        } else if (x < 0 && y >= 0) {
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(0, 50%, ${97 - intensity}%)`;
        } else if (x < 0 && y < 0) {
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(240, 50%, ${97 - intensity}%)`;
        } else {
            const intensity = Math.min(distanceBand * 3, 15);
            baseColor = `hsl(30, 50%, ${97 - intensity}%)`;
        }
        return { bg: baseColor, text: '#1f2937' };
    }

    // --- XP Changes Section ---
    function ensureXPChangesSection() {
        const infoBtn = document.getElementById('infoTabBtn');
        if (!infoBtn) {
            if (document.getElementById('infoTab')) {
                console.log('[Guild XP] Could not find tab buttons, appending to infoTab instead');
                ensureXPChangesSectionLegacy();
            }
            return;
        }

        const tabNav = infoBtn.parentElement;
        if (document.getElementById('xpTrackerTabBtn')) return;

        const existingPanes = document.querySelectorAll('#xpTrackerPane');
        existingPanes.forEach(pane => pane.remove());

        const xpTabBtn = document.createElement('button');
        xpTabBtn.textContent = 'XP Tracker';
        xpTabBtn.id = 'xpTrackerTabBtn';
        xpTabBtn.className = infoBtn.className;
        xpTabBtn.classList.remove('text-blue-600', 'border-blue-500');
        xpTabBtn.classList.add('text-gray-500', 'border-transparent');
        xpTabBtn.style.borderBottom = '2px solid transparent';

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

        const infoTab = document.getElementById('infoTab');
        const contentContainer = infoTab?.parentElement;

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

        xpTabBtn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            const allPanes = contentContainer.querySelectorAll('.guild-tab-content, [id$="Tab"], [id$="Pane"]');
            allPanes.forEach(pane => { pane.style.display = 'none'; pane.classList.add('hidden'); });
            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 = '';
            });
            xpTabPane.style.display = 'block';
            xpTabPane.classList.remove('hidden');
            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';
            renderXPChanges(xpTabPane);
        };

        const existingTabs = tabNav.querySelectorAll('button');
        existingTabs.forEach(btn => {
            if (btn.id === 'xpTrackerTabBtn' || btn.dataset.xpTrackerHooked) return;
            const originalOnClick = btn.onclick;
            btn.onclick = (e) => {
                xpTabPane.style.display = 'none';
                xpTabPane.classList.add('hidden');
                const allPanes = contentContainer.querySelectorAll('.guild-tab-content');
                allPanes.forEach(pane => { if (pane.id !== 'xpTrackerPane') pane.style.display = ''; });
                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 = '';
                if (originalOnClick) originalOnClick.call(btn, e);
            };
            btn.dataset.xpTrackerHooked = 'true';
        });

        tabNav.appendChild(xpTabBtn);
        contentContainer.appendChild(xpTabPane);

        const navObserver = new MutationObserver(() => {
            if (!document.getElementById('xpTrackerTabBtn')) 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);
        infoTab.appendChild(section);
    }

    function collapseOtherSections() {
        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.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.name || 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 [key, data] of Object.entries(snap.members)) {
                    const xp = getXp(data);
                    const displayName = (data && typeof data === 'object' && data.name) ? data.name : key;
                    csv += `${idx + 1},"${timestamp}","${displayName}",${xp}\n`;
                }
            });
            // Add current
            const now = new Date(getVirtualNow()).toLocaleString();
            for (const [key, data] of Object.entries(currentMembers)) {
                const xp = getXp(data);
                const displayName = (data && typeof data === 'object' && data.name) ? data.name : key;
                csv += `Current,"${now}","${displayName}",${xp}\n`;
            }
            csvContent = csv;
        }

        // Open CSV Modal
        const csvOverlay = document.createElement('div');
        csvOverlay.className = 'gmi-modal-overlay';
        csvOverlay.style.zIndex = '10001';
        csvOverlay.onclick = () => { csvOverlay.remove(); csvModal.remove(); };

        const csvModal = document.createElement('div');
        csvModal.className = 'gmi-csv-modal';

        const title = document.createElement('h3');
        title.textContent = 'CSV Export';

        const textarea = document.createElement('textarea');
        textarea.value = csvContent;
        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 = 'gmi-action-btn success';
        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.className = 'gmi-modal-overlay';

        const modal = document.createElement('div');
        modal.className = 'gmi-modal';
        modal.style.minWidth = '350px';

        const title = document.createElement('h3');
        title.textContent = 'Clean History Options';
        modal.appendChild(title);

        const deleteAllBtn = document.createElement('button');
        deleteAllBtn.innerHTML = 'Select All Snapshots for Deletion';
        deleteAllBtn.className = 'gmi-modal-btn danger';
        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.className = 'gmi-modal-btn warning';
        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.className = 'gmi-modal-btn primary';
        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.className = 'gmi-modal-btn';
        delete7DaysBtn.style.color = 'var(--color-purple-500, #8b5cf6)';
        delete7DaysBtn.onclick = () => {
            const result = deleteHistoryOlderThan7Days();
            if (result !== null) {
                onClose(result);
            }
        };
        modal.appendChild(delete7DaysBtn);

        const cancelBtn = document.createElement('button');
        cancelBtn.innerHTML = 'Cancel';
        cancelBtn.className = 'gmi-modal-btn';
        cancelBtn.style.marginTop = '15px';
        cancelBtn.style.borderTop = '1px solid var(--color-gray-300, #ddd)';
        cancelBtn.style.paddingTop = '15px';
        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.className = 'gmi-modal-overlay';

        const modal = document.createElement('div');
        modal.className = 'gmi-modal';
        modal.style.cssText = `
            width: 90%;
            max-width: 600px;
            max-height: 80vh;
            display: flex;
            flex-direction: column;
        `;

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

        const title = document.createElement('h3');
        title.textContent = 'Manage Snapshots';
        header.appendChild(title);

        const info = document.createElement('p');
        info.textContent = `Total snapshots: ${history.length}`;
        info.style.cssText = 'margin: 0; font-size: 12px; color: var(--color-gray-500, #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 var(--color-gray-200, #e5e7eb);
        `;

        const maxLabel = document.createElement('label');
        maxLabel.textContent = 'Max Snapshots:';
        maxLabel.className = 'gmi-modal-label';
        maxSnapshotsDiv.appendChild(maxLabel);

        const maxSelect = document.createElement('select');
        maxSelect.className = 'gmi-modal-select';

        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.className = 'gmi-modal-section';
        intervalDiv.style.cssText = `
            display: grid;
            grid-template-columns: 150px 1fr;
            align-items: center;
            gap: 12px;
        `;

        const intervalLabel = document.createElement('label');
        intervalLabel.textContent = 'Snapshot Interval:';
        intervalLabel.className = 'gmi-modal-label';
        intervalLabel.style.whiteSpace = 'nowrap';
        intervalDiv.appendChild(intervalLabel);

        const intervalSelect = document.createElement('select');
        intervalSelect.id = 'snapshotIntervalSelect';
        intervalSelect.className = 'gmi-modal-select';
        intervalSelect.style.cssText = `
            padding: 8px 12px;
            border: 2px solid #d1d5db;
            border-radius: 6px;
            background: white;
            font-size: 13px;
            cursor: pointer;
            color: #374151;
            transition: all 0.2s ease;
            font-weight: 500;
            max-width: 280px;
        `;

        // Add hover and focus styles through a style tag
        intervalSelect.onmouseover = () => {
            intervalSelect.style.borderColor = '#3b82f6';
            intervalSelect.style.boxShadow = '0 0 4px rgba(59, 130, 246, 0.2)';
        };
        intervalSelect.onmouseout = () => {
            if (document.activeElement !== intervalSelect) {
                intervalSelect.style.borderColor = '#d1d5db';
                intervalSelect.style.boxShadow = 'none';
            }
        };
        intervalSelect.onfocus = () => {
            intervalSelect.style.borderColor = '#3b82f6';
            intervalSelect.style.boxShadow = '0 0 6px rgba(59, 130, 246, 0.3)';
        };
        intervalSelect.onblur = () => {
            intervalSelect.style.borderColor = '#d1d5db';
            intervalSelect.style.boxShadow = 'none';
        };

        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 checkboxInputStyle = `
            width: 16px;
            height: 16px;
            margin-right: 8px;
            cursor: pointer;
            accent-color: var(--color-blue-500, #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.className = 'gmi-checkbox-option';
        allOption.style.color = 'var(--color-red-500, #ef4444)';
        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 = 'var(--color-red-500, #ef4444)';
        allOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === 'all' ? 'var(--color-red-500, #ef4444)' : 'var(--color-gray-300, #ddd)';
        shortcutsDiv.appendChild(allOption);

        // Keep daily checkbox
        const dailyOption = document.createElement('label');
        dailyOption.className = 'gmi-checkbox-option';
        dailyOption.style.color = 'var(--color-yellow-500, #f59e0b)';
        const dailyCheckbox = document.createElement('input');
        dailyCheckbox.type = 'checkbox';
        dailyCheckbox.style.cssText = checkboxInputStyle;
        dailyCheckbox.style.accentColor = 'var(--color-yellow-500, #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 = 'var(--color-yellow-500, #f59e0b)';
        dailyOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === 'daily' ? 'var(--color-yellow-500, #f59e0b)' : 'var(--color-gray-300, #ddd)';
        shortcutsDiv.appendChild(dailyOption);

        // Keep weekly checkbox
        const weeklyOption = document.createElement('label');
        weeklyOption.className = 'gmi-checkbox-option';
        weeklyOption.style.color = 'var(--color-blue-500, #3b82f6)';
        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 = 'var(--color-blue-500, #3b82f6)';
        weeklyOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === 'weekly' ? 'var(--color-blue-500, #3b82f6)' : 'var(--color-gray-300, #ddd)';
        shortcutsDiv.appendChild(weeklyOption);

        // 7+ days old checkbox
        const deleteOldOption = document.createElement('label');
        deleteOldOption.className = 'gmi-checkbox-option';
        deleteOldOption.style.color = 'var(--color-purple-500, #8b5cf6)';
        const deleteOldCheckbox = document.createElement('input');
        deleteOldCheckbox.type = 'checkbox';
        deleteOldCheckbox.style.cssText = checkboxInputStyle;
        deleteOldCheckbox.style.accentColor = 'var(--color-purple-500, #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 = 'var(--color-purple-500, #8b5cf6)';
        deleteOldOption.onmouseout = (e) => e.currentTarget.style.borderColor = selectedPreset === '7days' ? 'var(--color-purple-500, #8b5cf6)' : 'var(--color-gray-300, #ddd)';
        shortcutsDiv.appendChild(deleteOldOption);

        modal.appendChild(shortcutsDiv);

        // Snapshot list container
        const listContainer = document.createElement('div');
        listContainer.className = 'gmi-snapshot-list';
        listContainer.style.cssText = `
            flex: 1;
            overflow-y: auto;
        `;
        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.className = isSelected ? 'gmi-snapshot-item selected' : 'gmi-snapshot-item';
                if (!isSelected && useAltColor) {
                    item.style.background = 'var(--color-gray-100, #f3f4f6)';
                }

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.checked = selectedIndices.has(idx);
                checkbox.style.cssText = 'margin-right: 10px; cursor: pointer; accent-color: var(--color-blue-500, #3b82f6);';
                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 ? 'var(--color-red-700, #991b1b)' : 'var(--color-gray-700, #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 ? 'var(--color-red-600, #b91c1c)' : 'var(--color-gray-500, #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 var(--color-gray-200, #e5e7eb);
            padding-top: 15px;
        `;

        const deleteSelectedBtn = document.createElement('button');
        deleteSelectedBtn.innerHTML = '🗑️ Delete Selected';
        deleteSelectedBtn.className = 'gmi-action-btn danger';
        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.className = 'gmi-action-btn primary';
        exportBtn.onclick = () => {
            exportSnapshots();
        };
        buttonDiv.appendChild(exportBtn);

        const importBtn = document.createElement('button');
        importBtn.innerHTML = '📥 Import Snapshots';
        importBtn.className = 'gmi-action-btn success';
        importBtn.onclick = () => {
            importSnapshots();
        };
        buttonDiv.appendChild(importBtn);

        const cancelBtn = document.createElement('button');
        cancelBtn.innerHTML = '✕ Close';
        cancelBtn.className = 'gmi-action-btn neutral';
        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', 'in-territory', 'out-of-territory'

        const clearAllFilterActive = () => {
            allBtn.classList.remove('active');
            activeBtn.classList.remove('active');
            inactiveBtn.classList.remove('active');
            inTerritoryBtn.classList.remove('active');
            outOfTerritoryBtn.classList.remove('active');
        };

        const allBtn = document.createElement('button');
        allBtn.innerHTML = 'Show All';
        allBtn.className = 'control-button active';
        allBtn.onclick = () => {
            filterMode = 'all';
            clearAllFilterActive();
            allBtn.classList.add('active');
            updateTable();
        };
        filterRow.appendChild(allBtn);

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

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

        const inTerritoryBtn = document.createElement('button');
        inTerritoryBtn.innerHTML = '🟦 In Territory';
        inTerritoryBtn.className = 'control-button xp-territory-filter-btn';
        inTerritoryBtn.style.display = playersVisible ? '' : 'none';
        inTerritoryBtn.onclick = () => {
            filterMode = 'in-territory';
            clearAllFilterActive();
            inTerritoryBtn.classList.add('active');
            updateTable();
        };
        filterRow.appendChild(inTerritoryBtn);

        const outOfTerritoryBtn = document.createElement('button');
        outOfTerritoryBtn.innerHTML = '🟥 Out of Territory';
        outOfTerritoryBtn.className = 'control-button xp-territory-filter-btn';
        outOfTerritoryBtn.style.display = playersVisible ? '' : 'none';
        outOfTerritoryBtn.onclick = () => {
            filterMode = 'out-of-territory';
            clearAllFilterActive();
            outOfTerritoryBtn.classList.add('active');
            updateTable();
        };
        filterRow.appendChild(outOfTerritoryBtn);

        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;
                });
            } else if (filterMode === 'in-territory') {
                changes = changes.filter(c => {
                    const markerInfo = playerMarkerData.find(m => m.name === c.name);
                    return markerInfo && markerInfo.inTerritory;
                });
            } else if (filterMode === 'out-of-territory') {
                changes = changes.filter(c => {
                    const markerInfo = playerMarkerData.find(m => m.name === c.name);
                    return markerInfo && !markerInfo.inTerritory;
                });
            }

            // 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.name || change.id;

                    userTd.appendChild(nameSpan);

                    // Extract ID — change.id is already the numeric userId string
                    const userId = change.id;
                    if (userId && /^\d+$/.test(userId)) {
                        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.setAttribute('data-player-name', change.name || change.id);
                        // If player markers are active, mark out-of-territory players red
                        if (playersVisible && playerMarkerData.length > 0) {
                            const markerInfo = playerMarkerData.find(m => m.name === (change.name || change.id));
                            if (markerInfo && !markerInfo.inTerritory) {
                                mapBtn.classList.add('out-of-territory');
                            }
                        }
                        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.name || 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);
    }

    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 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)})`;
            }
        }
    }

    // =====================================================
    // === TERRITORY MAP OVERLAY (New in 3.0.0) ===
    // =====================================================

    /**
     * Create the territory overlay canvas that sits on top of the map.
     * Similar approach to geopixels++ censor canvas but draws stroke-only rectangles.
     */
    function createTerritoryCanvas() {
        if (territoryCanvas) return;

        territoryCanvas = document.createElement('canvas');
        territoryCanvas.id = 'territory-canvas';
        document.body.appendChild(territoryCanvas);
        console.log('[Guild Territories] Territory canvas created');
    }

    /**
     * Load an image from a data URL and return its natural dimensions.
     */
    function getImageDimensionsFromSrc(src) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
            img.onerror = () => reject(new Error('Failed to load image'));
            img.src = src;
        });
    }

    /**
     * Process all guild projects and build territory rectangles.
     * Each project has imageGridX, imageGridY (top-left) and an image (base64 PNG).
     * Width/height are the pixel dimensions of the PNG (1 pixel = 1 grid unit).
     */
    async function buildTerritoryRects() {
        if (typeof userGuildData === 'undefined' || !userGuildData || !userGuildData.projects) {
            console.warn('[Guild Territories] No guild data or projects available');
            return [];
        }

        const projects = userGuildData.projects;
        if (projects.length === 0) return [];

        const rects = [];

        for (let i = 0; i < projects.length; i++) {
            const project = projects[i];
            try {
                const dims = await getImageDimensionsFromSrc(project.image);
                rects.push({
                    gridX: project.imageGridX,
                    gridY: project.imageGridY,
                    width: dims.width,
                    height: dims.height,
                    index: i + 1 // 1-based logical order matching guild modal display
                });
            } catch (err) {
                console.warn(`[Guild Territories] Failed to get dimensions for project #${i + 1} (id ${project.id}):`, err);
            }
        }

        return rects;
    }

    /**
     * Export guild territories as JSON compatible with the GeoPixels Json "Import JSON" feature.
     * Copies to clipboard in the format: [{ name, x, y, width, height }]
     * where x,y is top-left corner (matching Tauri/Json region format).
     */
    async function exportTerritoriesForJson() {
        const exportBtn = document.getElementById('exportTerritoriesBtn');
        if (exportBtn) {
            exportBtn.disabled = true;
            exportBtn.innerHTML = '⏳ Loading...';
        }

        try {
            // Ensure guild projects are fetched
            if (typeof userGuildData !== 'undefined' && userGuildData && typeof fetchGuildProjects === 'function') {
                await fetchGuildProjects();
            }

            const rects = await buildTerritoryRects();

            if (rects.length === 0) {
                alert('No guild projects found to export.');
                return;
            }

            // Convert to json-compatible format
            const regions = rects.map(rect => ({
                name: `Template #${rect.index}`,
                x: rect.gridX,
                y: rect.gridY,
                width: rect.width,
                height: rect.height
            }));

            const json = JSON.stringify(regions, null, 2);

            try {
                await navigator.clipboard.writeText(json);
                if (exportBtn) {
                    exportBtn.innerHTML = '✅ Copied!';
                    setTimeout(() => { exportBtn.innerHTML = '📋 Export to Clipboard'; }, 2000);
                }
                console.log(`[Guild Territories] Exported ${regions.length} territories to clipboard for Json import`);
            } catch (clipErr) {
                // Fallback: show in prompt for manual copy
                prompt('Copy this JSON and paste into Json\'s "Import JSON":', json);
            }
        } catch (err) {
            console.error('[Guild Territories] Export failed:', err);
            alert('Failed to export territories: ' + err.message);
        } finally {
            if (exportBtn) exportBtn.disabled = false;
            // Restore button text if not in "Copied!" state
            if (exportBtn && !exportBtn.innerHTML.includes('✅')) {
                exportBtn.innerHTML = '📋 Export to Clipboard';
            }
        }
    }

    /**
     * Draw a single territory border rectangle on the canvas.
     * Converts grid coordinates → Mercator → WGS84 → screen pixels.
     *
     * The coordinate system:
     * - gridX, gridY = top-left of the image in grid space
     * - In GeoPixels, Y axis in grid space is inverted relative to image space
     *   (gridY is top, gridY - height is bottom)
     */
    function drawTerritoryRect(ctx, rect, gSize, color, thickness, fillColor) {
        if (typeof turf === 'undefined' || typeof map === 'undefined') return;

        // Top-left in mercator: gridX is left edge, gridY is top edge
        // The image extends rightward (+X) and downward (-Y in grid terms)
        const topLeftMerc = [
            (rect.gridX - 0.5) * gSize,
            (rect.gridY + 0.5) * gSize
        ];
        const bottomRightMerc = [
            (rect.gridX - 0.5 + rect.width) * gSize,
            (rect.gridY + 0.5 - rect.height) * gSize
        ];

        const topLeftScreen = map.project(turf.toWgs84(topLeftMerc));
        const bottomRightScreen = map.project(turf.toWgs84(bottomRightMerc));

        const screenX = topLeftScreen.x;
        const screenY = topLeftScreen.y;
        const screenW = bottomRightScreen.x - topLeftScreen.x;
        const screenH = bottomRightScreen.y - topLeftScreen.y;

        // Frustum culling - skip if entirely off-screen
        if (
            screenX + screenW < 0 ||
            screenX > ctx.canvas.width ||
            screenY + screenH < 0 ||
            screenY > ctx.canvas.height
        ) return;

        // Optional fill
        if (territorySettings.showFill) {
            ctx.fillStyle = fillColor || territorySettings.fillColor;
            ctx.globalAlpha = territorySettings.fillAlpha;
            ctx.fillRect(screenX, screenY, screenW, screenH);
        }

        // Border stroke
        ctx.strokeStyle = color;
        ctx.lineWidth = thickness;
        ctx.globalAlpha = 1;
        ctx.strokeRect(screenX, screenY, screenW, screenH);

        // Draw project label if enabled (uses logical order #1, #2, etc.)
        if (territorySettings.showLabels && screenW > 40 && screenH > 20) {
            const label = `#${rect.index}`;
            ctx.font = `bold ${territorySettings.labelFontSize}px sans-serif`;
            ctx.fillStyle = color;
            ctx.globalAlpha = 0.85;
            ctx.textAlign = 'left';
            ctx.textBaseline = 'top';
            ctx.fillText(label, screenX + 4, screenY + 4);
        }
    }

    /**
     * Build a map of territory index → boolean indicating whether any guild
     * member is currently positioned inside each territory.
     * Used to distinguish active (in-use) vs abandoned/finished territories.
     */
    function buildTerritoryActivityMap() {
        const activity = {};
        if (territoryRects.length === 0) return activity;

        const members = parseGuildMembers();
        if (!members || Object.keys(members).length === 0) return activity;

        for (const rect of territoryRects) {
            let hasPlayers = false;
            for (const [, data] of Object.entries(members)) {
                const coords = getCoords(data);
                if (coords) {
                    const [gx, gy] = coords;
                    if (
                        gx >= rect.gridX &&
                        gx < rect.gridX + rect.width &&
                        gy <= rect.gridY &&
                        gy > rect.gridY - rect.height
                    ) {
                        hasPlayers = true;
                        break;
                    }
                }
            }
            activity[rect.index] = hasPlayers;
        }
        return activity;
    }

    /**
     * Redraw all territory rectangles on the overlay canvas.
     */
    function drawTerritories() {
        if (!territoryCanvas || !territoryVisible) return;

        const pixelCanvas = document.getElementById('pixel-canvas');
        if (!pixelCanvas) return;

        territoryCanvas.width = pixelCanvas.width;
        territoryCanvas.height = pixelCanvas.height;
        const ctx = territoryCanvas.getContext('2d');
        ctx.clearRect(0, 0, territoryCanvas.width, territoryCanvas.height);

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

        const gSize = (typeof gridSize !== 'undefined') ? gridSize : 25;
        const thickness = territorySettings.borderThickness;

        // Build activity map for two-tone coloring if enabled
        if (territorySettings.colorByActivity) {
            territoryActivityMap = buildTerritoryActivityMap();
        }

        // When both territories and players are visible, compute occupancy
        // to highlight unoccupied territories in red
        const occupancyMap = (playersVisible && playerMarkerData.length > 0)
            ? buildTerritoryActivityMap()
            : null;

        territoryRects.forEach(rect => {
            let color = territorySettings.borderColor;
            let fillColor = territorySettings.fillColor;

            if (territorySettings.colorByActivity) {
                const isActive = territoryActivityMap[rect.index] ?? false;
                color = isActive ? territorySettings.activeBorderColor : territorySettings.abandonedBorderColor;
                fillColor = isActive ? territorySettings.activeFillColor : territorySettings.abandonedFillColor;
            }

            // Override: if players are visible and territory is unoccupied, color red
            if (occupancyMap && !(occupancyMap[rect.index])) {
                color = '#ef4444';
                fillColor = '#ef4444';
            }

            drawTerritoryRect(ctx, rect, gSize, color, thickness, fillColor);
        });
    }

    /**
     * Hook into map events so territories redraw on pan/zoom/resize.
     */
    function hookTerritoryToMap() {
        function waitForMapReady(callback) {
            let tries = 0;
            function check() {
                if (typeof map !== 'undefined' && map && map.on && map.getContainer) callback();
                else if (tries++ < 100) setTimeout(check, 100);
            }
            check();
        }

        waitForMapReady(() => {
            ['move', 'rotate', 'zoom'].forEach(ev => map.on(ev, drawTerritories));
            new ResizeObserver(drawTerritories).observe(map.getContainer());
            map.once('load', drawTerritories);
            console.log('[Guild Territories] Hooked to map events');
        });
    }

    /**
     * Toggle territory overlay on/off. If turning on, process projects first.
     */
    async function toggleTerritories() {
        if (territoryVisible) {
            // Turn off
            territoryVisible = false;
            if (territoryCanvas) {
                const ctx = territoryCanvas.getContext('2d');
                ctx.clearRect(0, 0, territoryCanvas.width, territoryCanvas.height);
            }
            updateTerritoryToggleButton();
            console.log('[Guild Territories] Territories hidden');
            return;
        }

        // Turn on - process projects
        const toggleBtn = document.getElementById('territoryToggleBtn');
        if (toggleBtn) {
            toggleBtn.disabled = true;
            toggleBtn.innerHTML = '⏳ Processing...';
        }

        try {
            // Ensure guild projects are fetched
            if (typeof userGuildData !== 'undefined' && userGuildData && typeof fetchGuildProjects === 'function') {
                await fetchGuildProjects();
            }

            territoryRects = await buildTerritoryRects();

            if (territoryRects.length === 0) {
                if (toggleBtn) {
                    toggleBtn.disabled = false;
                    toggleBtn.innerHTML = '🗺️ Show Territories';
                    toggleBtn.className = 'territory-toggle-btn inactive';
                }
                alert('No guild projects found to display territories for.');
                return;
            }

            createTerritoryCanvas();
            territoryVisible = true;
            drawTerritories();
            updateTerritoryToggleButton();
            console.log(`[Guild Territories] Showing ${territoryRects.length} territories`);

        } catch (err) {
            console.error('[Guild Territories] Error building territories:', err);
            alert('Failed to process territories: ' + err.message);
        }

        if (toggleBtn) toggleBtn.disabled = false;
    }

    /**
     * Update the toggle button appearance based on state.
     */
    function updateTerritoryToggleButton() {
        const toggleBtn = document.getElementById('territoryToggleBtn');
        if (!toggleBtn) return;

        if (territoryVisible) {
            toggleBtn.innerHTML = '🗺️ Hide Territories';
            toggleBtn.className = 'territory-toggle-btn active';
        } else {
            toggleBtn.innerHTML = '🗺️ Show Territories';
            toggleBtn.className = 'territory-toggle-btn inactive';
        }
    }

    /**
     * Build the inline collapsible settings panel HTML.
     * Returns the container element to be appended inside the territory controls.
     */
    function buildTerritorySettingsPanel() {
        const wrapper = document.createElement('div');
        wrapper.className = 'territory-settings-collapsible';
        wrapper.id = 'territorySettingsCollapsible';

        // Toggle header
        const toggle = document.createElement('button');
        toggle.className = 'territory-settings-toggle';
        toggle.innerHTML = '<span>⚙️ Settings</span><span class="toggle-arrow collapsed">▼</span>';

        // Content
        const content = document.createElement('div');
        content.className = 'territory-settings-content collapsed';

        const thicknessOptions = [
            { value: 1, label: 'Thin (1px)' },
            { value: 2, label: 'Normal (2px)' },
            { value: 3, label: 'Medium (3px)' },
            { value: 4, label: 'Thick (4px)' },
            { value: 6, label: 'Heavy (6px)' },
            { value: 8, label: 'Extra Heavy (8px)' }
        ];

        const fillOpacityPct = Math.round(territorySettings.fillAlpha * 100);

        content.innerHTML = `
            <div class="territory-setting-row">
                <label>Border Color</label>
                <input type="color" id="territoryColorInput" value="${territorySettings.borderColor}"
                       class="w-10 h-7 rounded-md cursor-pointer p-0.5">
            </div>
            <div class="territory-setting-row">
                <label>Border Thickness</label>
                <select id="territoryThicknessSelect" class="px-2 py-1 rounded-md text-xs min-w-[120px]">
                    ${thicknessOptions.map(opt =>
                        `<option value="${opt.value}" ${territorySettings.borderThickness == opt.value ? 'selected' : ''}>${opt.label}</option>`
                    ).join('')}
                </select>
            </div>
            <div class="territory-setting-row">
                <label>Show Labels</label>
                <input type="checkbox" id="territoryLabelsCheck" ${territorySettings.showLabels ? 'checked' : ''}
                       class="w-4 h-4 cursor-pointer accent-blue-500">
            </div>
            <div class="territory-setting-row">
                <label>Label Size</label>
                <select id="territoryFontSelect" class="px-2 py-1 rounded-md text-xs min-w-[120px]">
                    ${[10, 12, 14, 16, 18, 20].map(s =>
                        `<option value="${s}" ${territorySettings.labelFontSize == s ? 'selected' : ''}>${s}px</option>`
                    ).join('')}
                </select>
            </div>

            <div class="territory-section-divider">Fill</div>
            <div class="territory-setting-row">
                <label>Enable Fill</label>
                <input type="checkbox" id="territoryFillCheck" ${territorySettings.showFill ? 'checked' : ''}
                       class="w-4 h-4 cursor-pointer accent-blue-500">
            </div>
            <div class="territory-setting-row">
                <label>Fill Color</label>
                <input type="color" id="territoryFillColorInput" value="${territorySettings.fillColor}"
                       class="w-10 h-7 rounded-md cursor-pointer p-0.5">
            </div>
            <div class="territory-setting-row">
                <label>Fill Opacity</label>
                <div class="flex items-center gap-1.5">
                    <input type="range" id="territoryFillAlphaRange" min="0.01" max="1" step="0.01" value="${territorySettings.fillAlpha}"
                           class="w-20 cursor-pointer">
                    <span id="territoryFillAlphaValue" class="text-xs min-w-[30px]" style="color: var(--color-gray-500, #6b7280);">${fillOpacityPct}%</span>
                </div>
            </div>

            <div class="territory-section-divider">Activity Coloring</div>
            <div class="territory-setting-row">
                <label>Color by activity</label>
                <input type="checkbox" id="territoryActivityCheck" ${territorySettings.colorByActivity ? 'checked' : ''}
                       class="w-4 h-4 cursor-pointer accent-blue-500"
                       title="Use different colors for territories with active players vs abandoned/finished">
            </div>
            <div id="activityColorRows" style="display:${territorySettings.colorByActivity ? 'flex' : 'none'};flex-direction:column;gap:10px;">
                <div class="territory-setting-row">
                    <label>Active Border</label>
                    <input type="color" id="territoryActiveBorderInput" value="${territorySettings.activeBorderColor}"
                           class="w-10 h-7 rounded-md cursor-pointer p-0.5">
                </div>
                <div class="territory-setting-row">
                    <label>Active Fill</label>
                    <input type="color" id="territoryActiveFillInput" value="${territorySettings.activeFillColor}"
                           class="w-10 h-7 rounded-md cursor-pointer p-0.5">
                </div>
                <div class="territory-setting-row">
                    <label>Abandoned Border</label>
                    <input type="color" id="territoryAbandonedBorderInput" value="${territorySettings.abandonedBorderColor}"
                           class="w-10 h-7 rounded-md cursor-pointer p-0.5">
                </div>
                <div class="territory-setting-row">
                    <label>Abandoned Fill</label>
                    <input type="color" id="territoryAbandonedFillInput" value="${territorySettings.abandonedFillColor}"
                           class="w-10 h-7 rounded-md cursor-pointer p-0.5">
                </div>
                <p style="font-size:11px;color:var(--color-gray-500,#6b7280);margin:0;">
                    Active = guild members drawing inside. Abandoned = no members inside.
                </p>
            </div>

            <div class="territory-section-divider">Preview</div>
            <div id="territoryPreviewContainer" class="flex items-center justify-center gap-2.5 py-1">
                <div id="territoryPreviewBox" style="width: 70px; height: 44px; border: ${territorySettings.borderThickness}px solid ${territorySettings.colorByActivity ? territorySettings.activeBorderColor : territorySettings.borderColor}; border-radius: 2px; position: relative; display: flex; align-items: flex-start; justify-content: flex-start; padding: 2px; background: var(--color-white, #fff);">
                    <div id="territoryPreviewFill" style="position: absolute; inset: 0; background: ${territorySettings.colorByActivity ? territorySettings.activeFillColor : territorySettings.fillColor}; opacity: ${territorySettings.showFill ? territorySettings.fillAlpha : 0}; border-radius: 1px;"></div>
                    <span style="font-size: ${territorySettings.labelFontSize}px; font-weight: bold; color: ${territorySettings.colorByActivity ? territorySettings.activeBorderColor : territorySettings.borderColor}; position: relative; z-index: 1;">${territorySettings.colorByActivity ? 'Active' : '#1'}</span>
                </div>
                <div id="territoryPreviewBoxAbandoned" style="width: 70px; height: 44px; border: ${territorySettings.borderThickness}px solid ${territorySettings.abandonedBorderColor}; border-radius: 2px; position: relative; display: ${territorySettings.colorByActivity ? 'flex' : 'none'}; align-items: flex-start; justify-content: flex-start; padding: 2px; background: var(--color-white, #fff);">
                    <div id="territoryPreviewFillAbandoned" style="position: absolute; inset: 0; background: ${territorySettings.abandonedFillColor}; opacity: ${territorySettings.showFill ? territorySettings.fillAlpha : 0}; border-radius: 1px;"></div>
                    <span style="font-size: ${territorySettings.labelFontSize}px; font-weight: bold; color: ${territorySettings.abandonedBorderColor}; position: relative; z-index: 1;">Done</span>
                </div>
            </div>
        `;

        // Toggle collapse
        toggle.addEventListener('click', () => {
            content.classList.toggle('collapsed');
            toggle.querySelector('.toggle-arrow').classList.toggle('collapsed');
        });

        wrapper.append(toggle, content);

        // Wire up live preview + auto-save after a brief delay
        const wireEvents = () => {
            const updatePreviewAndSave = () => {
                const color = document.getElementById('territoryColorInput')?.value;
                const thickness = parseInt(document.getElementById('territoryThicknessSelect')?.value);
                const fontSize = parseInt(document.getElementById('territoryFontSelect')?.value);
                const showLabels = document.getElementById('territoryLabelsCheck')?.checked;
                const showFill = document.getElementById('territoryFillCheck')?.checked;
                const fillColor = document.getElementById('territoryFillColorInput')?.value;
                const fillAlpha = parseFloat(document.getElementById('territoryFillAlphaRange')?.value);
                const colorByActivity = document.getElementById('territoryActivityCheck')?.checked;
                const activeBorderColor = document.getElementById('territoryActiveBorderInput')?.value;
                const activeFillColor = document.getElementById('territoryActiveFillInput')?.value;
                const abandonedBorderColor = document.getElementById('territoryAbandonedBorderInput')?.value;
                const abandonedFillColor = document.getElementById('territoryAbandonedFillInput')?.value;

                // Show/hide activity color rows
                const activityRows = document.getElementById('activityColorRows');
                if (activityRows) activityRows.style.display = colorByActivity ? 'flex' : 'none';

                // Update main preview box
                const box = document.getElementById('territoryPreviewBox');
                const fillDiv = document.getElementById('territoryPreviewFill');
                const previewBorderColor = colorByActivity ? activeBorderColor : color;
                const previewFillCol = colorByActivity ? activeFillColor : fillColor;

                if (box) {
                    box.style.borderColor = previewBorderColor;
                    box.style.borderWidth = thickness + 'px';
                    const label = box.querySelector('span');
                    if (label) {
                        label.style.color = previewBorderColor;
                        label.style.fontSize = fontSize + 'px';
                        label.textContent = colorByActivity ? 'Active' : '#1';
                    }
                }
                if (fillDiv) {
                    fillDiv.style.background = previewFillCol;
                    fillDiv.style.opacity = showFill ? fillAlpha : 0;
                }

                // Update abandoned preview box
                const boxAbandoned = document.getElementById('territoryPreviewBoxAbandoned');
                const fillDivAbandoned = document.getElementById('territoryPreviewFillAbandoned');
                if (boxAbandoned) {
                    boxAbandoned.style.display = colorByActivity ? 'flex' : 'none';
                    boxAbandoned.style.borderColor = abandonedBorderColor;
                    boxAbandoned.style.borderWidth = thickness + 'px';
                    const label = boxAbandoned.querySelector('span');
                    if (label) {
                        label.style.color = abandonedBorderColor;
                        label.style.fontSize = fontSize + 'px';
                    }
                }
                if (fillDivAbandoned) {
                    fillDivAbandoned.style.background = abandonedFillColor;
                    fillDivAbandoned.style.opacity = showFill ? fillAlpha : 0;
                }

                const alphaLabel = document.getElementById('territoryFillAlphaValue');
                if (alphaLabel) alphaLabel.textContent = Math.round(fillAlpha * 100) + '%';

                // Save and redraw
                territorySettings.borderColor = color;
                territorySettings.borderThickness = thickness;
                territorySettings.showLabels = showLabels;
                territorySettings.labelFontSize = fontSize;
                territorySettings.showFill = showFill;
                territorySettings.fillColor = fillColor;
                territorySettings.fillAlpha = fillAlpha;
                territorySettings.colorByActivity = colorByActivity;
                territorySettings.activeBorderColor = activeBorderColor;
                territorySettings.activeFillColor = activeFillColor;
                territorySettings.abandonedBorderColor = abandonedBorderColor;
                territorySettings.abandonedFillColor = abandonedFillColor;
                saveTerritorySettings();
                drawTerritories();
            };

            ['territoryColorInput', 'territoryFillColorInput', 'territoryActiveBorderInput', 'territoryActiveFillInput', 'territoryAbandonedBorderInput', 'territoryAbandonedFillInput'].forEach(id => {
                document.getElementById(id)?.addEventListener('input', updatePreviewAndSave);
            });
            ['territoryThicknessSelect', 'territoryFontSelect'].forEach(id => {
                document.getElementById(id)?.addEventListener('change', updatePreviewAndSave);
            });
            ['territoryLabelsCheck', 'territoryFillCheck', 'territoryActivityCheck'].forEach(id => {
                document.getElementById(id)?.addEventListener('change', updatePreviewAndSave);
            });
            document.getElementById('territoryFillAlphaRange')?.addEventListener('input', updatePreviewAndSave);
        };

        // Defer event wiring until after DOM insertion
        setTimeout(wireEvents, 0);

        return wrapper;
    }

    /**
     * Add numbered badges (#1, #2, ...) to each project card in the guild modal.
     * Numbers match the logical display order used by the territory overlay.
     */
    function numberProjectCards() {
        const container = document.getElementById('guildProjectsContainer');
        if (!container) return;

        const cards = container.querySelectorAll(':scope > div');
        cards.forEach((card, i) => {
            // Skip if already numbered
            if (card.querySelector('.project-number-badge')) return;

            // Position the card for the badge
            card.style.position = 'relative';

            const badge = document.createElement('div');
            badge.className = 'project-number-badge';
            badge.textContent = `#${i + 1}`;
            badge.style.cssText = `
                position: absolute;
                top: 6px;
                left: 6px;
                background: var(--color-blue-500, #3b82f6);
                color: var(--color-white, #fff);
                font-size: 11px;
                font-weight: 700;
                padding: 2px 7px;
                border-radius: 6px;
                z-index: 5;
                box-shadow: 0 1px 4px rgba(0,0,0,0.2);
                pointer-events: none;
                line-height: 1.4;
            `;
            card.insertBefore(badge, card.firstChild);
        });
    }

    /**
     * Inject the territory controls into the Projects tab of the guild modal.
     */
    function injectTerritoryControls() {
        const projectsTab = document.getElementById('projectsTab');
        if (!projectsTab) return;

        // Don't inject twice
        if (document.getElementById('territoryControlsContainer')) return;

        const container = document.createElement('div');
        container.id = 'territoryControlsContainer';
        container.className = 'flex flex-col gap-3 mb-3 p-3 rounded-lg border';

        // Top row: toggle button + info (right-aligned)
        const topRow = document.createElement('div');
        topRow.className = 'flex items-center justify-between gap-2 flex-wrap';

        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'territoryToggleBtn';
        toggleBtn.className = territoryVisible ? 'territory-toggle-btn active' : 'territory-toggle-btn inactive';
        toggleBtn.innerHTML = territoryVisible ? '🗺️ Hide Territories' : '🗺️ Show Territories';
        toggleBtn.addEventListener('click', toggleTerritories);

        const playersBtn = document.createElement('button');
        playersBtn.id = 'playersToggleBtn';
        playersBtn.className = playersVisible ? 'territory-toggle-btn active' : 'territory-toggle-btn inactive';
        playersBtn.innerHTML = playersVisible ? '👥 Hide Players' : '👥 Show Players';
        playersBtn.addEventListener('click', togglePlayers);

        const exportBtn = document.createElement('button');
        exportBtn.id = 'exportTerritoriesBtn';
        exportBtn.className = 'territory-toggle-btn inactive';
        exportBtn.innerHTML = '📋 Export to Clipboard';
        exportBtn.title = 'Copy guild territories as JSON for the GeoPixels Json import';
        exportBtn.addEventListener('click', exportTerritoriesForJson);

        const info = document.createElement('span');
        info.className = 'territory-info-text';
        info.textContent = 'Overlay territories or player locations on the map';

        topRow.append(toggleBtn, playersBtn, exportBtn, info);
        container.appendChild(topRow);

        // Player marker options row (checkboxes)
        const optionsRow = document.createElement('div');
        optionsRow.id = 'playersOptionsRow';
        optionsRow.className = 'player-marker-options';
        optionsRow.style.display = playersVisible ? 'flex' : 'none';

        const showNamesLabel = document.createElement('label');
        const showNamesCheck = document.createElement('input');
        showNamesCheck.type = 'checkbox';
        showNamesCheck.id = 'playersShowNamesCheck';
        showNamesCheck.checked = playersShowNames;
        showNamesCheck.addEventListener('change', (e) => {
            playersShowNames = e.target.checked;
            refreshMarkerLabels();
        });
        showNamesLabel.appendChild(showNamesCheck);
        showNamesLabel.appendChild(document.createTextNode('Show all names'));

        const colorTerritoryLabel = document.createElement('label');
        const colorTerritoryCheck = document.createElement('input');
        colorTerritoryCheck.type = 'checkbox';
        colorTerritoryCheck.id = 'playersColorTerritoryCheck';
        colorTerritoryCheck.checked = playersColorByTerritory;
        colorTerritoryCheck.addEventListener('change', (e) => {
            playersColorByTerritory = e.target.checked;
            refreshMarkerColors();
        });
        colorTerritoryLabel.appendChild(colorTerritoryCheck);
        colorTerritoryLabel.appendChild(document.createTextNode('Blue if in territory'));

        const showInTerritoryLabel = document.createElement('label');
        const showInTerritoryCheck = document.createElement('input');
        showInTerritoryCheck.type = 'checkbox';
        showInTerritoryCheck.id = 'playersShowInTerritoryCheck';
        showInTerritoryCheck.checked = playersShowInTerritory;
        showInTerritoryCheck.addEventListener('change', (e) => {
            playersShowInTerritory = e.target.checked;
            updatePlayerPositions();
        });
        showInTerritoryLabel.appendChild(showInTerritoryCheck);
        showInTerritoryLabel.appendChild(document.createTextNode('Show in-territory'));

        const showOutsideTerritoryLabel = document.createElement('label');
        const showOutsideTerritoryCheck = document.createElement('input');
        showOutsideTerritoryCheck.type = 'checkbox';
        showOutsideTerritoryCheck.id = 'playersShowOutsideTerritoryCheck';
        showOutsideTerritoryCheck.checked = playersShowOutsideTerritory;
        showOutsideTerritoryCheck.addEventListener('change', (e) => {
            playersShowOutsideTerritory = e.target.checked;
            updatePlayerPositions();
        });
        showOutsideTerritoryLabel.appendChild(showOutsideTerritoryCheck);
        showOutsideTerritoryLabel.appendChild(document.createTextNode('Show outside territory'));

        optionsRow.append(showNamesLabel, colorTerritoryLabel, showInTerritoryLabel, showOutsideTerritoryLabel);
        container.appendChild(optionsRow);

        // Settings collapsible panels (full width)
        container.appendChild(buildTerritorySettingsPanel());
        container.appendChild(buildPlayerSettingsPanel());

        // Insert at the top of the projects tab, before the first child
        projectsTab.insertBefore(container, projectsTab.firstChild);
    }

    // =====================================================
    // === PLAYER MARKERS OVERLAY (New in 3.1.0) ===
    // =====================================================

    /**
     * Collect guild member positions from the currently rendered member list.
     * Returns array of { name, gridX, gridY } for members with coordinates.
     */
    function buildPlayerMarkerData() {
        const members = parseGuildMembers();
        if (!members) return [];

        const markers = [];
        for (const [key, data] of Object.entries(members)) {
            const coords = getCoords(data);
            if (coords) {
                // key is now a numeric ID string; use data.name (full display name) when available
                const name = (data && data.name) || key;
                markers.push({ name, gridX: coords[0], gridY: coords[1] });
            }
        }
        return markers;
    }

    /**
     * Create the players overlay container (a div for DOM marker elements).
     */
    function createPlayersContainer() {
        if (playersContainer) return;

        playersContainer = document.createElement('div');
        playersContainer.id = 'players-container';
        document.body.appendChild(playersContainer);
        console.log('[Guild Players] Players container created');
    }

    /**
     * Create a single Google-Maps-style pin marker DOM element for a player.
     * The pin tip anchors at the exact grid coordinate.
     */
    /**
     * Check if a grid coordinate falls inside any territory rectangle.
     */
    function isInsideTerritory(gridX, gridY) {
        for (const rect of territoryRects) {
            if (
                gridX >= rect.gridX &&
                gridX < rect.gridX + rect.width &&
                gridY <= rect.gridY &&
                gridY > rect.gridY - rect.height
            ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get the pin fill color for a marker based on territory status.
     */
    function getMarkerColor(inTerritory) {
        return (playersColorByTerritory && inTerritory) ? playerSettings.territoryColor : playerSettings.defaultColor;
    }

    function createMarkerElement(marker) {
        const wrapper = document.createElement('div');
        wrapper.className = 'player-marker' + (playersShowNames ? ' show-label' : '');
        wrapper.setAttribute('data-player', marker.name);

        const pinColor = getMarkerColor(marker.inTerritory);
        const w = playerSettings.markerSize;
        const h = Math.round(w * 40 / 28); // maintain aspect ratio 28:40

        // Google Maps teardrop SVG pin
        wrapper.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 24 36">
                <path class="pin-body" d="M12 0C5.4 0 0 5.4 0 12c0 9 12 24 12 24s12-15 12-24C24 5.4 18.6 0 12 0z" fill="${pinColor}"/>
                <circle cx="12" cy="11" r="4.5" fill="white"/>
            </svg>
            <div class="player-marker-tooltip" style="font-size:${playerSettings.labelFontSize}px">${marker.name.replace(/</g, '&lt;')}</div>
        `;

        // Click to teleport — find the member's actual Find button in the DOM
        // and .click() it (runs in page context), with fallback via script injection
        wrapper.addEventListener('click', (e) => {
            e.stopPropagation();

            let found = false;
            const memberRows = document.querySelectorAll('#guildMembersContainer div.flex.items-center.justify-between');
            for (const row of memberRows) {
                const nameEl = row.querySelector('p.font-semibold');
                if (nameEl) {
                    let displayName = nameEl.textContent.trim();
                    const badge = nameEl.querySelector('span');
                    if (badge) displayName = displayName.replace(badge.textContent, '').trim();
                    if (displayName === marker.name) {
                        const findBtn = row.querySelector('button[onclick^="goToGridLocation"]');
                        if (findBtn) {
                            findBtn.click();
                            found = true;
                            break;
                        }
                    }
                }
            }

            // Fallback: inject a script tag to call goToGridLocation in page context
            if (!found) {
                const s = document.createElement('script');
                s.textContent = `if(typeof goToGridLocation==='function')goToGridLocation(${parseInt(marker.gridX)},${parseInt(marker.gridY)});`;
                document.documentElement.appendChild(s);
                s.remove();
            }

            // Mark as visited
            const coordKey = `${marker.gridX},${marker.gridY}`;
            sessionState.visitedCoords.add(coordKey);
        });

        return wrapper;
    }

    /**
     * Convert a grid coordinate to screen pixel position using the same
     * pipeline as territory overlay: grid → Mercator → WGS84 → screen.
     */
    function gridToScreen(gridX, gridY, gSize) {
        if (typeof turf === 'undefined' || typeof map === 'undefined') return null;
        const mercCoord = [gridX * gSize, gridY * gSize];
        const screenPos = map.project(turf.toWgs84(mercCoord));
        return screenPos; // { x, y }
    }

    /**
     * Reposition all player marker DOM elements to match current map view.
     * Called on every map move/zoom/resize.
     */
    function updatePlayerPositions() {
        if (!playersContainer || !playersVisible) return;

        const gSize = (typeof gridSize !== 'undefined') ? gridSize : 25;
        const viewW = window.innerWidth;
        const viewH = window.innerHeight;
        const margin = 60; // off-screen buffer before hiding

        for (const marker of playerMarkerData) {
            // Hide based on territory visibility checkboxes
            if (marker.inTerritory && !playersShowInTerritory) {
                marker.element.style.display = 'none';
                continue;
            }
            if (!marker.inTerritory && !playersShowOutsideTerritory) {
                marker.element.style.display = 'none';
                continue;
            }

            const pos = gridToScreen(marker.gridX, marker.gridY, gSize);
            if (!pos) continue;

            // Frustum cull with margin
            if (pos.x < -margin || pos.x > viewW + margin || pos.y < -margin || pos.y > viewH + margin) {
                marker.element.style.display = 'none';
            } else {
                marker.element.style.display = '';
                marker.element.style.left = pos.x + 'px';
                marker.element.style.top = pos.y + 'px';
            }
        }
    }

    /**
     * Hook into map events so player markers reposition on pan/zoom/resize.
     */
    function hookPlayersToMap() {
        function waitForMapReady(callback) {
            let tries = 0;
            function check() {
                if (typeof map !== 'undefined' && map && map.on && map.getContainer) callback();
                else if (tries++ < 100) setTimeout(check, 100);
            }
            check();
        }

        waitForMapReady(() => {
            ['move', 'rotate', 'zoom'].forEach(ev => map.on(ev, updatePlayerPositions));
            new ResizeObserver(updatePlayerPositions).observe(map.getContainer());
            map.once('load', updatePlayerPositions);
            console.log('[Guild Players] Hooked to map events');
        });
    }

    /**
     * Toggle player markers overlay on/off.
     */
    /**
     * Update the "Find on Map" buttons in the XP Tracker tab to reflect
     * territory status (red for out-of-territory players) from playerMarkerData.
     */
    function updateXPTrackerMapButtons() {
        const xpPane = document.getElementById('xpTrackerPane');
        if (!xpPane) return;

        const mapBtns = xpPane.querySelectorAll('.map-icon[data-player-name]');
        mapBtns.forEach(btn => {
            const playerName = btn.getAttribute('data-player-name');
            if (!playerName) return;

            // Don't override visited state
            if (btn.classList.contains('visited')) return;

            if (playersVisible && playerMarkerData.length > 0) {
                const markerInfo = playerMarkerData.find(m => m.name === playerName);
                if (markerInfo && !markerInfo.inTerritory) {
                    btn.classList.add('out-of-territory');
                } else {
                    btn.classList.remove('out-of-territory');
                }
            } else {
                btn.classList.remove('out-of-territory');
            }
        });

        // Show/hide territory filter buttons in XP Tracker
        const xpTerritoryBtns = xpPane.querySelectorAll('.xp-territory-filter-btn');
        xpTerritoryBtns.forEach(btn => {
            btn.style.display = playersVisible ? '' : 'none';
        });
    }

    /**
     * Refresh all marker pin colors (e.g. after territory data changes or checkbox toggle).
     */
    function refreshMarkerColors() {
        for (const marker of playerMarkerData) {
            const pinBody = marker.element.querySelector('.pin-body');
            if (pinBody) {
                pinBody.setAttribute('fill', getMarkerColor(marker.inTerritory));
            }
        }
    }

    /**
     * Toggle show-label class on all markers.
     */
    function refreshMarkerLabels() {
        for (const marker of playerMarkerData) {
            marker.element.classList.toggle('show-label', playersShowNames);
        }
    }

    /**
     * Refresh all marker sizes and label font sizes from playerSettings.
     */
    function refreshMarkerSizes() {
        const w = playerSettings.markerSize;
        const h = Math.round(w * 40 / 28);
        for (const marker of playerMarkerData) {
            const svg = marker.element.querySelector('svg');
            if (svg) {
                svg.setAttribute('width', w);
                svg.setAttribute('height', h);
            }
            const tooltip = marker.element.querySelector('.player-marker-tooltip');
            if (tooltip) {
                tooltip.style.fontSize = playerSettings.labelFontSize + 'px';
            }
        }
    }

    /**
     * Build a collapsible settings panel for player marker appearance.
     */
    function buildPlayerSettingsPanel() {
        const wrapper = document.createElement('div');
        wrapper.className = 'territory-settings-collapsible';
        wrapper.id = 'playerSettingsCollapsible';

        const toggle = document.createElement('button');
        toggle.className = 'territory-settings-toggle';
        toggle.innerHTML = '<span>👥 Player Settings</span><span class="toggle-arrow collapsed">▼</span>';

        const content = document.createElement('div');
        content.className = 'territory-settings-content collapsed';

        const sizeOptions = [
            { value: 16, label: 'Tiny (16px)' },
            { value: 20, label: 'Small (20px)' },
            { value: 24, label: 'Medium (24px)' },
            { value: 28, label: 'Default (28px)' },
            { value: 34, label: 'Large (34px)' },
            { value: 42, label: 'Extra Large (42px)' }
        ];

        content.innerHTML = `
            <div class="territory-setting-row">
                <label>Marker Size</label>
                <select id="playerSizeSelect" class="px-2 py-1 rounded-md text-xs min-w-[120px]">
                    ${sizeOptions.map(opt =>
                        `<option value="${opt.value}" ${playerSettings.markerSize == opt.value ? 'selected' : ''}>${opt.label}</option>`
                    ).join('')}
                </select>
            </div>
            <div class="territory-setting-row">
                <label>Label Size</label>
                <select id="playerLabelSizeSelect" class="px-2 py-1 rounded-md text-xs min-w-[120px]">
                    ${[9, 10, 11, 12, 13, 14, 16].map(s =>
                        `<option value="${s}" ${playerSettings.labelFontSize == s ? 'selected' : ''}>${s}px</option>`
                    ).join('')}
                </select>
            </div>
            <div class="territory-setting-row">
                <label>Default Color</label>
                <input type="color" id="playerDefaultColorInput" value="${playerSettings.defaultColor}"
                       class="w-10 h-7 rounded-md cursor-pointer p-0.5">
            </div>
            <div class="territory-setting-row">
                <label>Territory Color</label>
                <input type="color" id="playerTerritoryColorInput" value="${playerSettings.territoryColor}"
                       class="w-10 h-7 rounded-md cursor-pointer p-0.5">
            </div>

            <div class="territory-section-divider">Preview</div>
            <div class="flex items-center justify-center gap-4 py-1">
                <div style="display:flex;flex-direction:column;align-items:center;gap:2px;">
                    <svg id="playerPreviewDefault" xmlns="http://www.w3.org/2000/svg" width="${playerSettings.markerSize}" height="${Math.round(playerSettings.markerSize*40/28)}" viewBox="0 0 24 36">
                        <path d="M12 0C5.4 0 0 5.4 0 12c0 9 12 24 12 24s12-15 12-24C24 5.4 18.6 0 12 0z" fill="${playerSettings.defaultColor}"/>
                        <circle cx="12" cy="11" r="4.5" fill="white"/>
                    </svg>
                    <span style="font-size:10px;color:var(--color-gray-500,#6b7280);">Outside</span>
                </div>
                <div style="display:flex;flex-direction:column;align-items:center;gap:2px;">
                    <svg id="playerPreviewTerritory" xmlns="http://www.w3.org/2000/svg" width="${playerSettings.markerSize}" height="${Math.round(playerSettings.markerSize*40/28)}" viewBox="0 0 24 36">
                        <path d="M12 0C5.4 0 0 5.4 0 12c0 9 12 24 12 24s12-15 12-24C24 5.4 18.6 0 12 0z" fill="${playerSettings.territoryColor}"/>
                        <circle cx="12" cy="11" r="4.5" fill="white"/>
                    </svg>
                    <span style="font-size:10px;color:var(--color-gray-500,#6b7280);">In Territory</span>
                </div>
            </div>
        `;

        toggle.addEventListener('click', () => {
            content.classList.toggle('collapsed');
            toggle.querySelector('.toggle-arrow').classList.toggle('collapsed');
        });

        wrapper.append(toggle, content);

        const wireEvents = () => {
            const update = () => {
                const size = parseInt(document.getElementById('playerSizeSelect')?.value);
                const labelSize = parseInt(document.getElementById('playerLabelSizeSelect')?.value);
                const defaultColor = document.getElementById('playerDefaultColorInput')?.value;
                const territoryColor = document.getElementById('playerTerritoryColorInput')?.value;

                playerSettings.markerSize = size;
                playerSettings.labelFontSize = labelSize;
                playerSettings.defaultColor = defaultColor;
                playerSettings.territoryColor = territoryColor;

                // Update preview
                const h = Math.round(size * 40 / 28);
                const prevDef = document.getElementById('playerPreviewDefault');
                const prevTer = document.getElementById('playerPreviewTerritory');
                if (prevDef) {
                    prevDef.setAttribute('width', size);
                    prevDef.setAttribute('height', h);
                    prevDef.querySelector('path').setAttribute('fill', defaultColor);
                }
                if (prevTer) {
                    prevTer.setAttribute('width', size);
                    prevTer.setAttribute('height', h);
                    prevTer.querySelector('path').setAttribute('fill', territoryColor);
                }

                savePlayerSettings();
                refreshMarkerSizes();
                refreshMarkerColors();
            };

            ['playerSizeSelect', 'playerLabelSizeSelect'].forEach(id => {
                document.getElementById(id)?.addEventListener('change', update);
            });
            ['playerDefaultColorInput', 'playerTerritoryColorInput'].forEach(id => {
                document.getElementById(id)?.addEventListener('input', update);
            });
        };

        setTimeout(wireEvents, 0);
        return wrapper;
    }

    function togglePlayers() {
        if (playersVisible) {
            // Turn off — remove all marker elements
            playersVisible = false;
            playerMarkerData.forEach(m => m.element.remove());
            playerMarkerData = [];
            updatePlayersToggleButton();
            updatePlayersOptionsVisibility();
            updateXPTrackerMapButtons();
            drawTerritories(); // refresh territory colors (remove red highlights)
            console.log('[Guild Players] Player markers hidden');
            return;
        }

        // Turn on — build markers from current guild members
        const toggleBtn = document.getElementById('playersToggleBtn');
        if (toggleBtn) {
            toggleBtn.disabled = true;
            toggleBtn.innerHTML = '⏳ Loading...';
        }

        const data = buildPlayerMarkerData();

        if (data.length === 0) {
            if (toggleBtn) {
                toggleBtn.disabled = false;
                toggleBtn.innerHTML = '👥 Show Players';
                toggleBtn.className = 'territory-toggle-btn inactive';
            }
            alert('No guild members with coordinates found. Make sure the guild Info tab has loaded.');
            return;
        }

        // If territory data is available, compute in-territory flag for each marker
        // If territoryRects hasn't been built yet, try building it now
        const enrichData = async () => {
            if (territoryRects.length === 0) {
                // Try to build territory rects (non-blocking, best-effort)
                try {
                    if (typeof userGuildData !== 'undefined' && userGuildData && typeof fetchGuildProjects === 'function') {
                        await fetchGuildProjects();
                    }
                    territoryRects = await buildTerritoryRects();
                } catch (e) {
                    console.warn('[Guild Players] Could not build territory rects for coloring:', e);
                }
            }

            // Mark each marker with territory membership
            for (const m of data) {
                m.inTerritory = isInsideTerritory(m.gridX, m.gridY);
            }

            createPlayersContainer();

            // Create DOM marker elements
            playerMarkerData = data.map(m => {
                const el = createMarkerElement(m);
                playersContainer.appendChild(el);
                return { ...m, element: el };
            });

            playersVisible = true;
            updatePlayerPositions();
            updatePlayersToggleButton();
            updatePlayersOptionsVisibility();
            updateXPTrackerMapButtons();
            drawTerritories(); // refresh territory colors (show red for unoccupied)

            const inTerritoryCount = data.filter(m => m.inTerritory).length;
            console.log(`[Guild Players] Showing ${playerMarkerData.length} player markers (${inTerritoryCount} in territory)`);

            if (toggleBtn) toggleBtn.disabled = false;
        };

        enrichData();
    }

    /**
     * Update the player toggle button appearance based on state.
     */
    function updatePlayersToggleButton() {
        const toggleBtn = document.getElementById('playersToggleBtn');
        if (!toggleBtn) return;

        if (playersVisible) {
            toggleBtn.innerHTML = '👥 Hide Players';
            toggleBtn.className = 'territory-toggle-btn active';
        } else {
            toggleBtn.innerHTML = '👥 Show Players';
            toggleBtn.className = 'territory-toggle-btn inactive';
        }
    }

    /**
     * Show/hide the player marker options row based on visibility.
     */
    function updatePlayersOptionsVisibility() {
        const optionsRow = document.getElementById('playersOptionsRow');
        if (optionsRow) {
            optionsRow.style.display = playersVisible ? 'flex' : 'none';
        }
    }

    // =====================================================
    // === MODAL TRANSFORMATION (inherited from v2.0) ===
    // =====================================================

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

        const membersContainer = document.getElementById('guildMembersContainer');
        if (membersContainer) {
            const observer = new MutationObserver(() => {
                ensureXPChangesSection();
                const members = parseGuildMembers();
                if (members && Object.keys(members).length > 0) {
                    saveGuildSnapshot(members);
                }
            });
            observer.observe(membersContainer, { childList: true, subtree: true });
        }

        ensureXPChangesSection();

        // Watch for the projects tab being shown so we can inject territory controls + number badges
        const projectsTab = document.getElementById('projectsTab');
        if (projectsTab) {
            const projectsObserver = new MutationObserver(() => {
                injectTerritoryControls();
                numberProjectCards();
            });
            projectsObserver.observe(projectsTab, { childList: true, subtree: true, attributes: true });
        }

        // Also watch the projects container specifically for re-renders
        const projectsContainer = document.getElementById('guildProjectsContainer');
        if (projectsContainer) {
            const containerObserver = new MutationObserver(() => {
                numberProjectCards();
            });
            containerObserver.observe(projectsContainer, { childList: true, subtree: true });
        }

        // Also hook into the projects tab button click
        const projectsTabBtn = document.getElementById('projectsTabBtn');
        if (projectsTabBtn) {
            const originalOnClick = projectsTabBtn.onclick;
            projectsTabBtn.addEventListener('click', () => {
                // Small delay to ensure tab content is visible
                setTimeout(() => {
                    injectTerritoryControls();
                    numberProjectCards();
                }, 50);
            });
        }
    }

    function setupMessageCollapsible() {
        const msgElement = document.getElementById('guildInfoMessage');
        if (!msgElement) return;

        const parent = msgElement.closest('div');
        if (!parent || parent.classList.contains('guild-message-section')) return;

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

        const header = document.createElement('div');
        header.className = 'guild-message-header';
        header.innerHTML = `<span>Guild Message</span><span class="guild-message-toggle">▼</span>`;

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

        parent.parentNode.insertBefore(section, parent);
        content.appendChild(parent);
        section.appendChild(header);
        section.appendChild(content);

        header.onclick = () => {
            content.classList.toggle('collapsed');
            header.querySelector('.guild-message-toggle').classList.toggle('collapsed');
            const infoTab = document.getElementById('infoTab');
            if (infoTab) infoTab.classList.toggle('message-collapsed', content.classList.contains('collapsed'));
        };
    }

    /**
     * Adds a slim loading progress bar below the header bar that tracks
     * guild data readiness: members loaded, XP section ready, projects available.
     * Auto-hides with a fade once all milestones are met.
     */
    function setupGuildLoadingBar(panel, headerBar) {
        if (document.getElementById('guild-loading-bar-container')) return;

        const container = document.createElement('div');
        container.id = 'guild-loading-bar-container';
        container.style.cssText = `
            position: absolute; top: 40px; left: 0; right: 0; height: 3px;
            background: rgba(0,0,0,0.1); z-index: 52; overflow: hidden;
            transition: opacity 0.5s ease; cursor: pointer;
        `;

        const bar = document.createElement('div');
        bar.id = 'guild-loading-bar';
        bar.style.cssText = `
            height: 100%; width: 0%; background: linear-gradient(90deg, #60a5fa, #3b82f6);
            transition: width 0.4s ease; border-radius: 0 2px 2px 0;
            pointer-events: none;
        `;
        container.appendChild(bar);

        // Hover tooltip
        const tooltip = document.createElement('div');
        tooltip.id = 'guild-loading-tooltip';
        tooltip.style.cssText = `
            position: fixed; display: none; padding: 6px 10px;
            background: ${isDarkMode() ? '#1e1e2e' : '#1f2937'}; color: #f3f4f6;
            font-size: 11px; line-height: 1.5; border-radius: 6px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3); pointer-events: none;
            z-index: 100000; white-space: nowrap;
        `;
        document.body.appendChild(tooltip);

        container.addEventListener('mouseenter', () => { tooltip.style.display = 'block'; updateTooltip(); });
        container.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; });
        container.addEventListener('mousemove', (e) => {
            tooltip.style.left = (e.clientX + 12) + 'px';
            tooltip.style.top = (e.clientY + 12) + 'px';
        });

        // Insert right after the header bar
        headerBar.insertAdjacentElement('afterend', container);

        // Milestones: each worth a portion of the bar
        const milestones = {
            modal:    { done: true,  weight: 10, label: 'Modal ready' },
            stats:    { done: false, weight: 20, label: 'Guild stats' },
            members:  { done: false, weight: 40, label: 'Members list' },
            xpTracker:{ done: false, weight: 30, label: 'XP Tracker' },
        };

        function updateTooltip() {
            const lines = Object.values(milestones).map(m =>
                (m.done ? '✅' : '⏳') + ' ' + m.label
            );
            tooltip.innerHTML = lines.join('<br>');
        }

        function updateProgress() {
            let progress = 0;
            let total = 0;
            const pending = [];
            for (const [key, m] of Object.entries(milestones)) {
                total += m.weight;
                if (m.done) progress += m.weight;
                else pending.push(key);
            }
            const pct = Math.round((progress / total) * 100);
            bar.style.width = pct + '%';

            if (pending.length === 0) {
                bar.style.width = '100%';
                updateTooltip();
                setTimeout(() => {
                    container.style.opacity = '0';
                    setTimeout(() => {
                        container.remove();
                        tooltip.remove();
                    }, 600);
                }, 800);
            }
            updateTooltip();
        }

        function markDone(key) {
            if (milestones[key] && !milestones[key].done) {
                milestones[key].done = true;
                updateProgress();
            }
        }

        // Check milestones periodically
        function poll() {
            // Stats: guild XP / pixels text is populated
            const xpEl = document.getElementById('guildInfoExperience');
            if (xpEl && xpEl.textContent.trim().length > 0) markDone('stats');

            // Members: guildMembersContainer has member rows
            const membersEl = document.getElementById('guildMembersContainer');
            if (membersEl && membersEl.querySelectorAll('div.flex.items-center.justify-between').length > 0) {
                markDone('members');
            }

            // XP Tracker: our injected tab button or legacy section exists
            if (document.getElementById('xpTrackerTabBtn') || document.getElementById('xpChangesSection')) markDone('xpTracker');

            // Keep polling until all done
            const allDone = Object.values(milestones).every(m => m.done);
            if (!allDone) setTimeout(poll, 300);
        }

        updateProgress();
        setTimeout(poll, 200);
    }

    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.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();

            // --- Loading progress bar ---
            setupGuildLoadingBar(panel, headerBar);

            // Inject territory controls and number badges when projects tab is available
            setTimeout(() => {
                injectTerritoryControls();
                numberProjectCards();
            }, 200);

            // Reset panel to center every time the modal is opened (fixes off-screen lock after dragging outside window)
            const _centerPanel = () => {
                panel.style.top = '100px';
                panel.style.left = 'calc(50% - 25rem)';
            };
            const _visibilityObserver = new MutationObserver(() => {
                if (!modal.classList.contains('hidden')) _centerPanel();
            });
            _visibilityObserver.observe(modal, { attributes: true, attributeFilter: ['class'] });

            console.log('[Guild Modal] v3.1 - Transformed to draggable floating panel with territories');

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

    function setupDragHandling(panel, header) {
        let isDragging = false;
        let startX = 0, startY = 0, offsetX = 0, 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;
            panel.style.left = (offsetX + deltaX) + 'px';
            panel.style.top = (offsetY + deltaY) + 'px';
        };

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

        header.addEventListener('mousedown', onMouseDown, true);

        // Also make the header bar itself draggable
        const headerBar = panel.querySelector('.guild-modal-header');
        if (headerBar && headerBar !== header) {
            headerBar.addEventListener('mousedown', onMouseDown, true);
        }
    }

    function setupResizeHandling(panel, handle) {
        let isResizing = false;
        let startX = 0, startY = 0, startW = 0, startH = 0;

        handle.addEventListener('mousedown', (e) => {
            isResizing = true;
            startX = e.clientX;
            startY = e.clientY;
            const rect = panel.getBoundingClientRect();
            startW = rect.width;
            startH = rect.height;
            panel.style.userSelect = 'none';
            document.addEventListener('mousemove', onMouseMove, true);
            document.addEventListener('mouseup', onMouseUp, true);
            e.preventDefault();
            e.stopPropagation();
        });

        const onMouseMove = (e) => {
            if (!isResizing) return;
            const newW = Math.max(300, startW + (e.clientX - startX));
            const newH = Math.max(200, startH + (e.clientY - startY));
            panel.style.width = newW + 'px';
            panel.style.maxHeight = newH + 'px';
        };

        const onMouseUp = () => {
            isResizing = false;
            panel.style.userSelect = 'auto';
            document.removeEventListener('mousemove', onMouseMove, true);
            document.removeEventListener('mouseup', onMouseUp, true);
        };
    }

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

    // --- Menu Commands ---
    // Commented out to keep the Tampermonkey menu clean.
    // Uncomment any block below to re-expose it in the menu.
    // All underlying functionality remains intact and accessible via the in-page UI.

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

    GM_registerMenuCommand("Toggle Territory Overlay", () => {
        toggleTerritories();
    });

    GM_registerMenuCommand("Toggle Player Markers", () => {
        togglePlayers();
    });

    GM_registerMenuCommand("Territory Settings", () => {
        // Open the guild modal projects tab where settings live
        if (typeof window.toggleMyGuildModal === 'function') {
            const modal = document.getElementById('myGuildModal');
            if (modal && modal.classList.contains('hidden')) window.toggleMyGuildModal();
            if (typeof window.switchGuildTab === 'function') window.switchGuildTab('projects');
            setTimeout(() => {
                const collapsible = document.getElementById('territorySettingsCollapsible');
                if (collapsible) {
                    const content = collapsible.querySelector('.territory-settings-content');
                    const arrow = collapsible.querySelector('.toggle-arrow');
                    if (content && content.classList.contains('collapsed')) {
                        content.classList.remove('collapsed');
                        if (arrow) arrow.classList.remove('collapsed');
                    }
                }
            }, 200);
        }
    });
    */

    // --- Initialization ---

    function init() {
        transformGuildModal();
        hookTerritoryToMap();
        hookPlayersToMap();

        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] v3.4.0 - Loaded with territory map overlay, player markers, territory-aware XP tracker, and activity-aware territory coloring');

            })();
            _featureStatus.guildOverhaul = 'ok';
            console.log('[GeoPixelcons++] ✅ Guild Overhaul loaded');
        } catch (err) {
            _featureStatus.guildOverhaul = 'error';
            dbgPush(`Guild Overhaul init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Guild Overhaul' });
            console.error('[GeoPixelcons++] ❌ Guild Overhaul failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Paint Brush Swap [paintBrushSwap]
    // ============================================================
    if (_settings.paintBrushSwap) {
        try {
            (function _init_paintBrushSwap() {

    // Page window reference — needed because GPC++ uses @grant which sandboxes `window`
    const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;

    // Helper to set page-scope `let` variables (not accessible via window/unsafeWindow)
    // Injects a <script> tag so the assignment runs in the page's own global scope
    function _setPageVar(name, value) {
        try {
            const s = document.createElement('script');
            s.textContent = `${name} = ${JSON.stringify(value)};`;
            (document.head || document.documentElement).appendChild(s);
            s.remove();
        } catch {}
    }
    function _runInPage(code) {
        try {
            const s = document.createElement('script');
            s.textContent = code;
            (document.head || document.documentElement).appendChild(s);
            s.remove();
        } catch {}
    }

    // ============================================
    // DEBUG MODE
    // ============================================
    const DEBUG = false; // Set to true for console logging

    // ============================================
    // STATE MANAGEMENT
    // ============================================
    const STORAGE_KEY = 'brushPresets';
    const RESIZE_STORAGE_KEY = 'brushSwapDropdownSize';
    const MAX_BRUSHES = 100;

    const scriptState = {
        brushes: [],
        nextId: 1,
        dropdownOpen: false,
        isRenaming: null, // Track which brush ID is being renamed
        scrollIndex: -1,  // Track current scroll-swap index (-1 = no selection)
        activeBrushId: null, // Track which brush is currently loaded
        dragState: null   // Track drag-to-reorder state
    };

    // ============================================
    // UTILITY FUNCTIONS
    // ============================================

    function loadBrushes() {
        const saved = localStorage.getItem(STORAGE_KEY);
        if (saved) {
            try {
                scriptState.brushes = JSON.parse(saved);
                scriptState.nextId = Math.max(...scriptState.brushes.map(b => b.id), 0) + 1;
            } catch (e) {
                console.error('Failed to parse brush presets:', e);
                scriptState.brushes = [];
                scriptState.nextId = 1;
            }
        }
    }

    function saveBrushes() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(scriptState.brushes));
    }

    function addBrush(pattern, brushSize) {
        if (scriptState.brushes.length >= MAX_BRUSHES) {
            // Delete oldest brush (first in array)
            scriptState.brushes.shift();
        }

        const newBrush = {
            id: scriptState.nextId++,
            name: `Brush ${scriptState.nextId}`,
            pattern: pattern,
            brushSize: brushSize
        };

        scriptState.brushes.push(newBrush);
        saveBrushes();
        return newBrush;
    }

    function deleteBrush(id) {
        scriptState.brushes = scriptState.brushes.filter(b => b.id !== id);
        saveBrushes();
        renderDropdown();
    }

    function renameBrush(id, newName) {
        const brush = scriptState.brushes.find(b => b.id === id);
        if (brush) {
            brush.name = newName.trim() || `Brush ${id}`;
            saveBrushes();
            renderDropdown();
        }
    }

    // ============================================
    // BRUSH CAPTURE FROM DOM
    // ============================================

    function captureBrushFromDOM() {
        const brushGrid = document.getElementById('brushGrid');
        if (!brushGrid) {
            console.warn('Brush Swap: brushGrid not found');
            return null;
        }

        const cells = brushGrid.querySelectorAll('div[data-x][data-y]');
        const pattern = [];
        let minX = Infinity, maxX = -Infinity;
        let minY = Infinity, maxY = -Infinity;
        let centerX = -1, centerY = -1;

        // Collect all active cells and find bounds, also locate center marker
        cells.forEach(cell => {
            if (cell.dataset.active === 'true') {
                const x = parseInt(cell.dataset.x);
                const y = parseInt(cell.dataset.y);
                pattern.push({ gridX: x, gridY: y });
                minX = Math.min(minX, x);
                maxX = Math.max(maxX, x);
                minY = Math.min(minY, y);
                maxY = Math.max(maxY, y);

                // Find center marker
                if (cell.dataset.isCenter === 'true' || cell.dataset.isCenter === 'true') {
                    centerX = x;
                    centerY = y;
                }
            }
        });

        if (pattern.length === 0) {
            console.warn('Brush Swap: No active cells in brush');
            return null;
        }

        // Calculate brush size from grid bounds
        const brushSize = Math.max(maxX - minX + 1, maxY - minY + 1);

        // If center wasn't found (shouldn't happen), use grid center
        if (centerX === -1 || centerY === -1) {
            centerX = Math.floor(brushSize / 2);
            centerY = Math.floor(brushSize / 2);
        }

        // Convert grid coordinates to relative coordinates, centered on the actual center pixel
        const relativePattern = pattern.map(p => ({
            x: p.gridX - centerX,
            y: (p.gridY - centerY) * -1 // Invert Y for consistency
        }));

        if (DEBUG) console.log('Brush Swap: Captured brush from DOM', {
            brushSize,
            centerX,
            centerY,
            pattern: relativePattern,
            cellCount: pattern.length
        });

        return {
            pattern: relativePattern,
            brushSize: brushSize
        };
    }

    function loadBrush(id) {
        const brush = scriptState.brushes.find(b => b.id === id);
        if (!brush) return;

        applyBrushToEditor(brush);
        scriptState.activeBrushId = id;
        toggleDropdown();
    }

    function applyBrushToEditor(brush) {
        // Track which brush is active
        scriptState.activeBrushId = brush.id;
        // Set page globals — BrushSize is `let`-declared so _pw.BrushSize won't reach it
        _setPageVar('BrushSize', brush.brushSize);
        _setPageVar('currentBrushPattern', [...brush.pattern]);
        // Also mirror on _pw for any code that reads from window
        _pw.BrushSize = brush.brushSize;
        _pw.currentBrushPattern = [...brush.pattern];

        if (DEBUG) console.log('Brush Swap: Set globals', {
            BrushSize: _pw.BrushSize,
            currentBrushPattern: _pw.currentBrushPattern
        });

        // Update userConfig
        if (_pw.userConfig) {
            _pw.userConfig = {
                ..._pw.userConfig,
                currentBrushPattern: _pw.currentBrushPattern,
                brushSize: _pw.BrushSize
            };
            localStorage.setItem('userConfig', JSON.stringify(_pw.userConfig));
            if (DEBUG) console.log('Brush Swap: Updated userConfig');
        }

        // Call server save if available
        _pw.saveConfigServer?.();

        // Regenerate the brush grid to reflect the new pattern/size
        _runInPage('generateBrushGrid(currentBrushPattern)');

        if (DEBUG) console.log('Brush Swap: Applied brush to editor', brush);
    }

    // ============================================
    // BRUSH DIMENSION CONTROL
    // ============================================

    function addBrushDimensionDropdown() {
        const brushEditorPanel = document.getElementById('brushEditorPanel');
        if (!brushEditorPanel) return;

        // Check if dropdown already exists
        if (document.getElementById('brush-swap-dimension-select')) return;

        // Find the header area to insert dropdown
        const header = brushEditorPanel.querySelector('h2');
        if (!header) return;

        // Create dropdown container with Tailwind classes
        const dropdownContainer = document.createElement('div');
        dropdownContainer.className = 'flex gap-2 items-center mb-3 px-1.5 dark:text-gray-300';

        // Create label
        const label = document.createElement('label');
        label.textContent = 'Grid Size:';
        label.className = 'text-xs font-semibold text-gray-700 dark:text-gray-300';

        // Create select
        const select = document.createElement('select');
        select.id = 'brush-swap-dimension-select';
        select.className = 'px-2 py-1 text-xs border rounded bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 cursor-pointer';

        const options = [
            { value: 1, label: '1×1' },
            { value: 3, label: '3×3' },
            { value: 5, label: '5×5' },
            { value: 7, label: '7×7' },
            { value: 9, label: '9×9' },
            { value: 11, label: '11×11' },
            { value: 13, label: '13×13' },
            { value: 15, label: '15×15' },
            { value: 17, label: '17×17' },
            { value: 19, label: '19×19' },
            { value: 21, label: '21×21' }
        ];

        // Ensure current BrushSize is in the list
        const curSize = _pw.BrushSize || 5;
        if (!options.some(o => o.value === curSize)) {
            options.push({ value: curSize, label: curSize + '×' + curSize });
            options.sort((a, b) => a.value - b.value);
        }

        options.forEach(opt => {
            const option = document.createElement('option');
            option.value = opt.value;
            option.textContent = opt.label;
            select.appendChild(option);
        });

        // Set current BrushSize as selected
        select.value = _pw.BrushSize || 5;

        // Handle change
        select.addEventListener('change', (e) => {
            const newSize = parseInt(e.target.value);
            _setPageVar('BrushSize', newSize);
            _pw.BrushSize = newSize;
            if (DEBUG) console.log(`Brush Swap: Changed grid size to ${newSize}x${newSize}`);

            // Regenerate grid with new size
            _runInPage('generateBrushGrid(currentBrushPattern)');
        });

        dropdownContainer.appendChild(label);
        dropdownContainer.appendChild(select);

        // Insert after the header
        header.parentNode.insertBefore(dropdownContainer, header.nextSibling);
    }

    // ============================================
    // BRUSH EDITOR ENHANCEMENTS (fill + drag-paint)
    // ============================================

    function _installBrushEditorEnhancements() {
        const panel = document.getElementById('brushEditorPanel');
        if (!panel) return;

        // ── Fill + Reset button row ──────────────────────────
        if (!panel.querySelector('#gpc-brush-fill-btn')) {
            const resetBtn = panel.querySelector('button[onclick="resetBrush()"]');
            if (resetBtn) {
                const container = resetBtn.parentElement;

                // Halve reset button width to make room for fill
                resetBtn.classList.remove('w-full');
                resetBtn.style.width = '50%';

                // Create fill button
                const fillBtn = document.createElement('button');
                fillBtn.id = 'gpc-brush-fill-btn';
                fillBtn.style.width = '50%';
                fillBtn.className = 'py-2 text-sm text-gray-500 hover:text-green-600 hover:bg-gray-50 rounded transition cursor-pointer text-center';
                fillBtn.textContent = 'Fill All';
                fillBtn.title = 'Set all cells to active';
                fillBtn.onclick = () => {
                    const grid = document.getElementById('brushGrid');
                    if (!grid) return;
                    grid.querySelectorAll('[data-x][data-y]').forEach(cell => {
                        cell.dataset.active = 'true';
                        cell.classList.add('!bg-gray-800');
                        cell.classList.remove('bg-white', 'hover:bg-gray-100', 'bg-red-100', 'hover:bg-red-200');
                    });
                };

                container.style.display = 'flex';
                container.style.gap = '8px';
                container.appendChild(fillBtn);
            }
        }

        // ── Drag-to-paint / drag-to-erase on brushGrid ───────
        _installBrushGridDrag();
    }

    function _installBrushGridDrag() {
        const grid = document.getElementById('brushGrid');
        if (!grid || grid._gpcDragInstalled) return;
        grid._gpcDragInstalled = true;

        function setCellActive(cell, active) {
            if (!cell || !cell.dataset || !('x' in cell.dataset)) return;
            const isCenter = cell.dataset.isCenter === 'true';
            cell.dataset.active = active ? 'true' : 'false';
            if (active) {
                cell.classList.add('!bg-gray-800');
                cell.classList.remove('bg-white', 'hover:bg-gray-100', 'bg-red-100', 'hover:bg-red-200');
            } else {
                cell.classList.remove('!bg-gray-800');
                if (isCenter) {
                    cell.classList.add('bg-red-100', 'hover:bg-red-200');
                } else {
                    cell.classList.add('bg-white', 'hover:bg-gray-100');
                }
            }
        }

        // Remove game's onclick handlers (we fully own click/drag behavior)
        function removeOnclikHandlers() {
            grid.querySelectorAll('[data-x][data-y]').forEach(cell => { cell.onclick = null; });
        }
        removeOnclikHandlers();

        // Re-remove onclick whenever generateBrushGrid rebuilds the cells
        const gridObserver = new MutationObserver(removeOnclikHandlers);
        gridObserver.observe(grid, { childList: true });

        let isDragging = false;
        let dragMode = null;    // 'paint' | 'erase'

        grid.addEventListener('mousedown', (e) => {
            const cell = e.target.closest('[data-x][data-y]');
            if (!cell) return;
            e.preventDefault(); // Prevent text selection
            if (e.button === 0) {
                dragMode = 'paint';
                isDragging = true;
                setCellActive(cell, true); // Immediately activate on mousedown
            } else if (e.button === 2) {
                dragMode = 'erase';
                isDragging = true;
                setCellActive(cell, false); // Right-click immediately erases
            }
        });

        grid.addEventListener('mousemove', (e) => {
            if (!isDragging || !dragMode) return;
            const el = document.elementFromPoint(e.clientX, e.clientY);
            const cell = el ? el.closest('[data-x][data-y]') : null;
            if (!cell) return;
            setCellActive(cell, dragMode === 'paint');
        });

        grid.addEventListener('contextmenu', (e) => {
            e.preventDefault(); // Suppress right-click context menu on the grid
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            dragMode = null;
        });
    }

    // ============================================

    function createBrushPreview(brush) {
        const grid = document.createElement('div');
        grid.className = 'brush-swap-preview-grid';

        // Create a map of active cells based on pattern
        const activeCells = new Map();
        const centerOffset = Math.floor(brush.brushSize / 2);
        let minX = Infinity, maxX = -Infinity;
        let minY = Infinity, maxY = -Infinity;

        brush.pattern.forEach(offset => {
            // Convert from relative coordinates to grid coordinates
            const gridX = offset.x + centerOffset;
            const gridY = (offset.y * -1) + centerOffset; // Denormalize Y-axis
            activeCells.set(`${gridX},${gridY}`, true);
            minX = Math.min(minX, gridX);
            maxX = Math.max(maxX, gridX);
            minY = Math.min(minY, gridY);
            maxY = Math.max(maxY, gridY);
        });

        // Calculate preview dimensions
        const width = maxX - minX + 1;
        const height = maxY - minY + 1;
        const maxDim = Math.max(width, height);

        // Scale cells to fit compact preview (8px max per cell)
        const cellSize = Math.max(4, Math.floor(32 / maxDim));

        // Calculate center of the pattern bounds (not the grid size)
        const patternCenterX = minX + Math.floor((maxX - minX) / 2);
        const patternCenterY = minY + Math.floor((maxY - minY) / 2);

        // Build preview with full pattern bounds
        for (let y = minY; y <= maxY; y++) {
            for (let x = minX; x <= maxX; x++) {
                const cell = document.createElement('div');
                cell.className = 'brush-swap-preview-cell';
                cell.style.width = cellSize + 'px';
                cell.style.height = cellSize + 'px';

                const isActive = activeCells.has(`${x},${y}`);
                const isCenter = x === patternCenterX && y === patternCenterY;

                if (isActive) {
                    cell.classList.add('active');
                    if (isCenter) {
                        cell.classList.add('center');
                    }
                }

                grid.appendChild(cell);
            }
        }

        // Set grid columns dynamically
        grid.style.gridTemplateColumns = `repeat(${width}, ${cellSize}px)`;

        return grid;
    }

    // ============================================
    // DRAG REORDER
    // ============================================

    function setupDragReorder(handle, itemEl, fromIdx, container) {
        handle.addEventListener('mousedown', (e) => {
            e.preventDefault();
            e.stopPropagation();

            const items = Array.from(container.querySelectorAll('[data-brush-idx]'));
            const rects = items.map(el => el.getBoundingClientRect());
            let currentDropIdx = fromIdx;

            itemEl.classList.add('brush-swap-item-dragging');

            // Remove any existing indicator
            let indicator = container.querySelector('.brush-swap-drop-indicator');

            function onMove(ev) {
                const y = ev.clientY;

                // Find which slot we're hovering
                let dropIdx = items.length; // default to end
                for (let i = 0; i < rects.length; i++) {
                    const mid = rects[i].top + rects[i].height / 2;
                    if (y < mid) {
                        dropIdx = i;
                        break;
                    }
                }
                if (dropIdx === currentDropIdx) return;
                currentDropIdx = dropIdx;

                // Remove old indicator
                if (indicator) indicator.remove();

                // Insert indicator at the drop position
                indicator = document.createElement('div');
                indicator.className = 'brush-swap-drop-indicator';
                if (dropIdx < items.length) {
                    container.insertBefore(indicator, items[dropIdx]);
                } else {
                    container.appendChild(indicator);
                }
            }

            function onUp() {
                document.removeEventListener('mousemove', onMove);
                document.removeEventListener('mouseup', onUp);
                itemEl.classList.remove('brush-swap-item-dragging');
                if (indicator) indicator.remove();

                // Perform the reorder
                if (currentDropIdx !== fromIdx && currentDropIdx !== fromIdx + 1) {
                    const [moved] = scriptState.brushes.splice(fromIdx, 1);
                    const insertAt = currentDropIdx > fromIdx ? currentDropIdx - 1 : currentDropIdx;
                    scriptState.brushes.splice(insertAt, 0, moved);
                    saveBrushes();
                    // Update scrollIndex to follow the moved brush
                    const newIdx = scriptState.brushes.findIndex(b => b.id === moved.id);
                    if (scriptState.scrollIndex === fromIdx) scriptState.scrollIndex = newIdx;
                }
                renderDropdown();
            }

            document.addEventListener('mousemove', onMove);
            document.addEventListener('mouseup', onUp);
        });
    }

    // ============================================
    // UI RENDERING
    // ============================================

    // ============================================
    // BRUSH EXPORT / IMPORT
    // ============================================

    function showExportBrushesModal() {
        const existing = document.getElementById('gpc-brush-export-modal');
        if (existing) { existing.remove(); return; }

        const dark = document.body.classList.contains('dark') ||
                     window.matchMedia('(prefers-color-scheme: dark)').matches;

        const overlay = document.createElement('div');
        overlay.id = 'gpc-brush-export-modal';
        overlay.style.cssText = `
            position: fixed; inset: 0; z-index: 100000;
            background: rgba(0,0,0,0.5); display: flex;
            align-items: center; justify-content: center;
            font-family: system-ui, -apple-system, sans-serif;
        `;
        overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });

        const modal = document.createElement('div');
        modal.style.cssText = `
            background: ${dark ? '#1e1e2e' : '#ffffff'};
            color: ${dark ? '#cdd6f4' : '#1e293b'};
            border-radius: 12px; padding: 0; width: 480px; max-width: 95vw;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden;
        `;

        // ── Header ──
        const headerTitle = document.createElement('span');
        headerTitle.style.cssText = 'font-weight:700;font-size:15px;';
        headerTitle.textContent = `\uD83D\uDCE4 Export Brushes`;

        const header = document.createElement('div');
        header.style.cssText = `
            padding: 14px 20px; display: flex; align-items: center;
            justify-content: space-between;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border-bottom: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
        `;
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '\u2715';
        closeBtn.style.cssText = `background:none;border:none;font-size:18px;cursor:pointer;color:${dark ? '#a6adc8' : '#64748b'};padding:4px 8px;border-radius:4px;`;
        closeBtn.onmouseenter = () => closeBtn.style.background = dark ? '#45475a' : '#e2e8f0';
        closeBtn.onmouseleave = () => closeBtn.style.background = 'none';
        closeBtn.onclick = () => overlay.remove();
        header.appendChild(headerTitle);
        header.appendChild(closeBtn);
        modal.appendChild(header);

        const body = document.createElement('div');
        body.style.cssText = 'padding: 16px 20px; display: flex; flex-direction: column; gap: 12px;';

        const hint = document.createElement('div');
        hint.style.cssText = `font-size:13px;color:${dark ? '#a6adc8' : '#64748b'};`;
        hint.textContent = 'Select the brushes to export, then copy or download the JSON.';
        body.appendChild(hint);

        // ── Brush checklist ──
        const listWrap = document.createElement('div');
        listWrap.style.cssText = `
            display: flex; flex-direction: column; gap: 2px;
            max-height: 180px; overflow-y: auto;
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            border-radius: 8px; padding: 6px 8px;
            background: ${dark ? '#181825' : '#f8fafc'};
        `;

        // Select All / None row
        const selRow = document.createElement('div');
        selRow.style.cssText = 'display:flex;gap:10px;margin-bottom:4px;';
        const selAll = document.createElement('button');
        selAll.textContent = 'Select All';
        selAll.style.cssText = `background:none;border:none;cursor:pointer;font-size:11px;font-weight:600;padding:0;color:${dark ? '#89b4fa' : '#3b82f6'};`;
        const selNone = document.createElement('button');
        selNone.textContent = 'Select None';
        selNone.style.cssText = selAll.style.cssText;
        selRow.appendChild(selAll);
        selRow.appendChild(selNone);
        listWrap.appendChild(selRow);

        const checkboxes = [];
        scriptState.brushes.forEach((brush) => {
            const row = document.createElement('label');
            row.style.cssText = `display:flex;align-items:center;gap:8px;padding:3px 2px;cursor:pointer;border-radius:4px;font-size:13px;`;
            row.onmouseenter = () => row.style.background = dark ? '#313244' : '#f1f5f9';
            row.onmouseleave = () => row.style.background = 'none';

            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.checked = true;
            cb.style.cssText = 'flex-shrink:0;cursor:pointer;accent-color:#3b82f6;';
            cb.addEventListener('change', updateOutput);

            const nameSpan = document.createElement('span');
            nameSpan.textContent = brush.name;
            nameSpan.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';

            row.appendChild(cb);
            row.appendChild(nameSpan);
            listWrap.appendChild(row);
            checkboxes.push({ cb, brush });
        });

        selAll.onclick  = () => { checkboxes.forEach(x => { x.cb.checked = true;  }); updateOutput(); };
        selNone.onclick = () => { checkboxes.forEach(x => { x.cb.checked = false; }); updateOutput(); };

        body.appendChild(listWrap);

        // ── JSON output ──
        const textarea = document.createElement('textarea');
        textarea.readOnly = true;
        textarea.style.cssText = `
            width: 100%; min-height: 120px; max-height: 200px; box-sizing: border-box;
            padding: 10px; font-family: monospace; font-size: 12px;
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            border-radius: 8px; resize: vertical; outline: none;
            background: ${dark ? '#181825' : '#f8fafc'};
            color: ${dark ? '#cdd6f4' : '#1e293b'};
        `;
        textarea.addEventListener('click', () => textarea.select());
        body.appendChild(textarea);

        function getSelected() {
            return checkboxes.filter(x => x.cb.checked).map(x => x.brush);
        }
        function buildJson(brushList) {
            return JSON.stringify(brushList.map(b => ({ name: b.name, pattern: b.pattern, brushSize: b.brushSize })), null, 2);
        }
        function updateOutput() {
            const sel = getSelected();
            headerTitle.textContent = `\uD83D\uDCE4 Export Brushes (${sel.length} of ${scriptState.brushes.length})`;
            textarea.value = sel.length ? buildJson(sel) : '';
        }
        updateOutput();

        // ── Buttons ──
        const btnRow = document.createElement('div');
        btnRow.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;';

        const copyBtn = document.createElement('button');
        copyBtn.style.cssText = `
            padding: 7px 16px; border-radius: 8px; border: none; cursor: pointer; font-size: 13px; font-weight: 600;
            background: ${dark ? '#585b70' : '#e2e8f0'}; color: ${dark ? '#cdd6f4' : '#1e293b'};
            transition: background 0.15s;
        `;
        copyBtn.textContent = '\uD83D\uDCCB Copy to Clipboard';
        copyBtn.onmouseenter = () => copyBtn.style.background = dark ? '#6c7086' : '#cbd5e1';
        copyBtn.onmouseleave = () => copyBtn.style.background = dark ? '#585b70' : '#e2e8f0';
        copyBtn.onclick = () => {
            const json = buildJson(getSelected());
            if (!json) return;
            navigator.clipboard.writeText(json).then(() => {
                copyBtn.textContent = '\u2705 Copied!';
                setTimeout(() => { copyBtn.textContent = '\uD83D\uDCCB Copy to Clipboard'; }, 1800);
            }).catch(() => {
                textarea.select();
                document.execCommand('copy');
                copyBtn.textContent = '\u2705 Copied!';
                setTimeout(() => { copyBtn.textContent = '\uD83D\uDCCB Copy to Clipboard'; }, 1800);
            });
        };

        const dlBtn = document.createElement('button');
        dlBtn.style.cssText = `
            padding: 7px 16px; border-radius: 8px; border: none; cursor: pointer; font-size: 13px; font-weight: 600;
            background: ${dark ? '#89b4fa' : '#3b82f6'}; color: ${dark ? '#1e1e2e' : '#ffffff'};
            transition: background 0.15s;
        `;
        dlBtn.textContent = '\uD83D\uDCBE Download';
        dlBtn.onmouseenter = () => dlBtn.style.background = dark ? '#74c7ec' : '#2563eb';
        dlBtn.onmouseleave = () => dlBtn.style.background = dark ? '#89b4fa' : '#3b82f6';
        dlBtn.onclick = () => {
            const json = buildJson(getSelected());
            if (!json) return;
            const blob = new Blob([json], { type: 'application/json' });
            const a = document.createElement('a');
            a.href = URL.createObjectURL(blob);
            a.download = `gpc-brushes-${new Date().toISOString().slice(0,10)}.json`;
            document.body.appendChild(a);
            a.click();
            setTimeout(() => { a.remove(); URL.revokeObjectURL(a.href); }, 1000);
        };

        btnRow.appendChild(copyBtn);
        btnRow.appendChild(dlBtn);
        body.appendChild(btnRow);
        modal.appendChild(body);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);
    }

    function showImportBrushesModal() {
        const existing = document.getElementById('gpc-brush-import-modal');
        if (existing) { existing.remove(); return; }

        const dark = document.body.classList.contains('dark') ||
                     window.matchMedia('(prefers-color-scheme: dark)').matches;

        const overlay = document.createElement('div');
        overlay.id = 'gpc-brush-import-modal';
        overlay.style.cssText = `
            position: fixed; inset: 0; z-index: 100000;
            background: rgba(0,0,0,0.5); display: flex;
            align-items: center; justify-content: center;
            font-family: system-ui, -apple-system, sans-serif;
        `;
        overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });

        const modal = document.createElement('div');
        modal.style.cssText = `
            background: ${dark ? '#1e1e2e' : '#ffffff'};
            color: ${dark ? '#cdd6f4' : '#1e293b'};
            border-radius: 12px; padding: 0; width: 480px; max-width: 95vw;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden;
        `;

        // ── Header ──
        const header = document.createElement('div');
        header.style.cssText = `
            padding: 14px 20px; display: flex; align-items: center;
            justify-content: space-between;
            background: ${dark ? '#313244' : '#f1f5f9'};
            border-bottom: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
        `;
        header.innerHTML = `<span style="font-weight:700;font-size:15px;">\uD83D\uDCE5 Import Brushes</span>`;
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '\u2715';
        closeBtn.style.cssText = `background:none;border:none;font-size:18px;cursor:pointer;color:${dark ? '#a6adc8' : '#64748b'};padding:4px 8px;border-radius:4px;`;
        closeBtn.onmouseenter = () => closeBtn.style.background = dark ? '#45475a' : '#e2e8f0';
        closeBtn.onmouseleave = () => closeBtn.style.background = 'none';
        closeBtn.onclick = () => overlay.remove();
        header.appendChild(closeBtn);
        modal.appendChild(header);

        const body = document.createElement('div');
        body.style.cssText = 'padding: 16px 20px; display: flex; flex-direction: column; gap: 12px;';

        const hint = document.createElement('div');
        hint.style.cssText = `font-size:13px;color:${dark ? '#a6adc8' : '#64748b'};`;
        hint.textContent = 'Paste brush JSON or upload a file. Select which brushes to add — they will be appended to your existing list.';
        body.appendChild(hint);

        const statusEl = document.createElement('div');
        statusEl.style.cssText = `font-size:12px;font-weight:600;min-height:16px;color:${dark ? '#a6e3a1' : '#16a34a'};`;
        body.appendChild(statusEl);

        // ── JSON input ──
        const textarea = document.createElement('textarea');
        textarea.placeholder = 'Paste JSON here\u2026';
        textarea.style.cssText = `
            width: 100%; min-height: 120px; max-height: 200px; box-sizing: border-box;
            padding: 10px; font-family: monospace; font-size: 12px;
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            border-radius: 8px; resize: vertical; outline: none;
            background: ${dark ? '#181825' : '#f8fafc'};
            color: ${dark ? '#cdd6f4' : '#1e293b'};
        `;
        body.appendChild(textarea);

        // ── Checklist (shown after parse) ──
        const listWrap = document.createElement('div');
        listWrap.style.cssText = `
            display: none; flex-direction: column; gap: 2px;
            max-height: 160px; overflow-y: auto;
            border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            border-radius: 8px; padding: 6px 8px;
            background: ${dark ? '#181825' : '#f8fafc'};
        `;

        const selRow = document.createElement('div');
        selRow.style.cssText = 'display:flex;gap:10px;margin-bottom:4px;';
        const selAll = document.createElement('button');
        selAll.textContent = 'Select All';
        selAll.style.cssText = `background:none;border:none;cursor:pointer;font-size:11px;font-weight:600;padding:0;color:${dark ? '#89b4fa' : '#3b82f6'};`;
        const selNone = document.createElement('button');
        selNone.textContent = 'Select None';
        selNone.style.cssText = selAll.style.cssText;
        selRow.appendChild(selAll);
        selRow.appendChild(selNone);
        listWrap.appendChild(selRow);

        let parsedBrushes = [];
        let checkboxes = [];

        function buildChecklist(brushes) {
            // Clear old rows (keep selRow)
            while (listWrap.children.length > 1) listWrap.removeChild(listWrap.lastChild);
            checkboxes = [];
            brushes.forEach((brush) => {
                const row = document.createElement('label');
                row.style.cssText = `display:flex;align-items:center;gap:8px;padding:3px 2px;cursor:pointer;border-radius:4px;font-size:13px;`;
                row.onmouseenter = () => row.style.background = dark ? '#313244' : '#f1f5f9';
                row.onmouseleave = () => row.style.background = 'none';

                const cb = document.createElement('input');
                cb.type = 'checkbox';
                cb.checked = true;
                cb.style.cssText = 'flex-shrink:0;cursor:pointer;accent-color:#3b82f6;';

                const nameSpan = document.createElement('span');
                nameSpan.textContent = (typeof brush.name === 'string' && brush.name.trim()) ? brush.name.trim() : 'Unnamed brush';
                nameSpan.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';

                row.appendChild(cb);
                row.appendChild(nameSpan);
                listWrap.appendChild(row);
                checkboxes.push({ cb, brush });
            });
            selAll.onclick  = () => { checkboxes.forEach(x => { x.cb.checked = true;  }); };
            selNone.onclick = () => { checkboxes.forEach(x => { x.cb.checked = false; }); };
            listWrap.style.display = 'flex';
        }

        function parseAndPreview(text) {
            let parsed;
            try { parsed = JSON.parse(text.trim()); } catch (e) {
                statusEl.style.color = dark ? '#f38ba8' : '#dc2626';
                statusEl.textContent = '\u274C Invalid JSON: ' + e.message;
                listWrap.style.display = 'none';
                return;
            }
            if (!Array.isArray(parsed)) {
                statusEl.style.color = dark ? '#f38ba8' : '#dc2626';
                statusEl.textContent = '\u274C Expected a JSON array of brush objects.';
                listWrap.style.display = 'none';
                return;
            }
            parsedBrushes = parsed.filter(b => Array.isArray(b.pattern) && typeof b.brushSize === 'number');
            const skipped = parsed.length - parsedBrushes.length;
            if (parsedBrushes.length === 0) {
                statusEl.style.color = dark ? '#f38ba8' : '#dc2626';
                statusEl.textContent = '\u274C No valid brush objects found.';
                listWrap.style.display = 'none';
                return;
            }
            statusEl.style.color = dark ? '#a6adc8' : '#64748b';
            statusEl.textContent = `Found ${parsedBrushes.length} brush${parsedBrushes.length !== 1 ? 'es' : ''}${skipped ? ` (${skipped} invalid skipped)` : ''} \u2014 select which to add:`;
            buildChecklist(parsedBrushes);
        }

        textarea.addEventListener('input', () => {
            if (textarea.value.trim()) parseAndPreview(textarea.value);
            else { listWrap.style.display = 'none'; statusEl.textContent = ''; }
        });

        body.appendChild(listWrap);

        // ── Buttons ──
        const btnRow = document.createElement('div');
        btnRow.style.cssText = 'display:flex;gap:8px;justify-content:space-between;align-items:center;';

        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json,application/json';
        fileInput.style.display = 'none';
        fileInput.addEventListener('change', () => {
            const file = fileInput.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = (e) => { textarea.value = e.target.result; parseAndPreview(e.target.result); };
            reader.readAsText(file);
            fileInput.value = '';
        });
        body.appendChild(fileInput);

        const uploadBtn = document.createElement('button');
        uploadBtn.style.cssText = `
            padding: 7px 14px; border-radius: 8px; border: 1px solid ${dark ? '#45475a' : '#e2e8f0'};
            cursor: pointer; font-size: 13px; font-weight: 600;
            background: ${dark ? '#313244' : '#f1f5f9'}; color: ${dark ? '#cdd6f4' : '#1e293b'};
            transition: background 0.15s;
        `;
        uploadBtn.textContent = '\uD83D\uDCC1 Upload File';
        uploadBtn.onmouseenter = () => uploadBtn.style.background = dark ? '#45475a' : '#e2e8f0';
        uploadBtn.onmouseleave = () => uploadBtn.style.background = dark ? '#313244' : '#f1f5f9';
        uploadBtn.onclick = () => fileInput.click();

        const importBtn = document.createElement('button');
        importBtn.style.cssText = `
            padding: 7px 16px; border-radius: 8px; border: none; cursor: pointer; font-size: 13px; font-weight: 600;
            background: ${dark ? '#89b4fa' : '#3b82f6'}; color: ${dark ? '#1e1e2e' : '#ffffff'};
            transition: background 0.15s;
        `;
        importBtn.textContent = '\u2705 Import Selected';
        importBtn.onmouseenter = () => importBtn.style.background = dark ? '#74c7ec' : '#2563eb';
        importBtn.onmouseleave = () => importBtn.style.background = dark ? '#89b4fa' : '#3b82f6';
        importBtn.onclick = () => {
            const selected = checkboxes.filter(x => x.cb.checked).map(x => x.brush);
            if (selected.length === 0) {
                statusEl.style.color = dark ? '#f38ba8' : '#dc2626';
                statusEl.textContent = '\u274C No brushes selected.';
                return;
            }
            selected.forEach(b => {
                scriptState.brushes.push({
                    id: scriptState.nextId++,
                    name: (typeof b.name === 'string' && b.name.trim()) ? b.name.trim() : `Brush ${scriptState.nextId - 1}`,
                    pattern: b.pattern,
                    brushSize: b.brushSize
                });
            });
            saveBrushes();
            statusEl.style.color = dark ? '#a6e3a1' : '#16a34a';
            statusEl.textContent = `\u2705 Added ${selected.length} brush${selected.length !== 1 ? 'es' : ''}. Total: ${scriptState.brushes.length}.`;
            textarea.value = '';
            listWrap.style.display = 'none';
            parsedBrushes = [];
            checkboxes = [];
            if (scriptState.dropdownOpen) renderDropdown();
        };

        btnRow.appendChild(uploadBtn);
        btnRow.appendChild(importBtn);
        body.appendChild(btnRow);
        modal.appendChild(body);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);
        setTimeout(() => textarea.focus(), 50);
    }

    function renderDropdown() {
        let dropdown = document.getElementById('brush-swap-dropdown');
        if (!dropdown) return;

        // Clear existing items
        const itemsContainer = dropdown.querySelector('.brush-swap-items');
        itemsContainer.innerHTML = '';

        if (scriptState.brushes.length === 0) {
            const emptyMsg = document.createElement('div');
            emptyMsg.className = 'text-center text-gray-500 dark:text-gray-400 text-xs py-3 px-2';
            emptyMsg.textContent = 'No saved brushes';
            itemsContainer.appendChild(emptyMsg);
            return;
        }

        scriptState.brushes.forEach((brush, idx) => {
            const item = document.createElement('div');
            const isActive = brush.id === scriptState.activeBrushId;
            item.className = 'flex items-center gap-2 p-1.5 border border-gray-200 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'
                + (isActive ? ' brush-swap-item-active' : '');
            item.dataset.brushId = brush.id;
            item.dataset.brushIdx = idx;

            // Preview grid (wrapped with click-to-expand)
            const previewWrap = document.createElement('div');
            previewWrap.className = 'brush-swap-preview-wrap';
            const preview = createBrushPreview(brush);
            previewWrap.appendChild(preview);
            previewWrap.addEventListener('click', (e) => {
                e.stopPropagation();
                previewWrap.classList.toggle('expanded');
            });
            // Fast tooltip that follows mouse
            let prevTip = null;
            previewWrap.addEventListener('mouseenter', (e) => {
                prevTip = document.createElement('div');
                prevTip.className = 'brush-swap-quick-tip';
                prevTip.textContent = 'click to expand';
                prevTip.style.left = (e.clientX + 12) + 'px';
                prevTip.style.top = (e.clientY - 8) + 'px';
                document.body.appendChild(prevTip);
            });
            previewWrap.addEventListener('mousemove', (e) => {
                if (prevTip) {
                    prevTip.style.left = (e.clientX + 12) + 'px';
                    prevTip.style.top = (e.clientY - 8) + 'px';
                }
            });
            previewWrap.addEventListener('mouseleave', () => {
                if (prevTip) { prevTip.remove(); prevTip = null; }
            });
            item.appendChild(previewWrap);

            // Name and controls
            const infoContainer = document.createElement('div');
            infoContainer.className = 'flex-1 flex flex-col gap-1';

            // Name display / edit
            const nameContainer = document.createElement('div');
            nameContainer.className = 'flex items-center gap-1 flex-1';

            if (scriptState.isRenaming === brush.id) {
                // Rename input mode
                const input = document.createElement('input');
                input.type = 'text';
                input.className = 'flex-1 px-1 py-0.5 text-xs border border-gray-500 dark:border-gray-400 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100';
                input.value = brush.name;
                input.maxLength = 30;

                input.addEventListener('blur', () => {
                    renameBrush(brush.id, input.value);
                    scriptState.isRenaming = null;
                });

                input.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter') {
                        renameBrush(brush.id, input.value);
                        scriptState.isRenaming = null;
                    }
                });

                nameContainer.appendChild(input);
                setTimeout(() => input.focus(), 0);
            } else {
                // Normal name display with pencil icon
                const nameSpan = document.createElement('span');
                nameSpan.className = 'flex-1 text-xs font-medium text-gray-900 dark:text-gray-100 whitespace-nowrap overflow-hidden text-ellipsis';
                nameSpan.textContent = brush.name;
                nameContainer.appendChild(nameSpan);

                const pencilBtn = document.createElement('button');
                pencilBtn.className = 'bg-none border-none cursor-pointer p-0 text-xs opacity-60 hover:opacity-100 transition-opacity flex-shrink-0';
                pencilBtn.title = 'Rename brush';
                pencilBtn.innerHTML = '✏️';
                pencilBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    scriptState.isRenaming = brush.id;
                    renderDropdown();
                });
                nameContainer.appendChild(pencilBtn);
            }

            infoContainer.appendChild(nameContainer);

            // Load and Delete buttons
            const buttonsContainer = document.createElement('div');
            buttonsContainer.className = 'flex gap-1 flex-shrink-0';

            const loadBtn = document.createElement('button');
            loadBtn.className = 'px-1.5 py-0.5 text-xs border border-gray-300 dark:border-gray-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 cursor-pointer rounded transition-colors hover:bg-blue-50 dark:hover:bg-blue-900 hover:border-blue-400 dark:hover:border-blue-400';
            loadBtn.textContent = 'Load';
            loadBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                loadBrush(brush.id);
            });

            const deleteBtn = document.createElement('button');
            deleteBtn.className = 'px-1 py-0.5 text-xs bg-none border border-gray-300 dark:border-gray-500 opacity-60 hover:opacity-100 transition-opacity cursor-pointer rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 hover:bg-red-50 dark:hover:bg-red-900 hover:border-red-400 dark:hover:border-red-400';
            deleteBtn.title = 'Delete brush';
            deleteBtn.innerHTML = '✕';
            deleteBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                deleteBrush(brush.id);
            });

            buttonsContainer.appendChild(loadBtn);
            buttonsContainer.appendChild(deleteBtn);
            infoContainer.appendChild(buttonsContainer);

            item.appendChild(infoContainer);

            // Drag handle (right side)
            const dragHandle = document.createElement('div');
            dragHandle.className = 'brush-swap-drag-handle';
            dragHandle.title = 'Drag to reorder';
            for (let d = 0; d < 4; d++) {
                const dot = document.createElement('div');
                dot.className = 'brush-swap-drag-handle-dot';
                dragHandle.appendChild(dot);
            }
            setupDragReorder(dragHandle, item, idx, itemsContainer);
            item.appendChild(dragHandle);

            itemsContainer.appendChild(item);
        });
    }

    function toggleDropdown() {
        const dropdown = document.getElementById('brush-swap-dropdown');
        if (!dropdown) return;

        scriptState.dropdownOpen = !scriptState.dropdownOpen;

        if (scriptState.dropdownOpen) {
            // Detect if the paint menu is docked to top
            const paintIsTop = localStorage.getItem('gpc-paint-is-top') === 'true';
            const resizeHandle = dropdown.querySelector('.brush-swap-resize-handle');
            if (paintIsTop) {
                dropdown.style.bottom = 'auto';
                dropdown.style.top = '100%';
                dropdown.style.marginBottom = '0';
                dropdown.style.marginTop = '8px';
                if (resizeHandle) resizeHandle.className = 'brush-swap-resize-handle brush-swap-resize-br';
            } else {
                dropdown.style.top = 'auto';
                dropdown.style.bottom = '100%';
                dropdown.style.marginTop = '0';
                dropdown.style.marginBottom = '8px';
                if (resizeHandle) resizeHandle.className = 'brush-swap-resize-handle brush-swap-resize-tr';
            }
            // Apply stored resize dimensions if any
            try {
                const stored = JSON.parse(localStorage.getItem(RESIZE_STORAGE_KEY));
                if (stored) {
                    dropdown.style.maxWidth = stored.w + 'px';
                    dropdown.style.maxHeight = stored.h + 'px';
                }
            } catch {}
            dropdown.classList.add('open');
            renderDropdown();
        } else {
            dropdown.classList.remove('open');
            scriptState.isRenaming = null;
        }
    }

    // ============================================
    // DOM INITIALIZATION
    // ============================================

    function injectCSS() {
        const style = document.createElement('style');
        style.textContent = `
            /* Paintbrush icon button */
            #brush-swap-toggle {
                opacity: 0.85;
                transition: all 0.2s ease;
            }

            #brush-swap-toggle:hover {
                opacity: 1;
            }

            #brush-swap-toggle:active {
                opacity: 0.7;
            }

            /* Dropdown container */
            #brush-swap-dropdown {
                position: absolute;
                bottom: 100%;
                right: 0;
                border-radius: 4px;
                margin-bottom: 8px;
                max-width: 300px;
                max-height: 0;
                overflow: hidden;
                opacity: 0;
                pointer-events: none;
                transition: max-height 0.3s ease, opacity 0.3s ease;
                z-index: 1000;
            }

            #brush-swap-dropdown.open {
                max-height: 600px;
                opacity: 1;
                overflow-y: auto;
                pointer-events: auto;
            }

            /* Disable all transitions during resize drag to prevent animation lag */
            #brush-swap-dropdown.brush-swap-resizing {
                transition: none !important;
                overflow-y: auto !important;
            }

            /* Items container */
            .brush-swap-items {
                display: flex;
                flex-direction: column;
                gap: 4px;
                padding: 8px;
                min-width: 250px;
            }

            /* Preview grid */
            .brush-swap-preview-grid {
                display: grid;
                gap: 1px;
                flex-shrink: 0;
                background: var(--color-gray-100, white);
                padding: 2px;
                border: 1px solid var(--color-gray-400, #ddd);
                border-radius: 2px;
            }

            .brush-swap-quick-tip {
                position: fixed;
                pointer-events: none;
                z-index: 100001;
                background: rgba(0,0,0,0.8);
                color: #fff;
                padding: 3px 7px;
                border-radius: 4px;
                font-size: 10px;
                font-weight: 500;
                white-space: nowrap;
                font-family: system-ui, sans-serif;
            }

            .brush-swap-preview-wrap {
                width: 36px;
                height: 36px;
                overflow: hidden;
                flex-shrink: 0;
                border-radius: 3px;
                cursor: pointer;
                position: relative;
                display: flex;
                align-items: center;
                justify-content: center;
            }

            .brush-swap-preview-wrap.expanded {
                width: auto;
                height: auto;
                max-width: 120px;
                max-height: 120px;
            }

            .brush-swap-preview-wrap.expanded .brush-swap-preview-grid {
                max-width: 100%;
                max-height: 100%;
            }

            .brush-swap-preview-cell {
                background: var(--color-gray-100, white);
                border: 0.5px solid var(--color-gray-300, #eee);
            }

            .brush-swap-preview-cell.active {
                background: var(--color-gray-800, #333);
            }

            .brush-swap-preview-cell.center {
                background: #ff6b6b;
            }

            /* Scrollbar styling for dropdown */
            #brush-swap-dropdown::-webkit-scrollbar {
                width: 6px;
            }

            #brush-swap-dropdown::-webkit-scrollbar-track {
                background: transparent;
            }

            #brush-swap-dropdown::-webkit-scrollbar-thumb {
                background: #888;
                border-radius: 3px;
            }

            #brush-swap-dropdown::-webkit-scrollbar-thumb:hover {
                background: #555;
            }

            /* Dark mode scrollbar */
            @media (prefers-color-scheme: dark) {
                #brush-swap-dropdown::-webkit-scrollbar-thumb {
                    background: #555;
                }

                #brush-swap-dropdown::-webkit-scrollbar-thumb:hover {
                    background: #777;
                }
            }

            /* Scroll-swap toast */
            #brush-swap-toast {
                position: fixed;
                pointer-events: none;
                z-index: 10000;
                background: rgba(0, 0, 0, 0.82);
                color: #fff;
                padding: 6px 10px;
                border-radius: 6px;
                font-size: 11px;
                font-weight: 600;
                font-family: system-ui, sans-serif;
                white-space: nowrap;
                opacity: 0;
                transition: opacity 0.15s ease;
                transform: translate(12px, -50%);
                display: flex;
                align-items: center;
                gap: 8px;
            }
            #brush-swap-toast.visible {
                opacity: 1;
            }
            #brush-swap-toast .toast-preview {
                flex-shrink: 0;
            }
            #brush-swap-toast .toast-preview .brush-swap-preview-cell {
                background: rgba(255,255,255,0.2);
                border-color: rgba(255,255,255,0.1);
            }
            #brush-swap-toast .toast-preview .brush-swap-preview-cell.active {
                background: #fff;
            }
            #brush-swap-toast .toast-preview .brush-swap-preview-cell.center {
                background: #ff6b6b;
            }
            #brush-swap-toast .toast-preview .brush-swap-preview-grid {
                background: transparent;
                border-color: rgba(255,255,255,0.15);
            }

            /* Active brush highlight */
            .brush-swap-item-active {
                outline: 2px solid var(--color-blue-500, #3b82f6) !important;
                outline-offset: -1px;
                background: var(--color-blue-50, rgba(59,130,246,0.08)) !important;
            }

            /* Drag handle */
            .brush-swap-drag-handle {
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
                width: 14px;
                cursor: grab;
                flex-shrink: 0;
                opacity: 0.35;
                transition: opacity 0.15s;
                user-select: none;
                padding: 2px 0;
            }
            .brush-swap-drag-handle:hover {
                opacity: 0.8;
            }
            .brush-swap-drag-handle:active {
                cursor: grabbing;
                opacity: 1;
            }
            .brush-swap-drag-handle-dot {
                width: 3px;
                height: 3px;
                border-radius: 50%;
                background: var(--color-gray-500, #9ca3af);
                margin: 1px 0;
            }

            /* Dragging visual */
            .brush-swap-item-dragging {
                opacity: 0.4;
            }
            .brush-swap-drop-indicator {
                height: 2px;
                background: var(--color-blue-500, #3b82f6);
                border-radius: 1px;
                margin: -2px 0;
                pointer-events: none;
            }

            /* Resize handle — single corner, right side */
            .brush-swap-resize-handle {
                position: absolute;
                right: 0;
                width: 14px;
                height: 14px;
                z-index: 1001;
                opacity: 0;
                transition: opacity 0.15s;
            }
            #brush-swap-dropdown:hover .brush-swap-resize-handle {
                opacity: 0.5;
            }
            .brush-swap-resize-handle:hover {
                opacity: 1 !important;
            }
            .brush-swap-resize-handle::after {
                content: '';
                position: absolute;
                width: 7px;
                height: 7px;
                border-style: solid;
                border-color: var(--color-gray-500, #6b7280);
            }
            /* top-right: open upward */
            .brush-swap-resize-tr {
                top: 0;
                cursor: ne-resize;
            }
            .brush-swap-resize-tr::after {
                top: 2px; right: 2px;
                border-width: 2px 2px 0 0;
            }
            /* bottom-right: open downward (paint menu at top) */
            .brush-swap-resize-br {
                bottom: 0;
                cursor: se-resize;
            }
            .brush-swap-resize-br::after {
                bottom: 2px; right: 2px;
                border-width: 0 2px 2px 0;
            }

            /* Export/Import footer */
            .brush-swap-footer {
                display: flex;
                justify-content: flex-end;
                gap: 4px;
                padding: 6px 8px;
                border-top: 1px solid var(--color-gray-200, #e5e7eb);
            }
            :is(.dark) .brush-swap-footer,
            .dark .brush-swap-footer {
                border-top-color: #374151;
            }
            .brush-swap-footer-btn {
                background: none;
                border: 1px solid var(--color-gray-300, #d1d5db);
                border-radius: 5px;
                cursor: pointer;
                font-size: 11px;
                line-height: 1;
                padding: 3px 8px;
                color: var(--color-gray-500, #6b7280);
                transition: background 0.12s, color 0.12s;
                white-space: nowrap;
                display: inline-flex;
                align-items: center;
                gap: 4px;
            }
            .brush-swap-footer-btn:hover {
                background: var(--color-gray-100, #f3f4f6);
                color: var(--color-gray-800, #1f2937);
                border-color: var(--color-gray-400, #9ca3af);
            }
            :is(.dark) .brush-swap-footer-btn,
            .dark .brush-swap-footer-btn {
                border-color: #4b5563;
                color: #9ca3af;
            }
            :is(.dark) .brush-swap-footer-btn:hover,
            .dark .brush-swap-footer-btn:hover {
                background: #374151;
                color: #e5e7eb;
                border-color: #6b7280;
            }
        `;
        document.head.appendChild(style);
    }

    function createUI(bottomControlsElement) {
        // Find commitBtn to position next to it
        const commitBtn = bottomControlsElement.querySelector('#commitBtn') ||
                         bottomControlsElement.querySelector('button');

        if (!commitBtn) {
            console.warn('Brush Swap: Could not find commitBtn');
            return;
        }

        // Create wrapper for button and dropdown
        const wrapper = document.createElement('div');
        wrapper.className = 'relative inline-block';

        // Create toggle button (paintbrush icon)
        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'brush-swap-toggle';
        toggleBtn.className = 'bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 cursor-pointer px-2.5 py-1.5 text-xs leading-none font-semibold text-gray-800 dark:text-gray-200 ml-2 inline-flex items-center justify-center rounded hover:bg-gray-200 dark:hover:bg-gray-600 hover:border-gray-600 dark:hover:border-gray-500 active:bg-gray-300 dark:active:bg-gray-800';
        toggleBtn.title = 'Toggle saved brushes';
        toggleBtn.innerHTML = '<span style="font-size: 10px; font-weight: 600; display: flex; align-items: center; gap: 4px;">▲ brushes</span>';
        toggleBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            toggleDropdown();
        });

        // ── Scroll-to-swap: mouse wheel over toggle button cycles brushes ──
        const toast = document.createElement('div');
        toast.id = 'brush-swap-toast';
        document.body.appendChild(toast);
        let toastTimer = null;

        function showSwapToast(brush, x, y) {
            toast.innerHTML = '';
            // Add preview
            const previewWrap = document.createElement('span');
            previewWrap.className = 'toast-preview';
            previewWrap.appendChild(createBrushPreview(brush));
            toast.appendChild(previewWrap);
            // Add name
            const nameSpan = document.createElement('span');
            nameSpan.textContent = brush.name;
            toast.appendChild(nameSpan);

            toast.style.left = x + 'px';
            toast.style.top = y + 'px';
            toast.classList.add('visible');
            clearTimeout(toastTimer);
            toastTimer = setTimeout(() => toast.classList.remove('visible'), 900);
        }

        toggleBtn.addEventListener('wheel', (e) => {
            if (scriptState.brushes.length === 0) return;
            e.preventDefault();
            e.stopPropagation();

            const dir = e.deltaY > 0 ? 1 : -1;
            const len = scriptState.brushes.length;

            // Initialize index to current brush if not set
            if (scriptState.scrollIndex < 0 || scriptState.scrollIndex >= len) {
                scriptState.scrollIndex = dir > 0 ? 0 : len - 1;
            } else {
                scriptState.scrollIndex = ((scriptState.scrollIndex + dir) % len + len) % len;
            }

            const brush = scriptState.brushes[scriptState.scrollIndex];
            applyBrushToEditor(brush);
            showSwapToast(brush, e.clientX, e.clientY);

            if (DEBUG) console.log(`Brush Swap: Scrolled to "${brush.name}" (index ${scriptState.scrollIndex})`);
        }, { passive: false });

        // Create dropdown container
        const dropdown = document.createElement('div');
        dropdown.id = 'brush-swap-dropdown';
        dropdown.className = 'bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 shadow-lg';
        dropdown.style.position = 'absolute'; // ensure positioned for resize handles

        // Single resize handle — class swapped in toggleDropdown based on paint menu position
        const resizeHandle = document.createElement('div');
        resizeHandle.className = 'brush-swap-resize-handle brush-swap-resize-tr'; // default: top-right
        dropdown.appendChild(resizeHandle);

        // Stored dimensions (persisted so they survive open/close)
        let storedSize = null;
        try { storedSize = JSON.parse(localStorage.getItem(RESIZE_STORAGE_KEY)); } catch {}

        function applyStoredSize() {
            if (storedSize) {
                dropdown.style.width = storedSize.w + 'px';
                dropdown.style.maxWidth = storedSize.w + 'px';
                dropdown.style.maxHeight = storedSize.h + 'px';
            }
        }

        function setupResize(handle) {
            handle.addEventListener('mousedown', (e) => {
                e.preventDefault();
                e.stopPropagation();
                const startX = e.clientX;
                const startY = e.clientY;
                const rect = dropdown.getBoundingClientRect();
                const startW = rect.width;
                const startH = rect.height;
                const isBottom = handle.classList.contains('brush-swap-resize-br');
                dropdown.style.userSelect = 'none';
                dropdown.classList.add('brush-swap-resizing');

                const onMove = (ev) => {
                    const dx = ev.clientX - startX;
                    const dy = ev.clientY - startY;
                    // Top-right: drag up = taller. Bottom-right: drag down = taller.
                    const newH = Math.max(150, isBottom ? startH + dy : startH - dy);
                    const newW = Math.max(200, startW + dx);
                    dropdown.style.width = newW + 'px';
                    dropdown.style.maxWidth = newW + 'px';
                    dropdown.style.maxHeight = newH + 'px';
                };

                const onUp = () => {
                    dropdown.style.userSelect = '';
                    dropdown.classList.remove('brush-swap-resizing');
                    document.removeEventListener('mousemove', onMove, true);
                    document.removeEventListener('mouseup', onUp, true);
                    const r = dropdown.getBoundingClientRect();
                    storedSize = { w: Math.round(r.width), h: Math.round(r.height) };
                    localStorage.setItem(RESIZE_STORAGE_KEY, JSON.stringify(storedSize));
                };

                document.addEventListener('mousemove', onMove, true);
                document.addEventListener('mouseup', onUp, true);
            });
        }

        setupResize(resizeHandle);

        const itemsContainer = document.createElement('div');
        itemsContainer.className = 'brush-swap-items';
        dropdown.appendChild(itemsContainer);

        // Footer: export/import buttons
        const dropdownFooter = document.createElement('div');
        dropdownFooter.className = 'brush-swap-footer';

        const exportBtn = document.createElement('button');
        exportBtn.className = 'brush-swap-footer-btn';
        exportBtn.innerHTML = '\uD83D\uDCE4 Export';
        exportBtn.addEventListener('click', (e) => { e.stopPropagation(); showExportBrushesModal(); });

        const importBtn = document.createElement('button');
        importBtn.className = 'brush-swap-footer-btn';
        importBtn.innerHTML = '\uD83D\uDCE5 Import';
        importBtn.addEventListener('click', (e) => { e.stopPropagation(); showImportBrushesModal(); });

        const resetSizeBtn = document.createElement('button');
        resetSizeBtn.className = 'brush-swap-footer-btn';
        resetSizeBtn.innerHTML = '\u21BA';
        resetSizeBtn.title = 'Reset size';
        resetSizeBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            storedSize = null;
            localStorage.removeItem(RESIZE_STORAGE_KEY);
            dropdown.style.width = '';
            dropdown.style.maxWidth = '';
            dropdown.style.maxHeight = '';
        });

        dropdownFooter.appendChild(exportBtn);
        dropdownFooter.appendChild(importBtn);
        dropdownFooter.appendChild(resetSizeBtn);
        dropdown.appendChild(dropdownFooter);

        // Assemble and insert
        wrapper.appendChild(toggleBtn);
        wrapper.appendChild(dropdown);

        // Insert after commitBtn
        commitBtn.parentNode.insertBefore(wrapper, commitBtn.nextSibling);

        // Close dropdown on click outside
        document.addEventListener('click', (e) => {
            if (!wrapper.contains(e.target) && scriptState.dropdownOpen) {
                toggleDropdown();
            }
        });
    }

    function hookToggleBrushEditor() {
        const originalToggle = _pw.toggleBrushEditor;

        if (typeof originalToggle === 'function') {
            _pw.toggleBrushEditor = function() {
                // Call original toggle
                originalToggle.call(this);

                // Add dimension dropdown after modal opens; also install drag/fill enhancements
                setTimeout(() => {
                    addBrushDimensionDropdown();
                    _installBrushEditorEnhancements();
                }, 50);

                // Opera GX / Blink failsafe: after the close animation (200ms), ensure the
                // overlay has pointer-events:none so it can't silently swallow map clicks.
                setTimeout(() => {
                    const overlay = document.getElementById('brushEditorMenu');
                    if (overlay && overlay.classList.contains('hidden')) {
                        overlay.style.pointerEvents = 'none';
                        overlay.style.visibility = 'hidden';
                    }
                }, 350);
                // When opening, restore pointer-events so the overlay is interactive
                setTimeout(() => {
                    const overlay = document.getElementById('brushEditorMenu');
                    if (overlay && !overlay.classList.contains('hidden')) {
                        overlay.style.pointerEvents = '';
                        overlay.style.visibility = '';
                    }
                }, 10);
            };
            if (DEBUG) console.log('Brush Swap: Hooked toggleBrushEditor');
        }
    }

    // ============================================

    function hookSaveBrushToPreset() {
        if (typeof _pw.saveBrushToPreset !== 'function') {
            console.warn('Brush Swap: saveBrushToPreset not yet available, retrying...');
            return false;
        }

        const originalSave = _pw.saveBrushToPreset;

        _pw.saveBrushToPreset = function(slotIndex) {
            // Call original function
            originalSave.call(this, slotIndex);

            // After save, capture brush from DOM grid
            const brushData = captureBrushFromDOM();
            if (brushData) {
                const newBrush = addBrush(brushData.pattern, brushData.brushSize);
                if (DEBUG) console.log('Brush Swap: Saved brush', newBrush);
                renderDropdown();
            } else {
                console.warn('Brush Swap: Failed to capture brush from DOM');
            }
        };

        if (DEBUG) console.log('Brush Swap: Successfully hooked saveBrushToPreset');
        return true;
    }

    // ============================================
    // INITIALIZATION
    // ============================================

    function init() {
        // Load saved brushes from localStorage
        loadBrushes();

        // Inject CSS
        injectCSS();

        // Wait for bottomControls and saveBrushToPreset to be ready
        let attempts = 0;
        const maxAttempts = 120; // 60 seconds at 500ms intervals

        const initInterval = setInterval(() => {
            attempts++;

            const bottomControls = document.getElementById('bottomControls');
            const hasSaveBrushToPreset = typeof _pw.saveBrushToPreset === 'function';

            if (bottomControls && hasSaveBrushToPreset && attempts <= maxAttempts) {
                clearInterval(initInterval);

                // Set default brush size to 9x9 — must wait for async config fetch
                // which overwrites BrushSize from userConfig.brushSize
                const DEFAULT_BRUSH_SIZE = 9;
                function applyDefaultBrushSize() {
                    _setPageVar('BrushSize', DEFAULT_BRUSH_SIZE);
                    _pw.BrushSize = DEFAULT_BRUSH_SIZE;
                    _runInPage('if(typeof generateBrushGrid==="function")generateBrushGrid(currentBrushPattern)');
                    // Update the dimension dropdown if it exists
                    const sel = document.getElementById('brush-swap-dimension-select');
                    if (sel) sel.value = DEFAULT_BRUSH_SIZE;
                }
                // Apply immediately, then re-apply after config fetch likely completes
                applyDefaultBrushSize();
                let configChecks = 0;
                const configWait = setInterval(() => {
                    configChecks++;
                    // userConfig gets written to localStorage when server fetch completes
                    const saved = localStorage.getItem('userConfig');
                    if (saved || configChecks > 20) {
                        clearInterval(configWait);
                        applyDefaultBrushSize();
                    }
                }, 250);

                // Create UI
                createUI(bottomControls);

                // Hook into saveBrushToPreset (now guaranteed to exist)
                hookSaveBrushToPreset();

                // Hook into toggleBrushEditor for dimension dropdown
                hookToggleBrushEditor();

                if (DEBUG) console.log('Brush Swap initialized successfully');
            } else if (attempts > maxAttempts) {
                clearInterval(initInterval);
                console.warn('Brush Swap: Could not initialize - bottomControls or saveBrushToPreset not found', {
                    hasBottomControls: !!bottomControls,
                    hasSaveBrushToPreset: hasSaveBrushToPreset
                });
            }
        }, 500);
    }

    // Start when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
            })();
            _featureStatus.paintBrushSwap = 'ok';
            console.log('[GeoPixelcons++] ✅ Paint Brush Swap loaded');
        } catch (err) {
            _featureStatus.paintBrushSwap = 'error';
            dbgPush(`Paint Brush Swap init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Paint Brush Swap' });
            console.error('[GeoPixelcons++] ❌ Paint Brush Swap failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Regions Highscore [regionsHighscore]
    // ============================================================
    if (_settings.regionsHighscore) {
        try {
            (function _init_regionsHighscore() {

    // ==================== CONFIGURATION ====================
    const GRID_SIZE = 25;
    const TILE_SIZE = 1000;
    const USERNAME_BATCH_SIZE = 10;
    const SELECTION_COLOR = 'rgba(59, 130, 246, 0.3)';
    const SELECTION_BORDER_COLOR = 'rgba(59, 130, 246, 0.8)';

    // ==================== STATE ====================
    let isSelectionModeActive = false;
    let isDragging = false;
    let selectionStart = null;
    let selectionEnd = null;
    let selectionCanvas = null;
    let selectionCtx = null;
    let highscoreButton = null;
    let _map = null; // resolved MapLibre map object (not the DOM element)

    // ==================== MAP ACCESS ====================
    function _getMap() {
        if (_map) return _map;
        try { const m = (0, eval)('map'); if (m && typeof m.scrollZoom !== 'undefined') return (_map = m); } catch {}
        if (typeof unsafeWindow !== 'undefined') { try { const m = unsafeWindow.eval('map'); if (m && typeof m.scrollZoom !== 'undefined') return (_map = m); } catch {} }
        return null;
    }

    // ==================== INITIALIZATION ====================
    function waitForGeoPixels() {
        return new Promise((resolve) => {
            const check = () => {
                if (
                    _getMap() &&
                    typeof turf !== 'undefined' &&
                    typeof tileImageCache !== 'undefined' &&
                    document.getElementById('controls-left')
                ) {
                    resolve();
                } else {
                    setTimeout(check, 500);
                }
            };
            check();
        });
    }

    async function init() {
        await waitForGeoPixels();
        console.log('[Regions Highscore] Initializing...');

        createSelectionCanvas();
        createHighscoreButton();
        setupEventListeners();

        console.log('[Regions Highscore] Ready!');
    }

    // ==================== UI COMPONENTS ====================
    function createHighscoreButton() {
        highscoreButton = document.createElement('button');
        highscoreButton.id = 'gpc-highscore-trigger';
        highscoreButton.style.display = 'none';
        highscoreButton.addEventListener('click', toggleSelectionMode);
        document.body.appendChild(highscoreButton);
    }

    function createSelectionCanvas() {
        selectionCanvas = document.createElement('canvas');
        selectionCanvas.id = 'highscore-selection-canvas';
        selectionCanvas.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            pointer-events: none;
            z-index: 1000;
        `;
        document.body.appendChild(selectionCanvas);

        selectionCtx = selectionCanvas.getContext('2d');

        // Sync canvas size with viewport
        const syncCanvasSize = () => {
            const dpr = window.devicePixelRatio || 1;
            selectionCanvas.width = window.innerWidth * dpr;
            selectionCanvas.height = window.innerHeight * dpr;
            selectionCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
        };

        syncCanvasSize();
        window.addEventListener('resize', syncCanvasSize);

        // Redraw on map events
        ['move', 'rotate', 'zoom'].forEach((ev) => {
            _map.on(ev, () => {
                if (isDragging) drawSelectionPreview();
            });
        });
    }

    function toggleSelectionMode() {
        isSelectionModeActive = !isSelectionModeActive;

        if (isSelectionModeActive) {
            highscoreButton.style.backgroundColor = '#3b82f6';
            highscoreButton.style.color = 'white';
            highscoreButton.style.boxShadow = '0 0 10px rgba(59, 130, 246, 0.5)';
            document.body.style.cursor = 'crosshair';
            disableMapInteractions();
            showNotification('Click and drag to select a region');
        } else {
            resetSelectionMode();
        }
    }

    function disableMapInteractions() {
        const m = _getMap(); if (!m) return;
        m.dragPan.disable();
        m.scrollZoom.disable();
        m.boxZoom.disable();
        m.doubleClickZoom.disable();
        m.touchZoomRotate.disable();
    }

    function enableMapInteractions() {
        const m = _getMap(); if (!m) return;
        m.dragPan.enable();
        m.scrollZoom.enable();
        m.boxZoom.enable();
        // Note: doubleClickZoom is intentionally NOT re-enabled — the native site disables it
        m.touchZoomRotate.enable();
    }

    function resetSelectionMode() {
        isSelectionModeActive = false;
        isDragging = false;
        selectionStart = null;
        selectionEnd = null;

        if (highscoreButton) {
            highscoreButton.style.backgroundColor = 'white';
            highscoreButton.style.color = 'black';
            highscoreButton.style.boxShadow = '';
        }

        document.body.style.cursor = '';
        enableMapInteractions();
        clearSelectionCanvas();
    }

    function clearSelectionCanvas() {
        if (selectionCtx && selectionCanvas) {
            selectionCtx.clearRect(0, 0, window.innerWidth, window.innerHeight);
        }
    }

    // ==================== EVENT HANDLERS ====================
    function setupEventListeners() {
        document.addEventListener('mousedown', handleMouseDown);
        document.addEventListener('mousemove', handleMouseMove);
        document.addEventListener('mouseup', handleMouseUp);
        document.addEventListener('keydown', handleKeyDown);
    }

    function handleMouseDown(e) {
        if (!isSelectionModeActive) return;
        if (e.button !== 0) return; // Only left click

        // Check if clicking on UI elements
        if (e.target.closest('#controls-left') || e.target.closest('#controls-right') || e.target.closest('.modal-container')) {
            return;
        }

        isDragging = true;
        selectionStart = screenPointToGrid(e.clientX, e.clientY);
        selectionEnd = selectionStart;

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

    function handleMouseMove(e) {
        if (!isDragging || !selectionStart) return;

        selectionEnd = screenPointToGrid(e.clientX, e.clientY);
        drawSelectionPreview();
    }

    async function handleMouseUp(e) {
        if (!isDragging || !selectionStart || !selectionEnd) return;

        isDragging = false;

        const bounds = getSelectionBounds();
        const width = bounds.maxX - bounds.minX + 1;
        const height = bounds.maxY - bounds.minY + 1;

        if (width < 2 || height < 2) {
            showNotification('Selection too small. Please select a larger area.');
            clearSelectionCanvas();
            resetSelectionMode();
            return;
        }

        // Reset mode but keep selection visible during computation
        isSelectionModeActive = false;
        if (highscoreButton) {
            highscoreButton.style.backgroundColor = 'white';
            highscoreButton.style.color = 'black';
            highscoreButton.style.boxShadow = '';
        }
        document.body.style.cursor = '';
        enableMapInteractions();

        // Show loading modal
        const modal = createLeaderboardModal(bounds, null, true);
        const progressEl = modal.querySelector('.rhs-progress-text');

        const updateProgress = (text) => {
            if (progressEl) progressEl.textContent = text;
        };

        try {
            const userCounts = await computeRegionPixels(bounds, updateProgress);
            updateProgress('Fetching usernames...');
            const leaderboard = await buildLeaderboard(userCounts);
            updateLeaderboardModal(modal, bounds, leaderboard);
        } catch (error) {
            console.error('[Regions Highscore] Error computing leaderboard:', error);
            showNotification('Error computing leaderboard: ' + error.message);
            modal.close();
        }

        clearSelectionCanvas();
        selectionStart = null;
        selectionEnd = null;
    }

    function handleKeyDown(e) {
        if (e.key === 'Escape') {
            if (isSelectionModeActive || isDragging) {
                resetSelectionMode();
            }
        }
    }

    // ==================== COORDINATE HELPERS ====================
    function screenPointToGrid(clientX, clientY) {
        // _map.unproject expects point relative to map container
        const mapContainer = _map.getContainer();
        const rect = mapContainer.getBoundingClientRect();
        const point = [clientX - rect.left, clientY - rect.top];

        const lngLat = _map.unproject(point);
        const merc = turf.toMercator([lngLat.lng, lngLat.lat]);

        return {
            gridX: Math.round(merc[0] / GRID_SIZE),
            gridY: Math.round(merc[1] / GRID_SIZE),
        };
    }

    function gridToScreen(gridX, gridY) {
        const mercX = gridX * GRID_SIZE;
        const mercY = gridY * GRID_SIZE;
        const lngLat = turf.toWgs84([mercX, mercY]);
        const point = _map.project(lngLat);

        // Convert map-relative coordinates to screen coordinates
        const mapContainer = _map.getContainer();
        const rect = mapContainer.getBoundingClientRect();

        return {
            x: point.x + rect.left,
            y: point.y + rect.top
        };
    }

    function getSelectionBounds() {
        return {
            minX: Math.min(selectionStart.gridX, selectionEnd.gridX),
            maxX: Math.max(selectionStart.gridX, selectionEnd.gridX),
            minY: Math.min(selectionStart.gridY, selectionEnd.gridY),
            maxY: Math.max(selectionStart.gridY, selectionEnd.gridY),
        };
    }

    // ==================== DRAWING ====================
    function drawSelectionPreview() {
        clearSelectionCanvas();
        if (!selectionStart || !selectionEnd) return;

        const bounds = getSelectionBounds();

        // Convert grid bounds to screen coordinates
        const topLeft = gridToScreen(bounds.minX - 0.5, bounds.maxY + 0.5);
        const bottomRight = gridToScreen(bounds.maxX + 0.5, bounds.minY - 0.5);

        const x = topLeft.x;
        const y = topLeft.y;
        const width = bottomRight.x - topLeft.x;
        const height = bottomRight.y - topLeft.y;

        selectionCtx.fillStyle = SELECTION_COLOR;
        selectionCtx.fillRect(x, y, width, height);

        selectionCtx.strokeStyle = SELECTION_BORDER_COLOR;
        selectionCtx.lineWidth = 2;
        selectionCtx.strokeRect(x, y, width, height);

        // Draw size indicator
        const selWidth = bounds.maxX - bounds.minX + 1;
        const selHeight = bounds.maxY - bounds.minY + 1;
        const sizeText = `${selWidth} × ${selHeight}`;

        selectionCtx.font = 'bold 14px sans-serif';
        selectionCtx.fillStyle = 'white';
        selectionCtx.strokeStyle = 'black';
        selectionCtx.lineWidth = 3;

        const textX = x + width / 2;
        const textY = y + height / 2;

        selectionCtx.textAlign = 'center';
        selectionCtx.textBaseline = 'middle';
        selectionCtx.strokeText(sizeText, textX, textY);
        selectionCtx.fillText(sizeText, textX, textY);
    }

    // ==================== PIXEL COMPUTATION ====================
    async function computeRegionPixels(bounds, updateProgress) {
        const userCounts = new Map();
        const { minX, maxX, minY, maxY } = bounds;

        // Determine which tiles we need (more efficient iteration)
        const neededTiles = new Set();
        const startTileX = Math.floor(minX / TILE_SIZE) * TILE_SIZE;
        const endTileX = Math.floor(maxX / TILE_SIZE) * TILE_SIZE;
        const startTileY = Math.floor(minY / TILE_SIZE) * TILE_SIZE;
        const endTileY = Math.floor(maxY / TILE_SIZE) * TILE_SIZE;

        for (let tx = startTileX; tx <= endTileX; tx += TILE_SIZE) {
            for (let ty = startTileY; ty <= endTileY; ty += TILE_SIZE) {
                neededTiles.add(`${tx},${ty}`);
            }
        }

        const tilesArray = [...neededTiles];
        console.log(`[Regions Highscore] Need ${tilesArray.length} tiles for region ${maxX - minX + 1}×${maxY - minY + 1}`);
        console.log(`[Regions Highscore] Selection bounds: X ${minX} to ${maxX}, Y ${minY} to ${maxY}`);
        console.log(`[Regions Highscore] Tiles needed:`, tilesArray);

        let processedTiles = 0;
        let totalPixelsFound = 0;

        for (const tileKey of tilesArray) {
            const [tileX, tileY] = tileKey.split(',').map(Number);

            // Update progress
            processedTiles++;
            if (updateProgress) {
                updateProgress(`Processing tile ${processedTiles}/${tilesArray.length}...`);
            }

            // Yield to UI
            await new Promise(resolve => setTimeout(resolve, 0));

            // Try to get from cache first
            let userBitmap = null;
            const cached = tileImageCache.get(tileKey);
            console.log(`[Regions Highscore] Cache lookup for ${tileKey}:`, cached ? 'FOUND' : 'NOT FOUND');
            if (cached) {
                console.log(`[Regions Highscore] Cache entry keys:`, Object.keys(cached));
                console.log(`[Regions Highscore] Cache entry:`, cached);
            }
            if (cached && cached.userBitmap) {
                userBitmap = cached.userBitmap;
                console.log(`[Regions Highscore] Using cached userBitmap, size: ${userBitmap.width}x${userBitmap.height}`);
            } else {
                // Fetch from API
                console.log(`[Regions Highscore] Fetching tile ${tileKey} from API...`);
                if (updateProgress) {
                    updateProgress(`Fetching tile ${tileKey}...`);
                }
                try {
                    const tileData = await fetchTileData(tileX, tileY);
                    console.log(`[Regions Highscore] API response for ${tileKey}:`, tileData);
                    if (tileData && tileData.userBitmap) {
                        userBitmap = tileData.userBitmap;
                        console.log(`[Regions Highscore] Fetched userBitmap, size: ${userBitmap.width}x${userBitmap.height}`);
                    }
                } catch (err) {
                    console.warn(`[Regions Highscore] Failed to fetch tile ${tileKey}:`, err);
                    continue;
                }
            }

            if (!userBitmap) continue;

            // Debug: check if bitmap has ANY non-zero data by sampling various points
            const debugCanvas = new OffscreenCanvas(userBitmap.width, userBitmap.height);
            const debugCtx = debugCanvas.getContext('2d', { willReadFrequently: true });
            debugCtx.drawImage(userBitmap, 0, 0);
            const fullData = debugCtx.getImageData(0, 0, userBitmap.width, userBitmap.height).data;
            let nonZeroInFullBitmap = 0;
            for (let i = 0; i < fullData.length; i += 4) {
                if (fullData[i] !== 0 || fullData[i+1] !== 0 || fullData[i+2] !== 0) {
                    nonZeroInFullBitmap++;
                    if (nonZeroInFullBitmap <= 3) {
                        const pixelIndex = i / 4;
                        const bmpX = pixelIndex % userBitmap.width;
                        const bmpY = Math.floor(pixelIndex / userBitmap.width);
                        // Convert back to grid coordinates both ways
                        const gridXFromBmp = tileX + bmpX;
                        const gridYInverted = tileY + (TILE_SIZE - 1 - bmpY);
                        const gridYDirect = tileY + bmpY;
                        console.log(`[Regions Highscore] Non-zero pixel at bitmap (${bmpX}, ${bmpY}): RGB(${fullData[i]},${fullData[i+1]},${fullData[i+2]})`);
                        console.log(`  - Grid coords if Y inverted: (${gridXFromBmp}, ${gridYInverted})`);
                        console.log(`  - Grid coords if Y direct: (${gridXFromBmp}, ${gridYDirect})`);
                    }
                }
            }
            console.log(`[Regions Highscore] Total non-zero pixels in FULL bitmap: ${nonZeroInFullBitmap}`);

            // Check specific pixel (-351700, 218914) that user mentioned
            const testGridX = -351700;
            const testGridY = 218914;
            if (testGridX >= tileX && testGridX < tileX + TILE_SIZE && testGridY >= tileY && testGridY < tileY + TILE_SIZE) {
                const testLocalX = testGridX - tileX;
                const testLocalYInverted = TILE_SIZE - 1 - (testGridY - tileY);
                const testLocalYDirect = testGridY - tileY;

                console.log(`[Regions Highscore] Test pixel (-351700, 218914):`);
                console.log(`  - If Y inverted: local (${testLocalX}, ${testLocalYInverted})`);
                console.log(`  - If Y direct: local (${testLocalX}, ${testLocalYDirect})`);

                const testIdxInverted = (testLocalYInverted * userBitmap.width + testLocalX) * 4;
                const testIdxDirect = (testLocalYDirect * userBitmap.width + testLocalX) * 4;

                console.log(`  - Inverted value: RGB(${fullData[testIdxInverted]},${fullData[testIdxInverted+1]},${fullData[testIdxInverted+2]},${fullData[testIdxInverted+3]})`);
                console.log(`  - Direct value: RGB(${fullData[testIdxDirect]},${fullData[testIdxDirect+1]},${fullData[testIdxDirect+2]},${fullData[testIdxDirect+3]})`);
            }

            // Calculate the region of this tile that overlaps with selection
            const tileMinX = Math.max(minX, tileX);
            const tileMaxX = Math.min(maxX, tileX + TILE_SIZE - 1);
            const tileMinY = Math.max(minY, tileY);
            const tileMaxY = Math.min(maxY, tileY + TILE_SIZE - 1);

            const regionWidth = tileMaxX - tileMinX + 1;
            const regionHeight = tileMaxY - tileMinY + 1;

            if (regionWidth <= 0 || regionHeight <= 0) continue;

            // Read the entire relevant region at once (much faster than 1x1)
            // Y is NOT inverted in the bitmap - use direct coordinates
            const localStartX = tileMinX - tileX;
            const localStartY = tileMinY - tileY;

            console.log(`[Regions Highscore] Tile ${tileKey}: reading region ${regionWidth}x${regionHeight} at local (${localStartX}, ${localStartY})`);
            console.log(`[Regions Highscore] Tile bounds: X ${tileMinX}-${tileMaxX}, Y ${tileMinY}-${tileMaxY}`);

            const tempCanvas = new OffscreenCanvas(regionWidth, regionHeight);
            const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
            tempCtx.drawImage(
                userBitmap,
                localStartX, localStartY, regionWidth, regionHeight,
                0, 0, regionWidth, regionHeight
            );

            const imageData = tempCtx.getImageData(0, 0, regionWidth, regionHeight);
            const data = imageData.data;

            // Debug: log sample pixels to understand the data format
            console.log(`[Regions Highscore] ImageData size: ${imageData.width}x${imageData.height}, data length: ${data.length}`);
            const samplePixels = [];
            for (let s = 0; s < Math.min(10, regionWidth * regionHeight); s++) {
                const idx = s * 4;
                samplePixels.push(`(${data[idx]},${data[idx+1]},${data[idx+2]},${data[idx+3]})`);
            }
            console.log(`[Regions Highscore] First 10 pixels (RGBA):`, samplePixels.join(' '));

            // Process pixels in chunks to avoid blocking UI
            const CHUNK_SIZE = 50000;
            const totalPixels = regionWidth * regionHeight;
            let nonZeroCount = 0;

            for (let i = 0; i < totalPixels; i++) {
                const offset = i * 4;
                const r = data[offset];
                const g = data[offset + 1];
                const b = data[offset + 2];
                const a = data[offset + 3];

                // User ID is encoded in RGB; check if RGB is non-zero (not alpha)
                const userId = (r << 16) | (g << 8) | b;
                if (userId > 0) {
                    userCounts.set(userId, (userCounts.get(userId) || 0) + 1);
                    totalPixelsFound++;
                    if (nonZeroCount < 3) {
                        console.log(`[Regions Highscore] Found user pixel: userId=${userId} (R=${r},G=${g},B=${b},A=${a})`);
                    }
                    nonZeroCount++;
                }

                // Yield every CHUNK_SIZE pixels to keep UI responsive
                if (i > 0 && i % CHUNK_SIZE === 0) {
                    await new Promise(resolve => setTimeout(resolve, 0));
                }
            }

            console.log(`[Regions Highscore] Found ${nonZeroCount} non-zero pixels in tile region`);
        }

        console.log(`[Regions Highscore] Total pixels with users found: ${totalPixelsFound}`);
        console.log(`[Regions Highscore] Unique users: ${userCounts.size}`);

        return userCounts;
    }

    async function fetchTileData(tileX, tileY) {
        const response = await fetch('https://geopixels.net/GetPixelsCached', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                Tiles: [{ x: tileX, y: tileY, timestamp: 0 }],
            }),
        });

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

        const data = await response.json();
        const tileKey = `tile_${tileX}_${tileY}`;
        const tileInfo = data.Tiles[tileKey];

        console.log(`[Regions Highscore] API tile key: ${tileKey}`);
        console.log(`[Regions Highscore] Available tiles in response:`, Object.keys(data.Tiles));
        console.log(`[Regions Highscore] Tile info:`, tileInfo);

        if (!tileInfo) return null;

        // Handle full tile with WebP images
        if (tileInfo.Type === 'full' && tileInfo.UserWebP) {
            const userBitmap = await decodeWebPToBitmap(tileInfo.UserWebP);
            return { userBitmap };
        }

        // Handle delta (partial update) - we need to process deltas
        if (tileInfo.Pixels && tileInfo.Pixels.length > 0) {
            // Create bitmap from deltas
            const userBitmap = await createBitmapFromDeltas(tileInfo.Pixels, tileX, tileY);
            return { userBitmap };
        }

        return null;
    }

    async function decodeWebPToBitmap(base64Data) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => {
                createImageBitmap(img).then(resolve).catch(reject);
            };
            img.onerror = reject;
            img.src = `data:image/webp;base64,${base64Data}`;
        });
    }

    async function createBitmapFromDeltas(deltas, tileX, tileY) {
        const canvas = new OffscreenCanvas(TILE_SIZE, TILE_SIZE);
        const ctx = canvas.getContext('2d');

        for (const delta of deltas) {
            const [gridX, gridY, color, userId] = delta;
            // Y is NOT inverted - use direct coordinates
            const localX = gridX - tileX;
            const localY = gridY - tileY;

            // Encode userId as RGB
            const r = (userId >> 16) & 0xff;
            const g = (userId >> 8) & 0xff;
            const b = userId & 0xff;

            ctx.fillStyle = `rgb(${r},${g},${b})`;
            ctx.fillRect(localX, localY, 1, 1);
        }

        return createImageBitmap(canvas);
    }

    // ==================== LEADERBOARD ====================
    async function buildLeaderboard(userCounts) {
        // Sort by pixel count descending
        const sorted = [...userCounts.entries()].sort((a, b) => b[1] - a[1]);

        // Fetch usernames
        const userIds = sorted.map(([id]) => id);
        const usernames = await fetchUsernames(userIds);

        return sorted.map(([userId, count], index) => ({
            rank: index + 1,
            userId,
            username: usernames.get(userId) || `User #${userId}`,
            pixelCount: count,
        }));
    }

    async function fetchUsernames(userIds) {
        const usernames = new Map();

        // Batch requests
        for (let i = 0; i < userIds.length; i += USERNAME_BATCH_SIZE) {
            const batch = userIds.slice(i, i + USERNAME_BATCH_SIZE);

            const promises = batch.map(async (userId) => {
                try {
                    const response = await fetch('https://geopixels.net/GetUserProfile', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ targetId: userId }),
                    });

                    if (response.ok) {
                        const data = await response.json();
                        return { userId, name: data.name || `User #${userId}` };
                    }
                } catch (err) {
                    console.warn(`[Regions Highscore] Failed to fetch user ${userId}:`, err);
                }
                return { userId, name: `User #${userId}` };
            });

            const results = await Promise.all(promises);
            for (const { userId, name } of results) {
                usernames.set(userId, name);
            }
        }

        return usernames;
    }

    // ==================== THEME HELPERS ====================
    function isDarkMode() {
        return getComputedStyle(document.documentElement).colorScheme === 'dark';
    }

    function getThemeColors() {
        const dark = isDarkMode();
        return {
            modalBg: dark ? '#1e2939' : 'white',
            overlayBg: dark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.5)',
            text: dark ? '#f3f4f6' : '#333',
            textSecondary: dark ? '#d1d5db' : '#666',
            textMuted: dark ? '#99a1af' : '#888',
            textSubtle: dark ? '#6a7282' : '#999',
            border: dark ? '#364153' : '#eee',
            headerBg: dark ? '#101828' : '#f0f0f0',
            summaryBg: dark ? '#101828' : '#f8f9fa',
            summaryText: dark ? '#d1d5db' : '#555',
            closeBtnColor: dark ? '#99a1af' : '#666',
            closeBtnHoverBg: dark ? '#364153' : '#f0f0f0',
            closeBtnHoverColor: dark ? '#f3f4f6' : '#333',
            notificationBg: dark ? '#1e2939' : '#333',
            notificationText: dark ? '#f3f4f6' : 'white',
        };
    }

    // ==================== ADJUST BOUNDS MODAL ====================
    function showAdjustModal(currentBounds, onConfirm) {
        const existing = document.querySelector('.rhs-adjust-overlay');
        if (existing) existing.remove();

        const t = getThemeColors();

        const overlay = document.createElement('div');
        overlay.className = 'rhs-adjust-overlay';
        overlay.style.cssText = `
            position: fixed; inset: 0; z-index: 10001;
            background: ${t.overlayBg};
            display: flex; align-items: center; justify-content: center;
            font-family: system-ui, sans-serif;
        `;

        const inputStyle = `width: 100%; margin-top: 2px; padding: 6px 8px; border-radius: 6px; border: 1px solid ${t.border}; background: ${t.headerBg}; color: ${t.text}; font-size: 13px; font-family: monospace;`;

        const box = document.createElement('div');
        box.style.cssText = `
            background: ${t.modalBg}; color: ${t.text}; border-radius: 12px;
            box-shadow: 0 16px 48px rgba(0,0,0,0.5);
            padding: 24px; min-width: 320px; display: flex; flex-direction: column; gap: 14px;
        `;

        box.innerHTML = `
            <h3 style="margin: 0; font-size: 16px; font-weight: 700;">Adjust Region Bounds</h3>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <label style="font-size: 12px; color: ${t.textSecondary};">X1 (min)
                    <input id="rhs-adj-x1" type="number" value="${currentBounds.minX}" style="${inputStyle}" />
                </label>
                <label style="font-size: 12px; color: ${t.textSecondary};">X2 (max)
                    <input id="rhs-adj-x2" type="number" value="${currentBounds.maxX}" style="${inputStyle}" />
                </label>
                <label style="font-size: 12px; color: ${t.textSecondary};">Y1 (min)
                    <input id="rhs-adj-y1" type="number" value="${currentBounds.minY}" style="${inputStyle}" />
                </label>
                <label style="font-size: 12px; color: ${t.textSecondary};">Y2 (max)
                    <input id="rhs-adj-y2" type="number" value="${currentBounds.maxY}" style="${inputStyle}" />
                </label>
            </div>
            <div id="rhs-adj-error" style="font-size: 12px; color: #ef4444; display: none;"></div>
            <div style="display: flex; gap: 10px; justify-content: flex-end;">
                <button id="rhs-adj-cancel" style="padding: 8px 16px; border-radius: 8px; border: 1px solid ${t.border}; background: ${t.headerBg}; color: ${t.textSecondary}; cursor: pointer; font-size: 13px;">Cancel</button>
                <button id="rhs-adj-confirm" style="padding: 8px 16px; border-radius: 8px; border: none; background: #3b82f6; color: white; cursor: pointer; font-size: 13px; font-weight: 600;">Apply</button>
            </div>
        `;

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

        const close = () => overlay.remove();
        overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
        box.querySelector('#rhs-adj-cancel').onclick = close;

        box.querySelector('#rhs-adj-confirm').onclick = () => {
            const x1 = parseInt(box.querySelector('#rhs-adj-x1').value);
            const x2 = parseInt(box.querySelector('#rhs-adj-x2').value);
            const y1 = parseInt(box.querySelector('#rhs-adj-y1').value);
            const y2 = parseInt(box.querySelector('#rhs-adj-y2').value);
            const errEl = box.querySelector('#rhs-adj-error');

            if ([x1, x2, y1, y2].some(isNaN)) {
                errEl.textContent = 'All fields must be valid numbers.';
                errEl.style.display = 'block';
                return;
            }

            const newBounds = {
                minX: Math.min(x1, x2), maxX: Math.max(x1, x2),
                minY: Math.min(y1, y2), maxY: Math.max(y1, y2),
            };
            const w = newBounds.maxX - newBounds.minX + 1;
            const h = newBounds.maxY - newBounds.minY + 1;

            if (w < 2 || h < 2) {
                errEl.textContent = 'Region must be at least 2×2.';
                errEl.style.display = 'block';
                return;
            }

            close();
            onConfirm(newBounds);
        };

        const escH = (e) => { if (e.key === 'Escape') { close(); document.removeEventListener('keydown', escH); } };
        document.addEventListener('keydown', escH);
    }

    async function rerunLeaderboard(newBounds) {
        // Remove any existing modal
        const existing = document.querySelector('.rhs-modal-container');
        if (existing) existing.remove();

        const modal = createLeaderboardModal(newBounds, null, true);
        const progressEl = modal.querySelector('.rhs-progress-text');
        const updateProgress = (text) => { if (progressEl) progressEl.textContent = text; };

        try {
            const userCounts = await computeRegionPixels(newBounds, updateProgress);
            updateProgress('Fetching usernames...');
            const leaderboard = await buildLeaderboard(userCounts);
            updateLeaderboardModal(modal, newBounds, leaderboard);
        } catch (error) {
            console.error('[Regions Highscore] Error:', error);
            showNotification('Error computing leaderboard: ' + error.message);
            modal.close();
        }
    }

    // ==================== MODAL ====================
    function createLeaderboardModal(bounds, leaderboard = null, loading = false) {
        // Remove existing modal if any
        const existing = document.querySelector('.rhs-modal-container');
        if (existing) existing.remove();

        const width = bounds.maxX - bounds.minX + 1;
        const height = bounds.maxY - bounds.minY + 1;
        const totalPixels = width * height;

        const t = getThemeColors();

        const modalContainer = document.createElement('div');
        modalContainer.className = 'rhs-modal-container';
        modalContainer.style.cssText = `
            position: fixed;
            inset: 0;
            z-index: 10000;
            background: ${t.overlayBg};
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        const modal = document.createElement('div');
        modal.className = 'rhs-modal';
        modal.style.cssText = `
            position: relative;
            background: ${t.modalBg};
            color: ${t.text};
            border-radius: 12px;
            box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
            padding: 24px;
            min-width: 400px;
            max-width: 90vw;
            max-height: 80vh;
            display: flex;
            flex-direction: column;
        `;

        // Close button
        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = '✕';
        closeBtn.style.cssText = `
            position: absolute;
            top: 12px;
            right: 12px;
            background: none;
            border: none;
            font-size: 20px;
            cursor: pointer;
            color: ${t.closeBtnColor};
            width: 32px;
            height: 32px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
        `;
        closeBtn.onmouseover = () => { closeBtn.style.background = t.closeBtnHoverBg; closeBtn.style.color = t.closeBtnHoverColor; };
        closeBtn.onmouseout = () => { closeBtn.style.background = 'none'; closeBtn.style.color = t.closeBtnColor; };

        // Header
        const header = document.createElement('div');
        header.style.cssText = 'margin-bottom: 16px; padding-right: 32px;';
        header.innerHTML = `
            <h2 style="margin: 0 0 8px 0; font-size: 24px; font-weight: bold; color: ${t.text};">📊 Region Leaderboard</h2>
            <p style="margin: 0; color: ${t.textSecondary}; font-size: 14px;">Selected area: ${width} × ${height} pixels (${totalPixels.toLocaleString()} total)</p>
            <p style="margin: 4px 0 0 0; color: ${t.textMuted}; font-size: 12px; font-family: monospace;">X: ${bounds.minX} to ${bounds.maxX} | Y: ${bounds.minY} to ${bounds.maxY}</p>
        `;

        const adjustBtn = document.createElement('button');
        adjustBtn.textContent = 'Adjust…';
        adjustBtn.style.cssText = `
            margin-top: 8px; padding: 5px 12px; border-radius: 6px; border: 1px solid ${t.border};
            background: ${t.headerBg}; color: ${t.textSecondary}; font-size: 12px; cursor: pointer;
            transition: background 0.15s; white-space: nowrap;
        `;
        adjustBtn.onmouseover = () => { adjustBtn.style.background = t.closeBtnHoverBg; };
        adjustBtn.onmouseout  = () => { adjustBtn.style.background = t.headerBg; };
        adjustBtn.onclick = () => {
            showAdjustModal(bounds, (newBounds) => {
                rerunLeaderboard(newBounds);
            });
        };
        header.appendChild(adjustBtn);

        // Content area
        const content = document.createElement('div');
        content.className = 'rhs-modal-content';
        content.style.cssText = `
            flex: 1;
            overflow-y: auto;
            min-height: 200px;
        `;

        if (loading) {
            content.innerHTML = `
                <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 200px; color: ${t.textSecondary};">
                    <div style="font-size: 32px; margin-bottom: 16px;">⏳</div>
                    <div class="rhs-progress-text">Calculating leaderboard...</div>
                    <div style="font-size: 12px; margin-top: 8px; color: ${t.textSubtle};">This may take a moment for large regions</div>
                </div>
            `;
        } else if (leaderboard) {
            content.appendChild(createLeaderboardTable(leaderboard));
        }

        modal.appendChild(closeBtn);
        modal.appendChild(header);
        modal.appendChild(content);
        modalContainer.appendChild(modal);
        document.body.appendChild(modalContainer);

        // Close handlers
        const closeModal = () => modalContainer.remove();
        closeBtn.onclick = closeModal;
        modalContainer.onclick = (e) => { if (e.target === modalContainer) closeModal(); };

        const escHandler = (e) => {
            if (e.key === 'Escape') {
                closeModal();
                document.removeEventListener('keydown', escHandler);
            }
        };
        document.addEventListener('keydown', escHandler);

        modal.close = closeModal;
        return modal;
    }

    function updateLeaderboardModal(modal, bounds, leaderboard) {
        const content = modal.querySelector('.rhs-modal-content');
        if (!content) return;

        content.innerHTML = '';

        if (leaderboard.length === 0) {
            const t = getThemeColors();
            content.innerHTML = `
                <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 150px; color: ${t.textSecondary};">
                    <div style="font-size: 32px; margin-bottom: 16px;">🤷</div>
                    <div>No pixels found in this region</div>
                </div>
            `;
        } else {
            content.appendChild(createLeaderboardTable(leaderboard));
        }
    }

    function createLeaderboardTable(leaderboard) {
        const t = getThemeColors();
        const container = document.createElement('div');

        // Summary
        const totalPixels = leaderboard.reduce((sum, entry) => sum + entry.pixelCount, 0);
        const summary = document.createElement('div');
        summary.style.cssText = `margin-bottom: 16px; padding: 12px; background: ${t.summaryBg}; border-radius: 8px; font-size: 14px; color: ${t.summaryText};`;
        summary.innerHTML = `<strong>${leaderboard.length}</strong> users placed <strong>${totalPixels.toLocaleString()}</strong> pixels in this region`;
        container.appendChild(summary);

        // Table
        const table = document.createElement('table');
        table.style.cssText = `width: 100%; border-collapse: collapse; font-size: 14px; color: ${t.text};`;

        // Header row
        const thead = document.createElement('thead');
        thead.innerHTML = `
            <tr style="background: ${t.headerBg}; text-align: left;">
                <th style="padding: 10px 12px; font-weight: 600; width: 60px;">Rank</th>
                <th style="padding: 10px 12px; font-weight: 600;">Username</th>
                <th style="padding: 10px 12px; font-weight: 600; text-align: right; width: 100px;">Pixels</th>
                <th style="padding: 10px 12px; font-weight: 600; text-align: right; width: 80px;">%</th>
            </tr>
        `;
        table.appendChild(thead);

        // Body rows
        const tbody = document.createElement('tbody');

        for (const entry of leaderboard) {
            const row = document.createElement('tr');
            row.style.cssText = `
                border-bottom: 1px solid ${t.border};
                ${entry.rank <= 3 ? 'background: ' + getRankBackground(entry.rank) + ';' : ''}
            `;

            const percent = ((entry.pixelCount / totalPixels) * 100).toFixed(1);
            const rankEmoji = getRankEmoji(entry.rank);

            row.innerHTML = `
                <td style="padding: 10px 12px; font-weight: ${entry.rank <= 3 ? 'bold' : 'normal'};">${rankEmoji} ${entry.rank}</td>
                <td style="padding: 10px 12px;">${escapeHtml(entry.username)}</td>
                <td style="padding: 10px 12px; text-align: right; font-family: monospace;">${entry.pixelCount.toLocaleString()}</td>
                <td style="padding: 10px 12px; text-align: right; color: ${t.textSecondary};">${percent}%</td>
            `;

            tbody.appendChild(row);
        }

        table.appendChild(tbody);
        container.appendChild(table);

        return container;
    }

    function getRankEmoji(rank) {
        switch (rank) {
            case 1: return '🥇';
            case 2: return '🥈';
            case 3: return '🥉';
            default: return '';
        }
    }

    function getRankBackground(rank) {
        switch (rank) {
            case 1: return 'rgba(255, 215, 0, 0.15)';
            case 2: return 'rgba(192, 192, 192, 0.15)';
            case 3: return 'rgba(205, 127, 50, 0.15)';
            default: return 'transparent';
        }
    }

    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // ==================== NOTIFICATIONS ====================
    function showNotification(message) {
        // Use GeoPixels' notification system if available
        if (typeof showAnnouncement === 'function') {
            showAnnouncement(message);
            return;
        }

        // Fallback notification
        const t = getThemeColors();
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: ${t.notificationBg};
            color: ${t.notificationText};
            padding: 12px 24px;
            border-radius: 8px;
            z-index: 10001;
            font-size: 14px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        `;
        notification.textContent = message;
        document.body.appendChild(notification);

        setTimeout(() => {
            notification.style.transition = 'opacity 0.3s';
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }

    // ==================== PROCESS WITH BOUNDS (for flyout) =========
    async function processWithBounds(bounds) {
        const width = bounds.maxX - bounds.minX + 1;
        const height = bounds.maxY - bounds.minY + 1;
        if (width < 2 || height < 2) { showNotification('Selection too small. Please select a larger area.'); return; }
        const modal = createLeaderboardModal(bounds, null, true);
        const progressEl = modal.querySelector('.rhs-progress-text');
        const updateProgress = (text) => { if (progressEl) progressEl.textContent = text; };
        try {
            const userCounts = await computeRegionPixels(bounds, updateProgress);
            updateProgress('Fetching usernames...');
            const leaderboard = await buildLeaderboard(userCounts);
            updateLeaderboardModal(modal, bounds, leaderboard);
        } catch (error) {
            console.error('[Regions Highscore] Error computing leaderboard:', error);
            showNotification('Error computing leaderboard: ' + error.message);
            try { modal.close(); } catch {}
        }
    }

    // ==================== START ====================
    init();

    // Expose API for flyout
    _regionsHighscore = { processWithBounds, toggleSelectionMode };
            })();
            _featureStatus.regionsHighscore = 'ok';
            console.log('[GeoPixelcons++] \u2705 Regions Highscore loaded');
        } catch (err) {
            _featureStatus.regionsHighscore = 'error';
            dbgPush(`Regions Highscore init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Regions Highscore' });
            console.error('[GeoPixelcons++] ❌ Regions Highscore failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Region Screenshot [regionScreenshot]
    // ============================================================
    if (_settings.regionScreenshot) {
        try {
            (function _init_regionScreenshot() {

    // ==================== CONFIGURATION ====================
    const GRID_SIZE = 25;
    const TILE_SIZE = 1000;
    const MAX_REGION_PIXELS = 15000 * 15000; // 225M px — hard limit to prevent OOM
    const SELECTION_COLOR = 'rgba(16, 185, 129, 0.25)';
    const SELECTION_BORDER_COLOR = 'rgba(16, 185, 129, 0.9)';

    // ==================== STATE ====================
    let isSelectionModeActive = false;
    let isDragging = false;
    let selectionStart = null;
    let selectionEnd = null;
    let selectionCanvas = null;
    let selectionCtx = null;
    let screenshotButton = null;
    let _map = null; // resolved MapLibre map object (not the DOM element)

    // ==================== MAP ACCESS ====================
    function _getMap() {
        if (_map) return _map;
        try { const m = (0, eval)('map'); if (m && typeof m.scrollZoom !== 'undefined') return (_map = m); } catch {}
        if (typeof unsafeWindow !== 'undefined') { try { const m = unsafeWindow.eval('map'); if (m && typeof m.scrollZoom !== 'undefined') return (_map = m); } catch {} }
        return null;
    }

    // ==================== INITIALIZATION ====================
    function waitForGeoPixels() {
        return new Promise((resolve) => {
            const check = () => {
                if (
                    _getMap() &&
                    typeof turf !== 'undefined' &&
                    typeof tileImageCache !== 'undefined' &&
                    document.getElementById('controls-left')
                ) {
                    resolve();
                } else {
                    setTimeout(check, 500);
                }
            };
            check();
        });
    }

    async function init() {
        await waitForGeoPixels();
        console.log('[Region Screenshot] Initializing...');
        createSelectionCanvas();
        createScreenshotButton();
        setupEventListeners();
        console.log('[Region Screenshot] Ready!');
    }

    // ==================== UI COMPONENTS ====================
    function createScreenshotButton() {
        screenshotButton = document.createElement('button');
        screenshotButton.id = 'gpc-screenshot-trigger';
        screenshotButton.style.display = 'none';
        screenshotButton.addEventListener('click', toggleSelectionMode);
        document.body.appendChild(screenshotButton);
    }

    function createSelectionCanvas() {
        selectionCanvas = document.createElement('canvas');
        selectionCanvas.id = 'screenshot-selection-canvas';
        selectionCanvas.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            pointer-events: none;
            z-index: 1000;
        `;
        document.body.appendChild(selectionCanvas);
        selectionCtx = selectionCanvas.getContext('2d');

        const syncSize = () => {
            const dpr = window.devicePixelRatio || 1;
            selectionCanvas.width = window.innerWidth * dpr;
            selectionCanvas.height = window.innerHeight * dpr;
            selectionCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
        };
        syncSize();
        window.addEventListener('resize', syncSize);

        ['move', 'rotate', 'zoom'].forEach((ev) => {
            _map.on(ev, () => {
                if (isDragging) drawSelectionPreview();
            });
        });
    }

    // ==================== SELECTION MODE ====================
    function toggleSelectionMode() {
        isSelectionModeActive = !isSelectionModeActive;
        if (isSelectionModeActive) {
            screenshotButton.style.backgroundColor = '#10b981';
            screenshotButton.style.color = 'white';
            screenshotButton.style.boxShadow = '0 0 10px rgba(16, 185, 129, 0.6)';
            document.body.style.cursor = 'crosshair';
            disableMapInteractions();
            showNotification('Click and drag to select a region to screenshot');
        } else {
            resetSelectionMode();
        }
    }

    function disableMapInteractions() {
        const m = _getMap(); if (!m) return;
        m.dragPan.disable();
        m.scrollZoom.disable();
        m.boxZoom.disable();
        m.doubleClickZoom.disable();
        m.touchZoomRotate.disable();
    }

    function enableMapInteractions() {
        const m = _getMap(); if (!m) return;
        m.dragPan.enable();
        m.scrollZoom.enable();
        m.boxZoom.enable();
        // Note: doubleClickZoom is intentionally NOT re-enabled — the native site disables it
        m.touchZoomRotate.enable();
    }

    function resetSelectionMode() {
        isSelectionModeActive = false;
        isDragging = false;
        selectionStart = null;
        selectionEnd = null;
        if (screenshotButton) {
            screenshotButton.style.backgroundColor = 'white';
            screenshotButton.style.color = 'black';
            screenshotButton.style.boxShadow = '';
        }
        document.body.style.cursor = '';
        enableMapInteractions();
        clearSelectionCanvas();
    }

    function clearSelectionCanvas() {
        if (selectionCtx && selectionCanvas) {
            selectionCtx.clearRect(0, 0, window.innerWidth, window.innerHeight);
        }
    }

    // ==================== EVENT HANDLERS ====================
    function setupEventListeners() {
        document.addEventListener('mousedown', handleMouseDown);
        document.addEventListener('mousemove', handleMouseMove);
        document.addEventListener('mouseup', handleMouseUp);
        document.addEventListener('keydown', handleKeyDown);
    }

    function handleMouseDown(e) {
        if (!isSelectionModeActive) return;
        if (e.button !== 0) return;
        if (
            e.target.closest('#controls-left') ||
            e.target.closest('#controls-right') ||
            e.target.closest('.rsc-modal-container')
        ) return;

        isDragging = true;
        selectionStart = screenPointToGrid(e.clientX, e.clientY);
        selectionEnd = selectionStart;
        e.preventDefault();
        e.stopPropagation();
    }

    function handleMouseMove(e) {
        if (!isDragging || !selectionStart) return;
        selectionEnd = screenPointToGrid(e.clientX, e.clientY);
        drawSelectionPreview();
    }

    async function handleMouseUp(e) {
        if (!isDragging || !selectionStart || !selectionEnd) return;
        isDragging = false;

        const bounds = getSelectionBounds();
        const width = bounds.maxX - bounds.minX + 1;
        const height = bounds.maxY - bounds.minY + 1;

        if (width < 2 || height < 2) {
            showNotification('Selection too small — please drag a larger area.');
            clearSelectionCanvas();
            resetSelectionMode();
            return;
        }

        if (width * height > MAX_REGION_PIXELS) {
            showNotification(`Region too large (${width}×${height}). Maximum is ~15000×15000 px.`);
            clearSelectionCanvas();
            resetSelectionMode();
            return;
        }

        // Exit selection mode
        isSelectionModeActive = false;
        if (screenshotButton) {
            screenshotButton.style.backgroundColor = 'white';
            screenshotButton.style.color = 'black';
            screenshotButton.style.boxShadow = '';
        }
        document.body.style.cursor = '';
        enableMapInteractions();

        // Show loading modal
        const modal = createPreviewModal(bounds, null, true);
        const progressEl = modal.querySelector('.rsc-progress-text');
        const updateProgress = (text) => { if (progressEl) progressEl.textContent = text; };

        try {
            const screenshotCanvas = await renderRegionToCanvas(bounds, updateProgress);
            updatePreviewModal(modal, bounds, screenshotCanvas);
        } catch (err) {
            console.error('[Region Screenshot] Error:', err);
            showNotification('Error capturing screenshot: ' + err.message);
            modal.closeModal();
        }

        clearSelectionCanvas();
        selectionStart = null;
        selectionEnd = null;
    }

    function handleKeyDown(e) {
        if (e.key === 'Escape' && (isSelectionModeActive || isDragging)) {
            resetSelectionMode();
        }
    }

    // ==================== COORDINATE HELPERS ====================
    function screenPointToGrid(clientX, clientY) {
        const mapContainer = _map.getContainer();
        const rect = mapContainer.getBoundingClientRect();
        const lngLat = _map.unproject([clientX - rect.left, clientY - rect.top]);
        const merc = turf.toMercator([lngLat.lng, lngLat.lat]);
        return {
            gridX: Math.round(merc[0] / GRID_SIZE),
            gridY: Math.round(merc[1] / GRID_SIZE),
        };
    }

    function gridToScreen(gridX, gridY) {
        const lngLat = turf.toWgs84([gridX * GRID_SIZE, gridY * GRID_SIZE]);
        const point = _map.project(lngLat);
        const rect = _map.getContainer().getBoundingClientRect();
        return { x: point.x + rect.left, y: point.y + rect.top };
    }

    function getSelectionBounds() {
        return {
            minX: Math.min(selectionStart.gridX, selectionEnd.gridX),
            maxX: Math.max(selectionStart.gridX, selectionEnd.gridX),
            minY: Math.min(selectionStart.gridY, selectionEnd.gridY),
            maxY: Math.max(selectionStart.gridY, selectionEnd.gridY),
        };
    }

    // ==================== SELECTION DRAWING ====================
    function drawSelectionPreview() {
        clearSelectionCanvas();
        if (!selectionStart || !selectionEnd) return;

        const bounds = getSelectionBounds();
        const topLeft = gridToScreen(bounds.minX - 0.5, bounds.maxY + 0.5);
        const bottomRight = gridToScreen(bounds.maxX + 0.5, bounds.minY - 0.5);

        const x = topLeft.x;
        const y = topLeft.y;
        const w = bottomRight.x - topLeft.x;
        const h = bottomRight.y - topLeft.y;

        selectionCtx.fillStyle = SELECTION_COLOR;
        selectionCtx.fillRect(x, y, w, h);

        selectionCtx.strokeStyle = SELECTION_BORDER_COLOR;
        selectionCtx.lineWidth = 2;
        selectionCtx.setLineDash([6, 3]);
        selectionCtx.strokeRect(x, y, w, h);
        selectionCtx.setLineDash([]);

        const selW = bounds.maxX - bounds.minX + 1;
        const selH = bounds.maxY - bounds.minY + 1;
        const sizeText = `${selW} × ${selH}`;

        selectionCtx.font = 'bold 14px sans-serif';
        selectionCtx.textAlign = 'center';
        selectionCtx.textBaseline = 'middle';
        selectionCtx.lineWidth = 3;
        selectionCtx.strokeStyle = 'rgba(0,0,0,0.6)';
        selectionCtx.strokeText(sizeText, x + w / 2, y + h / 2);
        selectionCtx.fillStyle = 'white';
        selectionCtx.fillText(sizeText, x + w / 2, y + h / 2);
    }

    // ==================== SCREENSHOT RENDERING ====================
    async function renderRegionToCanvas(bounds, updateProgress) {
        const { minX, maxX, minY, maxY } = bounds;
        const outWidth  = maxX - minX + 1;
        const outHeight = maxY - minY + 1;

        // Output canvas — transparent background, 1px = 1 grid cell
        const outputCanvas = new OffscreenCanvas(outWidth, outHeight);
        const outputCtx = outputCanvas.getContext('2d');
        outputCtx.clearRect(0, 0, outWidth, outHeight);

        // Find all tiles that overlap with the selection
        const startTileX = Math.floor(minX / TILE_SIZE) * TILE_SIZE;
        const endTileX   = Math.floor(maxX / TILE_SIZE) * TILE_SIZE;
        const startTileY = Math.floor(minY / TILE_SIZE) * TILE_SIZE;
        const endTileY   = Math.floor(maxY / TILE_SIZE) * TILE_SIZE;

        const neededTiles = [];
        for (let tx = startTileX; tx <= endTileX; tx += TILE_SIZE) {
            for (let ty = startTileY; ty <= endTileY; ty += TILE_SIZE) {
                neededTiles.push([tx, ty]);
            }
        }

        console.log(`[Region Screenshot] ${neededTiles.length} tile(s) needed for ${outWidth}×${outHeight} region`);

        let processed = 0;
        for (const [tileX, tileY] of neededTiles) {
            processed++;
            const tileKey = `${tileX},${tileY}`;
            updateProgress && updateProgress(`Processing tile ${processed}/${neededTiles.length}…`);

            // Yield to browser
            await new Promise(r => setTimeout(r, 0));

            // ---- Try cache first ----
            let colorBitmap = null;
            let deltas = null;

            const cached = tileImageCache.get(tileKey);
            if (cached) {
                colorBitmap = cached.colorBitmap || null;
                deltas = cached.deltas || null;
            }

            // ---- Fallback: fetch from API ----
            if (!colorBitmap) {
                updateProgress && updateProgress(`Fetching tile ${tileKey} from server…`);
                try {
                    const fetched = await fetchTileColorBitmap(tileX, tileY);
                    colorBitmap = fetched.colorBitmap;
                    deltas = fetched.deltas;
                } catch (err) {
                    console.warn(`[Region Screenshot] Could not load tile ${tileKey}:`, err);
                    continue;
                }
            }

            if (!colorBitmap) continue;

            // ---- Compute overlapping region in tile local coords ----
            const tileMinX = Math.max(minX, tileX);
            const tileMaxX = Math.min(maxX, tileX + TILE_SIZE - 1);
            const tileMinY = Math.max(minY, tileY);
            const tileMaxY = Math.min(maxY, tileY + TILE_SIZE - 1);

            const regionW = tileMaxX - tileMinX + 1;
            const regionH = tileMaxY - tileMinY + 1;
            if (regionW <= 0 || regionH <= 0) continue;

            const localStartX = tileMinX - tileX;
            const localStartY = tileMinY - tileY;

            // Destination position on output canvas
            const destX = tileMinX - minX;
            const destY = tileMinY - minY;

            // Draw this tile's color section onto the output canvas
            outputCtx.drawImage(
                colorBitmap,
                localStartX, localStartY, regionW, regionH,
                destX, destY, regionW, regionH
            );

            // ---- Apply any recent in-memory deltas on top ----
            // These are pixel updates that have arrived since the last full sync
            // and may not yet be baked into the colorBitmap.
            if (deltas && deltas.length > 0) {
                for (const delta of deltas) {
                    const gx = delta.gridX;
                    const gy = delta.gridY;

                    // Skip if outside this tile's contributing region
                    if (gx < tileMinX || gx > tileMaxX || gy < tileMinY || gy > tileMaxY) continue;

                    const ox = gx - minX;
                    const oy = gy - minY;

                    if (delta.color === '#00000000' || delta.color === null) {
                        // Erased pixel — clear it
                        outputCtx.clearRect(ox, oy, 1, 1);
                    } else if (delta.color) {
                        outputCtx.fillStyle = delta.color;
                        outputCtx.fillRect(ox, oy, 1, 1);
                    }
                }
            }
        }

        updateProgress && updateProgress('Finalizing…');

        // Transfer to a regular (main-thread) canvas so we can export it.
        // Flip vertically: grid Y increases northward but canvas Y increases downward,
        // so without a flip the image is upside-down.
        const regularCanvas = document.createElement('canvas');
        regularCanvas.width = outWidth;
        regularCanvas.height = outHeight;
        const regularCtx = regularCanvas.getContext('2d');
        regularCtx.save();
        regularCtx.translate(0, outHeight);
        regularCtx.scale(1, -1);
        regularCtx.drawImage(outputCanvas, 0, 0);
        regularCtx.restore();

        return regularCanvas;
    }

    // ---- API tile fetch (fallback when not cached) ----
    async function fetchTileColorBitmap(tileX, tileY) {
        const response = await fetch('https://geopixels.net/GetPixelsCached', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ Tiles: [{ x: tileX, y: tileY, timestamp: 0 }] }),
        });

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

        const data = await response.json();
        const tileKey = `tile_${tileX}_${tileY}`;
        const tileInfo = data.Tiles && data.Tiles[tileKey];

        if (!tileInfo) return { colorBitmap: null, deltas: null };

        // Full tile with WebP
        if (tileInfo.Type === 'full' && tileInfo.ColorWebP) {
            const colorBitmap = await decodeWebP(tileInfo.ColorWebP);
            // Process any bundled deltas
            const deltas = buildDeltasFromRaw(tileInfo.Deltas || []);
            return { colorBitmap, deltas };
        }

        // Delta-only tile — build a small bitmap from the delta array
        if (tileInfo.Pixels && tileInfo.Pixels.length > 0) {
            const colorBitmap = await buildColorBitmapFromDeltas(tileInfo.Pixels, tileX, tileY);
            return { colorBitmap, deltas: null };
        }

        return { colorBitmap: null, deltas: null };
    }

    async function decodeWebP(base64Data) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => createImageBitmap(img).then(resolve).catch(reject);
            img.onerror = reject;
            img.src = `data:image/webp;base64,${base64Data}`;
        });
    }

    function buildDeltasFromRaw(rawDeltas) {
        return rawDeltas.map(p => {
            const [gridX, gridY, color] = p;
            if (color === -1) return { gridX, gridY, color: null };
            const r = (color >> 16) & 0xff;
            const g = (color >> 8) & 0xff;
            const b = color & 0xff;
            return { gridX, gridY, color: `rgb(${r},${g},${b})` };
        });
    }

    async function buildColorBitmapFromDeltas(rawDeltas, tileX, tileY) {
        const canvas = new OffscreenCanvas(TILE_SIZE, TILE_SIZE);
        const ctx = canvas.getContext('2d');
        for (const [gridX, gridY, color] of rawDeltas) {
            if (color === -1) continue;
            const r = (color >> 16) & 0xff;
            const g = (color >> 8) & 0xff;
            const b = color & 0xff;
            ctx.fillStyle = `rgb(${r},${g},${b})`;
            ctx.fillRect(gridX - tileX, gridY - tileY, 1, 1);
        }
        return createImageBitmap(canvas);
    }

    // ==================== ADJUST BOUNDS MODAL ====================
    function showAdjustModal(currentBounds, onConfirm) {
        const existing = document.querySelector('.rsc-adjust-overlay');
        if (existing) existing.remove();

        const overlay = document.createElement('div');
        overlay.className = 'rsc-adjust-overlay';
        overlay.style.cssText = `
            position: fixed; inset: 0; z-index: 10001;
            background: rgba(0,0,0,0.55);
            display: flex; align-items: center; justify-content: center;
            font-family: system-ui, sans-serif;
        `;

        const box = document.createElement('div');
        box.style.cssText = `
            background: #1e1e2e; color: #cdd6f4; border-radius: 12px;
            box-shadow: 0 16px 48px rgba(0,0,0,0.5);
            padding: 24px; min-width: 320px; display: flex; flex-direction: column; gap: 14px;
        `;

        box.innerHTML = `
            <h3 style="margin: 0; font-size: 16px; font-weight: 700; color: #cba6f7;">Adjust Region Bounds</h3>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <label style="font-size: 12px; color: #a6adc8;">X1 (min)
                    <input id="rsc-adj-x1" type="number" value="${currentBounds.minX}" style="width: 100%; margin-top: 2px; padding: 6px 8px; border-radius: 6px; border: 1px solid #45475a; background: #313244; color: #cdd6f4; font-size: 13px; font-family: monospace;" />
                </label>
                <label style="font-size: 12px; color: #a6adc8;">X2 (max)
                    <input id="rsc-adj-x2" type="number" value="${currentBounds.maxX}" style="width: 100%; margin-top: 2px; padding: 6px 8px; border-radius: 6px; border: 1px solid #45475a; background: #313244; color: #cdd6f4; font-size: 13px; font-family: monospace;" />
                </label>
                <label style="font-size: 12px; color: #a6adc8;">Y1 (min)
                    <input id="rsc-adj-y1" type="number" value="${currentBounds.minY}" style="width: 100%; margin-top: 2px; padding: 6px 8px; border-radius: 6px; border: 1px solid #45475a; background: #313244; color: #cdd6f4; font-size: 13px; font-family: monospace;" />
                </label>
                <label style="font-size: 12px; color: #a6adc8;">Y2 (max)
                    <input id="rsc-adj-y2" type="number" value="${currentBounds.maxY}" style="width: 100%; margin-top: 2px; padding: 6px 8px; border-radius: 6px; border: 1px solid #45475a; background: #313244; color: #cdd6f4; font-size: 13px; font-family: monospace;" />
                </label>
            </div>
            <div id="rsc-adj-error" style="font-size: 12px; color: #f38ba8; display: none;"></div>
            <div style="display: flex; gap: 10px; justify-content: flex-end;">
                <button id="rsc-adj-cancel" style="padding: 8px 16px; border-radius: 8px; border: 1px solid #45475a; background: #313244; color: #a6adc8; cursor: pointer; font-size: 13px;">Cancel</button>
                <button id="rsc-adj-confirm" style="padding: 8px 16px; border-radius: 8px; border: none; background: #cba6f7; color: #1e1e2e; cursor: pointer; font-size: 13px; font-weight: 600;">Apply</button>
            </div>
        `;

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

        const close = () => overlay.remove();
        overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
        box.querySelector('#rsc-adj-cancel').onclick = close;

        box.querySelector('#rsc-adj-confirm').onclick = () => {
            const x1 = parseInt(box.querySelector('#rsc-adj-x1').value);
            const x2 = parseInt(box.querySelector('#rsc-adj-x2').value);
            const y1 = parseInt(box.querySelector('#rsc-adj-y1').value);
            const y2 = parseInt(box.querySelector('#rsc-adj-y2').value);
            const errEl = box.querySelector('#rsc-adj-error');

            if ([x1, x2, y1, y2].some(isNaN)) {
                errEl.textContent = 'All fields must be valid numbers.';
                errEl.style.display = 'block';
                return;
            }

            const newBounds = {
                minX: Math.min(x1, x2), maxX: Math.max(x1, x2),
                minY: Math.min(y1, y2), maxY: Math.max(y1, y2),
            };
            const w = newBounds.maxX - newBounds.minX + 1;
            const h = newBounds.maxY - newBounds.minY + 1;

            if (w < 2 || h < 2) {
                errEl.textContent = 'Region must be at least 2×2.';
                errEl.style.display = 'block';
                return;
            }
            if (w * h > MAX_REGION_PIXELS) {
                errEl.textContent = `Region too large (${w}×${h}).`;
                errEl.style.display = 'block';
                return;
            }

            close();
            onConfirm(newBounds);
        };

        const escH = (e) => { if (e.key === 'Escape') { close(); document.removeEventListener('keydown', escH); } };
        document.addEventListener('keydown', escH);
    }

    async function rerunScreenshot(newBounds) {
        // Remove any existing modal
        const existing = document.querySelector('.rsc-modal-container');
        if (existing) existing.remove();

        const modal = createPreviewModal(newBounds, null, true);
        const progressEl = modal.querySelector('.rsc-progress-text');
        const updateProgress = (text) => { if (progressEl) progressEl.textContent = text; };

        try {
            const screenshotCanvas = await renderRegionToCanvas(newBounds, updateProgress);
            updatePreviewModal(modal, newBounds, screenshotCanvas);
        } catch (err) {
            console.error('[Region Screenshot] Error:', err);
            showNotification('Error capturing screenshot: ' + err.message);
            modal.closeModal();
        }
    }

    // ==================== MODAL ====================
    function createPreviewModal(bounds, screenshotCanvas, loading = false) {
        // Remove any existing modal
        const existing = document.querySelector('.rsc-modal-container');
        if (existing) existing.remove();

        const w = bounds.maxX - bounds.minX + 1;
        const h = bounds.maxY - bounds.minY + 1;

        // ---- Overlay ----
        const overlay = document.createElement('div');
        overlay.className = 'rsc-modal-container';
        overlay.style.cssText = `
            position: fixed;
            inset: 0;
            z-index: 10000;
            background: rgba(0, 0, 0, 0.6);
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        // ---- Modal box ----
        const modal = document.createElement('div');
        modal.className = 'rsc-modal';
        modal.style.cssText = `
            position: relative;
            background: #1e1e2e;
            color: #cdd6f4;
            border-radius: 14px;
            box-shadow: 0 24px 60px rgba(0,0,0,0.55);
            padding: 24px;
            min-width: 380px;
            max-width: min(90vw, 700px);
            max-height: 90vh;
            display: flex;
            flex-direction: column;
            gap: 16px;
            font-family: system-ui, sans-serif;
        `;

        // ---- Header ----
        const header = document.createElement('div');
        header.style.cssText = 'display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;';
        header.innerHTML = `
            <div>
                <h2 style="margin: 0 0 6px 0; font-size: 20px; font-weight: 700; color: #cba6f7;">📷 Region Screenshot</h2>
                <p style="margin: 0; font-size: 13px; color: #a6adc8;">${w} × ${h} px &nbsp;|&nbsp; X: ${bounds.minX} → ${bounds.maxX} &nbsp;|&nbsp; Y: ${bounds.minY} → ${bounds.maxY}</p>
            </div>
        `;

        const adjustBtn = document.createElement('button');
        adjustBtn.textContent = 'Adjust…';
        adjustBtn.style.cssText = `
            flex-shrink: 0; padding: 5px 12px; border-radius: 6px; border: 1px solid #45475a;
            background: #313244; color: #a6adc8; font-size: 12px; cursor: pointer;
            transition: background 0.15s; white-space: nowrap;
        `;
        adjustBtn.onmouseover = () => { adjustBtn.style.background = '#45475a'; };
        adjustBtn.onmouseout  = () => { adjustBtn.style.background = '#313244'; };
        adjustBtn.onclick = () => {
            showAdjustModal(bounds, (newBounds) => {
                rerunScreenshot(newBounds);
            });
        };
        header.insertBefore(adjustBtn, null);

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = '✕';
        closeBtn.style.cssText = `
            flex-shrink: 0;
            background: #313244;
            border: none;
            color: #a6adc8;
            font-size: 16px;
            width: 32px;
            height: 32px;
            border-radius: 8px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background 0.15s;
        `;
        closeBtn.onmouseover = () => { closeBtn.style.background = '#45475a'; };
        closeBtn.onmouseout  = () => { closeBtn.style.background = '#313244'; };
        header.appendChild(closeBtn);

        // ---- Content area ----
        const content = document.createElement('div');
        content.className = 'rsc-modal-content';
        content.style.cssText = `
            flex: 1;
            overflow: auto;
            display: flex;
            flex-direction: column;
            gap: 14px;
        `;

        if (loading) {
            content.innerHTML = `
                <div style="display: flex; flex-direction: column; align-items: center; justify-content: center;
                            min-height: 180px; color: #a6adc8; gap: 12px;">
                    <div style="font-size: 36px;">⏳</div>
                    <div class="rsc-progress-text" style="font-size: 14px;">Preparing screenshot…</div>
                    <div style="font-size: 12px; color: #6c7086;">Large regions may take a moment</div>
                </div>
            `;
        } else if (screenshotCanvas) {
            buildPreviewContent(content, bounds, screenshotCanvas);
        }

        // ---- Assemble ----
        modal.appendChild(header);
        modal.appendChild(content);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        const closeModal = () => overlay.remove();
        closeBtn.onclick = closeModal;
        overlay.onclick = (e) => { if (e.target === overlay) closeModal(); };

        const escHandler = (e) => {
            if (e.key === 'Escape') { closeModal(); document.removeEventListener('keydown', escHandler); }
        };
        document.addEventListener('keydown', escHandler);

        modal.closeModal = closeModal;
        return modal;
    }

    function updatePreviewModal(modal, bounds, screenshotCanvas) {
        const content = modal.querySelector('.rsc-modal-content');
        if (!content) return;
        content.innerHTML = '';
        buildPreviewContent(content, bounds, screenshotCanvas);
    }

    function buildPreviewContent(container, bounds, canvas) {
        const w = bounds.maxX - bounds.minX + 1;
        const h = bounds.maxY - bounds.minY + 1;

        // ---- Checkerboard preview wrapper (shows transparency) ----
        const previewWrapper = document.createElement('div');
        previewWrapper.style.cssText = `
            border-radius: 10px;
            overflow: hidden;
            max-height: 55vh;
            background: repeating-conic-gradient(#313244 0% 25%, #45475a 0% 50%) 0 0 / 16px 16px;
            display: flex;
            align-items: center;
            justify-content: center;
            border: 2px solid #45475a;
        `;

        const imgEl = document.createElement('img');
        imgEl.style.cssText = `
            max-width: 100%;
            max-height: 55vh;
            image-rendering: pixelated;
            object-fit: contain;
        `;
        imgEl.src = canvas.toDataURL('image/png');

        previewWrapper.appendChild(imgEl);
        container.appendChild(previewWrapper);

        // ---- Info row ----
        const info = document.createElement('p');
        info.style.cssText = 'margin: 0; font-size: 12px; color: #6c7086; text-align: center;';
        info.textContent = `${w}×${h} pixels • PNG with transparent background`;
        container.appendChild(info);

        // ---- Buttons ----
        const btnRow = document.createElement('div');
        btnRow.style.cssText = 'display: flex; gap: 10px; justify-content: stretch;';

        // Download button
        const downloadBtn = document.createElement('button');
        downloadBtn.innerHTML = '⬇ Download PNG';
        downloadBtn.style.cssText = `
            flex: 1;
            padding: 10px 16px;
            background: #cba6f7;
            color: #1e1e2e;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: opacity 0.15s;
        `;
        downloadBtn.onmouseover = () => { downloadBtn.style.opacity = '0.85'; };
        downloadBtn.onmouseout  = () => { downloadBtn.style.opacity = '1'; };
        downloadBtn.onclick = () => {
            const link = document.createElement('a');
            const ts = (() => { try { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T${String(d.getHours()).padStart(2,'0')}-${String(d.getMinutes()).padStart(2,'0')}-${String(d.getSeconds()).padStart(2,'0')}`; } catch(_) { return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); } })();
            link.download = `geopixels-screenshot-${ts}.png`;
            link.href = canvas.toDataURL('image/png');
            link.click();
        };

        // Copy button
        const copyBtn = document.createElement('button');
        copyBtn.innerHTML = '📋 Copy to Clipboard';
        copyBtn.style.cssText = `
            flex: 1;
            padding: 10px 16px;
            background: #89dceb;
            color: #1e1e2e;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: opacity 0.15s;
        `;
        copyBtn.onmouseover = () => { copyBtn.style.opacity = '0.85'; };
        copyBtn.onmouseout  = () => { copyBtn.style.opacity = '1'; };
        copyBtn.onclick = () => {
            if (!navigator.clipboard || !window.ClipboardItem) {
                showNotification('Clipboard API not supported in this browser.');
                return;
            }
            canvas.toBlob(async (blob) => {
                try {
                    await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
                    copyBtn.innerHTML = '✅ Copied!';
                    setTimeout(() => { copyBtn.innerHTML = '📋 Copy to Clipboard'; }, 2000);
                } catch (err) {
                    console.error('[Region Screenshot] Clipboard write failed:', err);
                    showNotification('Could not write to clipboard: ' + err.message);
                }
            }, 'image/png');
        };

        btnRow.appendChild(downloadBtn);
        btnRow.appendChild(copyBtn);
        container.appendChild(btnRow);
    }

    // ==================== NOTIFICATIONS ====================
    function showNotification(message) {
        if (typeof showAnnouncement === 'function') {
            showAnnouncement(message);
            return;
        }

        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: #313244;
            color: #cdd6f4;
            padding: 12px 24px;
            border-radius: 10px;
            z-index: 10002;
            font-size: 14px;
            box-shadow: 0 4px 16px rgba(0,0,0,0.4);
            font-family: system-ui, sans-serif;
            max-width: 400px;
            text-align: center;
        `;
        notification.textContent = message;
        document.body.appendChild(notification);

        setTimeout(() => {
            notification.style.transition = 'opacity 0.3s';
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3500);
    }

    // ==================== PROCESS WITH BOUNDS (for flyout) =========
    async function processWithBounds(bounds) {
        const width = bounds.maxX - bounds.minX + 1;
        const height = bounds.maxY - bounds.minY + 1;
        if (width < 2 || height < 2) { showNotification('Selection too small — please select a larger area.'); return; }
        if (width * height > MAX_REGION_PIXELS) { showNotification(`Region too large (${width}×${height}). Maximum is ~15000×15000 px.`); return; }
        const modal = createPreviewModal(bounds, null, true);
        const progressEl = modal.querySelector('.rsc-progress-text');
        const updateProgress = (text) => { if (progressEl) progressEl.textContent = text; };
        try {
            const screenshotCanvas = await renderRegionToCanvas(bounds, updateProgress);
            updatePreviewModal(modal, bounds, screenshotCanvas);
        } catch (err) {
            console.error('[Region Screenshot] Error:', err);
            showNotification('Error capturing screenshot: ' + err.message);
            try { modal.closeModal(); } catch {}
        }
    }

    async function silentDownload(bounds) {
        const width = bounds.maxX - bounds.minX + 1;
        const height = bounds.maxY - bounds.minY + 1;
        if (width < 2 || height < 2 || width * height > MAX_REGION_PIXELS) return;
        try {
            const canvas = await renderRegionToCanvas(bounds, () => {});
            const link = document.createElement('a');
            const ts = (() => { try { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T${String(d.getHours()).padStart(2,'0')}-${String(d.getMinutes()).padStart(2,'0')}-${String(d.getSeconds()).padStart(2,'0')}`; } catch(_) { return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); } })();
            link.download = `geopixels-screenshot-${ts}.png`;
            link.href = canvas.toDataURL('image/png');
            link.click();
        } catch (err) {
            console.error('[Region Screenshot] Silent download error:', err);
        }
    }

    // ==================== START ====================
    init();

    // Expose API for flyout
    _regionScreenshot = { processWithBounds, toggleSelectionMode, silentDownload };
            })();
            _featureStatus.regionScreenshot = 'ok';
            console.log('[GeoPixelcons++] \u2705 Region Screenshot loaded');
        } catch (err) {
            _featureStatus.regionScreenshot = 'error';
            dbgPush(`Region Screenshot init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Region Screenshot' });
            console.error('[GeoPixelcons++] ❌ Region Screenshot failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Bulk Purchase Colors [bulkPurchaseColors]
    // ============================================================
    if (_settings.bulkPurchaseColors) {
        try {
            (function _init_bulkPurchaseColors() {

    // ─── Constants ────────────────────────────────────────────────────────────────
    const PIXELS_PER_COLOR = 100; // Informational cost shown in the preview
    const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;

    // ─── Dark mode detection (geopixels++ compatibility) ──────────────────────────
    function isDarkMode() {
        return getComputedStyle(document.documentElement).colorScheme === 'dark';
    }

    function t() {
        const dark = isDarkMode();
        return {
            panelBg:      dark ? '#1e2939' : '#fff',
            text:         dark ? '#f3f4f6' : '#1f2937',
            textMed:      dark ? '#e5e7eb' : '#374151',
            textSec:      dark ? '#d1d5db' : '#6b7280',
            textMuted:    dark ? '#99a1af' : '#9ca3af',
            textOwned:    dark ? '#6a7282' : '#b0b0b0',
            border:       dark ? '#364153' : '#e5e7eb',
            borderLight:  dark ? '#364153' : '#ececec',
            inputBorder:  dark ? '#4a5565' : '#d1d5db',
            rowBg:        dark ? '#101828' : '#fff',
            rowOwnedBg:   dark ? '#1e2939' : '#f3f4f6',
            sepBg:        dark ? '#101828' : '#f9fafb',
            progressBg:   dark ? '#364153' : '#e5e7eb',
            cancelBg:     dark ? '#364153' : '#e5e7eb',
            cancelText:   dark ? '#e5e7eb' : '#374151',
            closeBg:      dark ? '#364153' : '#f3f4f6',
            closeText:    dark ? '#d1d5db' : '#6b7280',
            queueBg:      dark ? '#101828' : '#fff',
            queueBorder:  dark ? '#364153' : '#e5e7eb',
        };
    }

    // ─── Credential access ────────────────────────────────────────────────────────
    //
    // The page declares `tokenUser`, `userID`, and `subject` with `let` in
    // index121.js. Top-level `let` is NOT a property of `window`, and
    // _pw.eval() cannot reach them either (different script scope).
    //
    // Solution: inject a <script> tag that registers a live getter on
    // window._gpAuth from within the page's own global scope.
    //
    (function installAuthBridge() {
        const s = document.createElement('script');
        s.textContent = `
            Object.defineProperty(window, '_gpAuth', {
                configurable: true,
                get: function() {
                    return {
                        token:   typeof tokenUser !== 'undefined' ? tokenUser : null,
                        userId:  typeof userID   !== 'undefined' ? userID   : null,
                        subject: typeof subject  !== 'undefined' ? subject  : null,
                    };
                }
            });
        `;
        (document.head || document.documentElement).appendChild(s);
        s.remove();
    })();

    /** Return auth credentials, or null if the user is not yet logged in. */
    function getAuth() {
        const a = _pw._gpAuth;
        const token   = (a && a.token)  || localStorage.getItem('tokenUser');
        const userId  = (a && a.userId != null) ? a.userId : parseInt(localStorage.getItem('userID') || '', 10);
        const subject = (a && a.subject) || '';
        if (!token || isNaN(userId)) return null;
        return { token, userId, subject };
    }

    // ─── Color sanitization ───────────────────────────────────────────────────────

    /**
     * Normalise a single raw token to an uppercase "#RRGGBB" string.
     * Returns null for anything that cannot be interpreted as a valid RGB colour.
     *
     * Accepted formats:
     *   - Hex with hash:    #FF0000
     *   - Hex without hash: FF0000
     *   - 3-digit shorthand:#F00 / F00
     *   - Decimal integer:  16711680
     */
    function sanitizeToken(token) {
        // Strip surrounding quotes that may appear in copy-pasted strings
        token = (token || '').trim().replace(/^["'`]+|["'`]+$/g, '').trim();
        if (!token) return null;

        // Pure decimal integer (digits only, no a-f)
        if (/^\d+$/.test(token)) {
            const n = parseInt(token, 10);
            if (n < 0 || n > 0xFFFFFF) return null;
            return '#' + n.toString(16).toUpperCase().padStart(6, '0');
        }

        const stripped = token.replace(/^#/, '');

        // 6-digit hex
        if (/^[0-9A-Fa-f]{6}$/.test(stripped)) {
            return '#' + stripped.toUpperCase();
        }

        // 3-digit shorthand → expand to 6-digit
        if (/^[0-9A-Fa-f]{3}$/.test(stripped)) {
            const expanded = stripped.split('').map(c => c + c).join('');
            return '#' + expanded.toUpperCase();
        }

        return null;
    }

    /**
     * Split raw textarea input (comma-, space-, or newline-separated) into
     * sanitized, deduplicated colour strings. Returns { valid, invalid }.
     */
    function parseColorInput(raw) {
        const tokens = (raw || '').split(/[\s,\n]+/).filter(Boolean);
        const seen = new Set();
        const valid = [];
        const invalid = [];

        for (const t of tokens) {
            const c = sanitizeToken(t);
            if (c) {
                if (!seen.has(c)) { seen.add(c); valid.push(c); }
            } else {
                invalid.push(t);
            }
        }

        return { valid, invalid };
    }

    // ─── Owned-colour helpers ─────────────────────────────────────────────────────

    /**
     * Build a Set of uppercase "#RRGGBB" hex strings from window.Colors,
     * which the site keeps in sync with the authenticated user's colour list.
     */
    function buildOwnedSet() {
        const set = new Set();
        // `Colors` is a top-level `let` in the page script. With @grant none it is
        // accessible as a bare name in most environments; use try/catch as guard.
        let colors;
        try { colors = Colors; } catch (_) { colors = window.Colors; }
        if (Array.isArray(colors)) {
            colors.forEach(h => {
                if (h && typeof h === 'string') set.add(h.toUpperCase());
            });
        }
        return set;
    }

    /**
     * Fetch a fresh copy of the user's data from the server and return a Set
     * of owned hex strings. Falls back to window.Colors on any error.
     */
    async function fetchOwnedHexSet() {
        const auth = getAuth();
        if (!auth) {
            console.warn('[BulkPurchase] Credentials not yet captured, falling back to local Colors.');
            return buildOwnedSet();
        }
        try {
            const resp = await window.fetch('/GetUserData', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ userId: auth.userId, token: auth.token }),
            });
            if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
            const data = await resp.json();

            const set = new Set();
            const raw = data.colors;

            // Server returns a comma-separated decimal string, e.g. "16777215, 0, 65280"
            if (typeof raw === 'string') {
                raw.split(',').forEach(s => {
                    const n = parseInt(s.trim(), 10);
                    // n >= 0 deliberately includes 0 (== #000000)
                    if (!isNaN(n) && n >= 0 && n <= 0xFFFFFF) {
                        set.add('#' + n.toString(16).toUpperCase().padStart(6, '0'));
                    }
                });
            } else if (Array.isArray(raw)) {
                raw.forEach(n => {
                    if (typeof n === 'number' && n >= 0 && n <= 0xFFFFFF) {
                        set.add('#' + n.toString(16).toUpperCase().padStart(6, '0'));
                    }
                });
            }

            // Merge local Colors as belt-and-suspenders
            buildOwnedSet().forEach(h => set.add(h));
            return set;
        } catch (err) {
            console.warn('[BulkPurchase] GetUserData failed, falling back to local Colors:', err);
            return buildOwnedSet();
        }
    }

    // ─── Local hex → integer helper ───────────────────────────────────────────────

    /** Convert "#RRGGBB" to its integer equivalent. */
    function hexToInt(hex) {
        return parseInt(hex.replace(/^#/, ''), 16);
    }

    // ─── Ghost palette DOM reader ─────────────────────────────────────────────────

    /**
     * Extract unique hex colours from the rendered #ghostColorPalette buttons.
     *
     * Two strategies, tried in order per-button:
     *   1. data-color-rgba="rgba(R,G,B,1)"  — set by ghost22.js, always present
     *   2. title first-line                 — also set by ghost22.js, may vary in format
     *
     * Using `data-color-rgba` as primary avoids any dependency on the title format,
     * which has changed across script versions (2-line, 3-line, etc.).
     */
    function getGhostColorsFromDOM() {
        const swatches = document.querySelectorAll('#ghostColorPalette button[data-color-rgba], #ghostColorPalette button[title]');
        const seen = new Set();
        const colors = []; // { hex, count }

        swatches.forEach(btn => {
            let hex = null;
            let count = 0;

            // Strategy 1: parse data-color-rgba="rgba(R,G,B,1)"
            const rgba = btn.dataset.colorRgba;
            if (rgba) {
                const m = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
                if (m) {
                    hex = '#' +
                        parseInt(m[1]).toString(16).toUpperCase().padStart(2, '0') +
                        parseInt(m[2]).toString(16).toUpperCase().padStart(2, '0') +
                        parseInt(m[3]).toString(16).toUpperCase().padStart(2, '0');
                }
            }

            // Strategy 2: title first line (e.g. "#D5BFB2" or "#D5BFB2\n42 pixels")
            if (!hex && btn.title) {
                const candidate = btn.title.split(/[\r\n]+/)[0].trim().toUpperCase();
                if (/^#[0-9A-F]{6}$/.test(candidate)) hex = candidate;
            }

            // Parse pixel count from title second line (e.g. "42 pixels")
            if (btn.title) {
                const lines = btn.title.split(/[\r\n]+/);
                for (let i = 1; i < lines.length; i++) {
                    const cm = lines[i].match(/(\d+)\s*pixel/i);
                    if (cm) { count = parseInt(cm[1], 10); break; }
                }
            }

            if (hex && /^#[0-9A-F]{6}$/.test(hex) && !seen.has(hex)) {
                seen.add(hex);
                colors.push({ hex, count });
            }
        });

        // Sort by pixel count descending (most used first)
        colors.sort((a, b) => b.count - a.count);

        return colors.map(c => c.hex);
    }

    /** Returns a Map of hex → pixel count from the ghost template palette DOM. */
    function getGhostPixelCounts() {
        const swatches = document.querySelectorAll('#ghostColorPalette button[data-color-rgba], #ghostColorPalette button[title]');
        const counts = new Map();

        swatches.forEach(btn => {
            let hex = null;
            let count = 0;

            const rgba = btn.dataset.colorRgba;
            if (rgba) {
                const m = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
                if (m) {
                    hex = '#' +
                        parseInt(m[1]).toString(16).toUpperCase().padStart(2, '0') +
                        parseInt(m[2]).toString(16).toUpperCase().padStart(2, '0') +
                        parseInt(m[3]).toString(16).toUpperCase().padStart(2, '0');
                }
            }

            if (!hex && btn.title) {
                const candidate = btn.title.split(/[\r\n]+/)[0].trim().toUpperCase();
                if (/^#[0-9A-F]{6}$/.test(candidate)) hex = candidate;
            }

            if (btn.title) {
                const lines = btn.title.split(/[\r\n]+/);
                for (let i = 1; i < lines.length; i++) {
                    const cm = lines[i].match(/(\d+)\s*pixel/i);
                    if (cm) { count = parseInt(cm[1], 10); break; }
                }
            }

            if (hex && /^#[0-9A-F]{6}$/.test(hex)) {
                counts.set(hex, count);
            }
        });

        return counts;
    }

    // ─── Confirmation / preview modal ─────────────────────────────────────────────

    /** The single shared overlay element (created once and reused). */
    let _bulkOverlay = null;
    /** Original ordered list passed to openBulkModal (preserved for results display). */
    let _pendingColors = [];

    /** Per-status visual style config. */
    function getStatusStyles() {
        const dark = isDarkMode();
        return {
            pending:   { label: '',                              bg: dark ? '#101828' : '#f9fafb', border: dark ? '#364153' : '#e5e7eb', textColor: dark ? '#6a7282' : '#9ca3af' },
            owned:     { label: 'Already Owned',                 bg: dark ? '#422006' : '#fefce8', border: dark ? '#a16207' : '#fde047', textColor: dark ? '#fbbf24' : '#92400e' },
            purchased: { label: 'Purchased ✓',                  bg: dark ? '#052e16' : '#f0fdf4', border: dark ? '#16a34a' : '#86efac', textColor: dark ? '#4ade80' : '#166534' },
            failed:    { label: 'Failed',                        bg: dark ? '#450a0a' : '#fef2f2', border: dark ? '#dc2626' : '#fca5a5', textColor: dark ? '#f87171' : '#991b1b' },
            skipped:   { label: 'Skipped (Insufficient Pixels)', bg: dark ? '#0f172a' : '#f1f5f9', border: dark ? '#475569' : '#cbd5e1', textColor: dark ? '#94a3b8' : '#64748b' },
        };
    }

    function buildColorRow(hex, status, ghostPixelCount) {
        const STATUS_STYLES = getStatusStyles();
        const s = STATUS_STYLES[status] || STATUS_STYLES.pending;
        const c = t();
        const row = document.createElement('div');
        row.dataset.gpColor = hex;
        row.style.cssText = `display:flex;align-items:center;gap:0.75rem;padding:0.5rem 0.75rem;` +
            `background:${s.bg};border:1px solid ${s.border};border-radius:0.5rem;`;

        const swatch = document.createElement('div');
        swatch.style.cssText = `width:1.75rem;height:1.75rem;border-radius:0.25rem;border:1px solid ${c.inputBorder};flex-shrink:0;background:${hex};`;

        const hexLabel = document.createElement('span');
        hexLabel.style.cssText = `font-family:monospace;font-size:0.875rem;color:${c.textMed};flex:1;`;
        hexLabel.textContent = hex;

        // Ghost template pixel count label
        const ghostLabel = document.createElement('span');
        ghostLabel.className = 'gp-row-ghost';
        if (ghostPixelCount != null) {
            ghostLabel.style.cssText = `font-size:0.7rem;font-weight:500;color:${ghostPixelCount > 0 ? (isDarkMode() ? '#a78bfa' : '#7c3aed') : (isDarkMode() ? '#6b7280' : '#9ca3af')};white-space:nowrap;`;
            ghostLabel.textContent = ghostPixelCount > 0 ? `${ghostPixelCount} px` : 'unused';
        }

        const badge = document.createElement('span');
        badge.className = 'gp-row-badge';
        badge.style.cssText = `font-size:0.7rem;font-weight:600;color:${s.textColor};white-space:nowrap;`;
        badge.textContent = status === 'pending' ? `${PIXELS_PER_COLOR} px` : s.label;

        row.appendChild(swatch);
        row.appendChild(hexLabel);
        if (ghostPixelCount != null) row.appendChild(ghostLabel);
        row.appendChild(badge);
        return row;
    }

    function updateColorRow(hex, status) {
        // Scoped to the modal list only — queue rows use data-gp-queue-color
        const list = document.getElementById('gp-bulk-list');
        if (!list) return;
        const row = list.querySelector(`[data-gp-color="${hex}"]`);
        if (!row) return;
        const STATUS_STYLES = getStatusStyles();
        const s = STATUS_STYLES[status] || STATUS_STYLES.pending;
        row.style.background = s.bg;
        row.style.borderColor = s.border;
        const badge = row.querySelector('.gp-row-badge');
        if (badge) { badge.textContent = s.label; badge.style.color = s.textColor; }
    }

    function ensureBulkModal() {
        if (_bulkOverlay) return;

        const c = t();

        _bulkOverlay = document.createElement('div');
        _bulkOverlay.id = 'gp-bulk-overlay';
        // Overlay sits above everything — including z-50 profile panel and z-40 ghost modal
        _bulkOverlay.style.cssText =
            'position:fixed;inset:0;z-index:10000;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,0.55);';

        _bulkOverlay.innerHTML = `
<div id="gp-bulk-panel"
     style="background:${c.panelBg};color:${c.text};border-radius:1rem;box-shadow:0 20px 60px rgba(0,0,0,0.3);width:90%;max-width:28rem;max-height:85vh;display:flex;flex-direction:column;padding:1.5rem;gap:1rem;overflow:hidden;">

    <!-- Header -->
    <div style="display:flex;align-items:center;justify-content:space-between;flex-shrink:0;">
        <h2 id="gp-bulk-title" style="margin:0;font-size:1.25rem;font-weight:700;color:${c.text};">Bulk Purchase Preview</h2>
        <button id="gp-bulk-close"
                style="width:2rem;height:2rem;border-radius:50%;border:none;background:${c.closeBg};cursor:pointer;font-size:1rem;display:flex;align-items:center;justify-content:center;color:${c.closeText};"
                title="Close">\u2715</button>
    </div>

    <!-- Subtitle -->
    <p id="gp-bulk-subtitle" style="margin:0;font-size:0.85rem;color:${c.textSec};flex-shrink:0;"></p>

    <!-- Colour list (all colors in original order, owned grayed out) -->
    <div id="gp-bulk-list"
         style="flex:1 1 auto;overflow-y:auto;display:flex;flex-direction:column;gap:0.5rem;padding-right:0.25rem;min-height:0;"></div>

    <!-- Progress bar (shown during purchase) -->
    <div id="gp-bulk-progress-wrap"
         style="flex-shrink:0;display:none;">
        <div style="width:100%;height:0.75rem;background:${c.progressBg};border-radius:9999px;overflow:hidden;">
            <div id="gp-bulk-progress-bar"
                 style="height:100%;width:0%;background:#3b82f6;border-radius:9999px;transition:width 0.2s ease;"></div>
        </div>
        <p id="gp-bulk-progress-text"
           style="margin:0.25rem 0 0;font-size:0.75rem;color:${c.textSec};text-align:center;"></p>
    </div>

    <!-- Action buttons -->
    <div style="display:flex;gap:0.75rem;flex-shrink:0;">
        <button id="gp-bulk-cancel"
                style="flex:1;padding:0.5rem 1rem;background:${c.cancelBg};border:none;border-radius:0.5rem;font-weight:600;cursor:pointer;font-size:0.9rem;color:${c.cancelText};">
            Cancel
        </button>
        <button id="gp-bulk-confirm"
                style="flex:1;padding:0.5rem 1rem;background:#3b82f6;color:#fff;border:none;border-radius:0.5rem;font-weight:600;cursor:pointer;font-size:0.9rem;">
            Purchase All
        </button>
    </div>
</div>`;

        document.body.appendChild(_bulkOverlay);

        document.getElementById('gp-bulk-close').addEventListener('click', closeBulkModal);
        document.getElementById('gp-bulk-cancel').addEventListener('click', closeBulkModal);
        _bulkOverlay.addEventListener('click', e => { if (e.target === _bulkOverlay) closeBulkModal(); });
        document.getElementById('gp-bulk-confirm').addEventListener('click', onBulkConfirm);
    }

    /**
     * Open the preview modal for a given list of "#RRGGBB" hex strings.
     * All colors are shown in original order; already-owned ones get an
     * "Already Owned" badge and are non-destructively skipped on confirm.
     */
    function openBulkModal(colors) {
        ensureBulkModal();

        _pendingColors = colors;

        const ownedSet = buildOwnedSet();
        const toBuyCount  = colors.filter(c => !ownedSet.has(c)).length;
        const ownedCount  = colors.length - toBuyCount;

        // Grab ghost template pixel counts if a template is loaded
        const ghostCounts = getGhostPixelCounts();
        const hasGhost = ghostCounts.size > 0;

        // --- Populate list (original order, owned shown in-place) ---
        const list = document.getElementById('gp-bulk-list');
        list.innerHTML = '';
        colors.forEach(hex => {
            const gpc = hasGhost ? (ghostCounts.get(hex) ?? 0) : undefined;
            list.appendChild(buildColorRow(hex, ownedSet.has(hex) ? 'owned' : 'pending', hasGhost ? gpc : undefined));
        });

        // --- Header / subtitle ---
        document.getElementById('gp-bulk-title').textContent = 'Bulk Purchase Preview';
        const parts = [`${toBuyCount} to purchase · est. ${(toBuyCount * PIXELS_PER_COLOR).toLocaleString()} Pixels`];
        if (ownedCount > 0) parts.push(`${ownedCount} already owned (will skip)`);
        document.getElementById('gp-bulk-subtitle').textContent = parts.join(' · ');

        // --- Ghost template legend ---
        let legend = document.getElementById('gp-bulk-ghost-legend');
        if (legend) legend.remove();
        if (hasGhost) {
            legend = document.createElement('p');
            legend.id = 'gp-bulk-ghost-legend';
            const purpleColor = isDarkMode() ? '#a78bfa' : '#7c3aed';
            legend.style.cssText = `margin:0;font-size:0.75rem;color:${isDarkMode() ? '#9ca3af' : '#6b7280'};flex-shrink:0;`;
            legend.innerHTML = `<span style="color:${purpleColor};font-weight:600;">Purple</span> = pixels used in the loaded ghost template`;
            const subtitle = document.getElementById('gp-bulk-subtitle');
            subtitle.insertAdjacentElement('afterend', legend);
        }

        // --- Reset progress bar ---
        document.getElementById('gp-bulk-progress-wrap').style.display = 'none';
        document.getElementById('gp-bulk-progress-bar').style.width = '0%';
        document.getElementById('gp-bulk-progress-text').textContent = '';

        // --- Reset action buttons ---
        const confirmBtn = document.getElementById('gp-bulk-confirm');
        const cancelBtn  = document.getElementById('gp-bulk-cancel');
        confirmBtn.style.display = '';
        confirmBtn.textContent = 'Purchase All';
        confirmBtn.disabled = toBuyCount === 0;
        confirmBtn.style.opacity = toBuyCount === 0 ? '0.5' : '1';
        confirmBtn.style.cursor  = toBuyCount === 0 ? 'not-allowed' : 'pointer';
        cancelBtn.textContent = 'Cancel';
        cancelBtn.disabled = false;

        _bulkOverlay.style.display = 'flex';
    }

    function closeBulkModal() {
        if (_bulkOverlay) _bulkOverlay.style.display = 'none';
        _pendingColors = [];
        // Sync the profile card queue now that the modal is gone
        if (document.getElementById('gp-bulk-queue-list')) refreshColorQueue();
    }

    async function onBulkConfirm() {
        const confirmBtn = document.getElementById('gp-bulk-confirm');
        const cancelBtn  = document.getElementById('gp-bulk-cancel');
        const closeBtn   = document.getElementById('gp-bulk-close');

        // Lock UI during purchase
        confirmBtn.disabled = true;
        confirmBtn.style.opacity = '0.5';
        confirmBtn.style.cursor = 'not-allowed';
        cancelBtn.disabled = true;
        closeBtn.disabled = true;

        const colors = [..._pendingColors];
        const results = await executeBulkPurchase(colors);

        // Silently strip purchased colors from textarea (queue refreshes when modal closes)
        const textarea = document.getElementById('gp-bulk-textarea');
        if (textarea) {
            const { valid } = parseColorInput(textarea.value);
            const purchasedSet = new Set(colors.filter(h => results.get(h) === 'purchased'));
            const remaining = valid.filter(c => !purchasedSet.has(c));
            textarea.value = remaining.length ? remaining.join(', ') : '';
        }

        // Switch to results view
        showBulkResults(colors, results);

        closeBtn.disabled = false;
        cancelBtn.disabled = false;
        cancelBtn.textContent = 'Close';
    }

    // ─── Purchase logic ───────────────────────────────────────────────────────────

    /**
     * Attempt to purchase each non-owned color in order.
     * On HTTP 402 (insufficient pixels), stops immediately and marks the current
     * color plus all remaining unattempted colors as 'skipped'.
     * Returns a Map<hex, 'owned'|'purchased'|'failed'|'skipped'>.
     */
    async function executeBulkPurchase(colors) {
        const progressWrap = document.getElementById('gp-bulk-progress-wrap');
        const progressBar  = document.getElementById('gp-bulk-progress-bar');
        const progressText = document.getElementById('gp-bulk-progress-text');

        progressWrap.style.display = 'block';

        const auth = getAuth();
        if (!auth) {
            progressText.textContent = 'Error: credentials not captured yet.';
            if (_pw.showAlert) _pw.showAlert('Error', 'Credentials not ready. Place a pixel first to initialise auth, then retry.');
            return new Map();
        }

        const ownedSet = buildOwnedSet();
        const results  = new Map();
        colors.forEach(hex => { if (ownedSet.has(hex)) results.set(hex, 'owned'); });

        const toPurchase = colors.filter(hex => !ownedSet.has(hex));
        let stoppedAt = -1;

        for (let i = 0; i < toPurchase.length; i++) {
            const hex = toPurchase[i];

            const pct = Math.round((i / toPurchase.length) * 100);
            progressBar.style.width = pct + '%';
            progressText.textContent = `Purchasing ${i + 1} of ${toPurchase.length}: ${hex}`;

            try {
                const resp = await window.fetch('/MakePurchase', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        Token:   auth.token,
                        UserId:  auth.userId,
                        Subject: auth.subject,
                        type:    'ExtraColor',
                        amount:  hexToInt(hex),
                    }),
                });

                if (resp.status === 200) {
                    results.set(hex, 'purchased');
                    updateColorRow(hex, 'purchased');
                } else if (resp.status === 402) {
                    // Insufficient pixels — stop the loop here
                    results.set(hex, 'skipped');
                    updateColorRow(hex, 'skipped');
                    stoppedAt = i;
                    break;
                } else {
                    results.set(hex, 'failed');
                    updateColorRow(hex, 'failed');
                    console.warn(`[BulkPurchase] Failed ${hex}: HTTP ${resp.status}`);
                }
            } catch (err) {
                results.set(hex, 'failed');
                updateColorRow(hex, 'failed');
                console.error('[BulkPurchase] Error purchasing', hex, err);
            }
        }

        // Mark everything that was never attempted (after the 402) as skipped
        if (stoppedAt >= 0) {
            for (let j = stoppedAt + 1; j < toPurchase.length; j++) {
                const hex = toPurchase[j];
                results.set(hex, 'skipped');
                updateColorRow(hex, 'skipped');
            }
        }

        progressBar.style.width = '100%';

        // Auto-enable newly purchased colors in the page's active palette,
        // mirroring what the native single-color purchase does.
        const successCount = toPurchase.filter(hex => results.get(hex) === 'purchased').length;
        if (successCount > 0) {
            try {
                let colorsLen;
                try { colorsLen = Colors.length; } catch (_) { colorsLen = (_pw.Colors || []).length; }
                const s = document.createElement('script');
                s.textContent = `(function(){try{for(var i=${colorsLen};i<${colorsLen + successCount};i++){if(!activeColors.includes(i))activeColors.push(i);}localStorage.setItem('activeColors',JSON.stringify(activeColors));}catch(e){}})();`;
                (document.head || document.documentElement).appendChild(s);
                s.remove();
            } catch (e) {
                console.warn('[BulkPurchase] Could not auto-enable purchased colors:', e);
            }
        }

        if (typeof window.synchronize === 'function') window.synchronize();

        return results;
    }

    /**
     * Switch the open modal into a results view.
     * The colour rows are already updated in real-time; this just updates the
     * title/subtitle and hides the confirm button.
     */
    function showBulkResults(original, results) {
        const purchased = original.filter(h => results.get(h) === 'purchased').length;
        const owned     = original.filter(h => results.get(h) === 'owned').length;
        const failed    = original.filter(h => results.get(h) === 'failed').length;
        const skipped   = original.filter(h => results.get(h) === 'skipped').length;

        document.getElementById('gp-bulk-title').textContent = 'Purchase Complete';

        const parts = [];
        if (purchased > 0) parts.push(`${purchased} purchased`);
        if (owned     > 0) parts.push(`${owned} already owned`);
        if (skipped   > 0) parts.push(`${skipped} skipped — insufficient Pixels`);
        if (failed    > 0) parts.push(`${failed} failed`);
        document.getElementById('gp-bulk-subtitle').textContent = parts.join(' · ');

        document.getElementById('gp-bulk-confirm').style.display = 'none';
    }

    // ─── Profile card queue helpers ───────────────────────────────────────────────

    /** Remove a single hex value from the textarea and fire 'input' to refresh the queue. */
    function removeColorFromTextarea(hex) {
        const textarea = document.getElementById('gp-bulk-textarea');
        if (!textarea) return;
        const { valid } = parseColorInput(textarea.value);
        const remaining = valid.filter(c => c !== hex);
        textarea.value = remaining.length ? remaining.join(', ') : '';
        textarea.dispatchEvent(new Event('input'));
    }

    /**
     * Re-render the right-side queue list from the current textarea contents.
     * Unowned colors come first (with Buy buttons); owned are grayed at the bottom.
     */
    function refreshColorQueue() {
        const textarea  = document.getElementById('gp-bulk-textarea');
        const list      = document.getElementById('gp-bulk-queue-list');
        const emptyHint = document.getElementById('gp-bulk-empty-hint');
        const buyAllBtn = document.getElementById('gp-bulk-buy-all-btn');
        const infoEl    = document.getElementById('gp-bulk-parse-info');
        if (!textarea || !list) return;

        const { valid, invalid } = parseColorInput(textarea.value);
        const ownedSet = buildOwnedSet();
        const unowned  = valid.filter(c => !ownedSet.has(c));
        const owned    = valid.filter(c =>  ownedSet.has(c));

        // Parse-info label (below textarea)
        if (infoEl) {
            if (!textarea.value.trim()) {
                infoEl.textContent = '';
            } else {
                const parts = [`${unowned.length} to purchase`];
                if (owned.length   > 0) parts.push(`${owned.length} already owned`);
                if (invalid.length > 0) parts.push(`${invalid.length} unrecognised`);
                infoEl.textContent = parts.join(' · ');
            }
        }

        // Buy All button state
        if (buyAllBtn) {
            const n = unowned.length;
            buyAllBtn.textContent = `🛒 Buy All (${n})`;
            buyAllBtn.disabled       = n === 0;
            buyAllBtn.style.opacity  = n === 0 ? '0.5' : '1';
            buyAllBtn.style.cursor   = n === 0 ? 'not-allowed' : 'pointer';
        }

        // Rebuild list
        list.innerHTML = '';
        if (valid.length === 0) {
            if (emptyHint) emptyHint.style.display = 'block';
            return;
        }
        if (emptyHint) emptyHint.style.display = 'none';

        // Grab ghost pixel counts for queue display
        const ghostCounts = getGhostPixelCounts();
        const hasGhost = ghostCounts.size > 0;

        unowned.forEach(hex => list.appendChild(buildQueueRow(hex, false, hasGhost ? (ghostCounts.get(hex) ?? 0) : undefined)));

        if (owned.length > 0) {
            const sep = document.createElement('div');
            const c = t();
            sep.style.cssText =
                `font-size:0.6rem;color:${c.textMuted};text-align:center;padding:0.2rem 0;` +
                `border-top:1px solid ${c.border};border-bottom:1px solid ${c.border};` +
                `background:${c.sepBg};letter-spacing:0.05em;user-select:none;`;
            sep.textContent = '── Already Owned ──';
            list.appendChild(sep);
            owned.forEach(hex => list.appendChild(buildQueueRow(hex, true, hasGhost ? (ghostCounts.get(hex) ?? 0) : undefined)));
        }
    }

    /** Build a single color row for the profile queue. */
    function buildQueueRow(hex, isOwned, ghostPixelCount) {
        const c = t();
        const row = document.createElement('div');
        row.dataset.gpQueueColor = hex;
        // Fixed height + no gap = button stays in same screen position as rows are removed
        row.style.cssText =
            'display:flex;align-items:center;height:1.625rem;padding:0 0.35rem;' +
            `background:${isOwned ? c.rowOwnedBg : c.rowBg};` +
            `border-bottom:1px solid ${isOwned ? c.border : c.borderLight};` +
            `${isOwned ? 'opacity:0.45;' : ''}`;

        const swatch = document.createElement('div');
        swatch.style.cssText =
            `width:0.875rem;height:0.875rem;border-radius:2px;flex-shrink:0;` +
            `background:${hex};border:1px solid rgba(0,0,0,0.12);margin-right:0.35rem;`;

        const label = document.createElement('span');
        label.style.cssText =
            `font-family:monospace;font-size:0.68rem;flex:1;overflow:hidden;` +
            `color:${isOwned ? c.textOwned : c.textMed};letter-spacing:-0.01em;`;
        label.textContent = hex;

        row.appendChild(swatch);
        row.appendChild(label);

        // Ghost template pixel count
        if (ghostPixelCount != null) {
            const ghostLabel = document.createElement('span');
            const purpleColor = isDarkMode() ? '#a78bfa' : '#7c3aed';
            const grayColor = isDarkMode() ? '#6b7280' : '#9ca3af';
            ghostLabel.style.cssText =
                `font-size:0.6rem;font-weight:500;white-space:nowrap;flex-shrink:0;padding:0 0.25rem;` +
                `color:${ghostPixelCount > 0 ? purpleColor : grayColor};`;
            ghostLabel.textContent = ghostPixelCount > 0 ? `${ghostPixelCount}px` : '—';
            row.appendChild(ghostLabel);
        }

        if (isOwned) {
            const badge = document.createElement('span');
            badge.style.cssText =
                `font-size:0.6rem;color:${c.textOwned};white-space:nowrap;flex-shrink:0;padding-left:0.25rem;`;
            badge.textContent = 'owned';
            row.appendChild(badge);
        } else {
            const btn = document.createElement('button');
            // Fixed width so the button is always at the same X — critical for spam-clicking
            btn.style.cssText =
                'width:2.25rem;height:1.25rem;flex-shrink:0;background:#3b82f6;color:#fff;border:none;' +
                'border-radius:3px;font-size:0.65rem;font-weight:700;cursor:pointer;' +
                'display:flex;align-items:center;justify-content:center;letter-spacing:0.02em;';
            btn.textContent = 'BUY';
            btn.addEventListener('mouseover', () => { if (!btn.disabled) btn.style.background = '#2563eb'; });
            btn.addEventListener('mouseout',  () => { if (!btn.disabled) btn.style.background = '#3b82f6'; });
            btn.addEventListener('click', () => buyIndividualColor(hex, btn));
            row.appendChild(btn);
        }

        return row;
    }

    /** Purchase one color immediately; on success remove it from the textarea and queue. */
    async function buyIndividualColor(hex, btn) {
        btn.disabled      = true;
        btn.style.opacity = '0.5';
        btn.style.cursor  = 'not-allowed';
        btn.textContent   = '…';

        const auth = getAuth();
        if (!auth) {
            if (_pw.showAlert) _pw.showAlert('Error', 'Not ready yet — credentials not captured. Try placing a pixel first, then retry.');
            btn.disabled = false; btn.style.opacity = '1';
            btn.style.cursor = 'pointer'; btn.textContent = 'Buy';
            return;
        }

        try {
            const resp = await window.fetch('/MakePurchase', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    Token:   auth.token,
                    UserId:  auth.userId,
                    Subject: auth.subject,
                    type:    'ExtraColor',
                    amount:  hexToInt(hex),
                }),
            });

            if (resp.status === 200) {
                // Remove from textarea → fires 'input' → refreshColorQueue removes the row
                removeColorFromTextarea(hex);
                if (typeof window.synchronize === 'function') window.synchronize();
                if (_pw.showAlert) _pw.showAlert('Success', `${hex} purchased successfully!`);
            } else if (resp.status === 402) {
                if (_pw.showAlert) _pw.showAlert('Error', 'Insufficient Pixels to purchase this color.');
                btn.disabled = false; btn.style.opacity = '1';
                btn.style.cursor = 'pointer'; btn.textContent = 'Buy';
            } else {
                const text = await resp.text().catch(() => '');
                if (_pw.showAlert) _pw.showAlert('Error', `Failed to purchase ${hex}. ${text}`.trim());
                btn.disabled = false; btn.style.opacity = '1';
                btn.style.cursor = 'pointer'; btn.textContent = 'Buy';
            }
        } catch (err) {
            console.error('[BulkPurchase] Network error:', err);
            if (_pw.showAlert) _pw.showAlert('Error', 'Network error during purchase.');
            btn.disabled = false; btn.style.opacity = '1';
            btn.style.cursor = 'pointer'; btn.textContent = 'Buy';
        }
    }

    // ─── Profile panel injection ──────────────────────────────────────────────────

    function injectProfileSection() {
        if (document.getElementById('gp-bulk-profile-card')) return;

        // Locate "Unlock Extra Color" card by its unique child element
        const freeColorNotice = document.getElementById('freeColorNotice');
        if (!freeColorNotice) return;

        // Walk up to the card wrapper (p-4 bg-gray-100 rounded-xl shadow)
        const extraColorCard = freeColorNotice.closest('div[class*="p-4"]');
        if (!extraColorCard) return;

        // The grid that contains all upgrade cards
        const grid = extraColorCard.parentElement;
        if (!grid) return;

        const c = t();

        const card = document.createElement('div');
        card.id = 'gp-bulk-profile-card';
        card.className = 'p-4 bg-gray-100 rounded-xl shadow flex flex-col gap-3';
        card.style.gridColumn = '1 / -1';

        card.innerHTML = `
<div style="font-weight:600;font-size:1rem;color:${c.text};margin-bottom:0.125rem;">Bulk Purchase Colors</div>
<div style="display:flex;gap:1rem;align-items:flex-start;flex-wrap:wrap;">

    <!-- Left: textarea input -->
    <div style="flex:1;min-width:11rem;display:flex;flex-direction:column;gap:0.5rem;">
        <div class="text-sm text-gray-500">Comma, space, or newline &mdash; hex or decimal</div>
        <textarea id="gp-bulk-textarea"
                  rows="6"
                  placeholder="#FF0000, #00FF00&#10;FF0000 00FF00&#10;16711680"
                  class="w-full border rounded-lg px-3 py-2 text-sm font-mono resize-y"
                  style="outline:none;transition:box-shadow 0.15s;"
                  onfocus="this.style.boxShadow='0 0 0 2px #3b82f6'"
                  onblur="this.style.boxShadow='none'"
        ></textarea>
        <p id="gp-bulk-parse-info" style="margin:0;font-size:0.75rem;color:${c.textMuted};min-height:1rem;"></p>
    </div>

    <!-- Right: live color queue -->
    <div style="flex:1;min-width:12rem;display:flex;flex-direction:column;gap:0.5rem;">
        <button id="gp-bulk-buy-all-btn"
                style="width:100%;padding:0.5rem 0.75rem;background:#3b82f6;color:#fff;
                       border:none;border-radius:0.5rem;font-weight:600;font-size:0.875rem;
                       cursor:not-allowed;opacity:0.5;text-align:center;"
                disabled>
            &#x1F6D2; Buy All (0)
        </button>
        <div id="gp-bulk-queue-list"
             style="max-height:260px;overflow-y:auto;display:flex;flex-direction:column;
                    border:1px solid ${c.queueBorder};border-radius:0.375rem;overflow-x:hidden;
                    background:${c.queueBg};"></div>
        <p id="gp-bulk-empty-hint"
           style="margin:0;font-size:0.75rem;color:${c.textMuted};text-align:center;">Enter colors on the left</p>
    </div>

</div>`;

        grid.appendChild(card);

        const textarea = document.getElementById('gp-bulk-textarea');
        textarea.addEventListener('input', refreshColorQueue);

        document.getElementById('gp-bulk-buy-all-btn').addEventListener('click', () => {
            const { valid } = parseColorInput(textarea.value);
            if (valid.length === 0) return;
            openBulkModal(valid);
        });

        // "Add Ghost Template Colors" button — fetches colors from the active ghost image palette
        const ghostFetchBtn = document.createElement('button');
        ghostFetchBtn.id = 'gp-bulk-ghost-fetch-btn';
        ghostFetchBtn.className = 'px-3 py-2 text-white text-sm rounded-lg shadow transition cursor-pointer';
        ghostFetchBtn.style.background = '#7c3aed';
        ghostFetchBtn.style.border = 'none';
        ghostFetchBtn.style.fontWeight = '600';
        ghostFetchBtn.textContent = '👻 Add Ghost Template Colors';
        ghostFetchBtn.title = 'Fetch colors from the current ghost template and populate the text field';
        ghostFetchBtn.addEventListener('mouseover', () => { ghostFetchBtn.style.background = '#6d28d9'; });
        ghostFetchBtn.addEventListener('mouseout',  () => { ghostFetchBtn.style.background = '#7c3aed'; });
        ghostFetchBtn.addEventListener('click', () => {
            const ghostColors = getGhostColorsFromDOM();
            if (ghostColors.length === 0) {
                alert('No ghost palette colors found. Make sure a ghost image is loaded.');
                return;
            }
            const existing = textarea.value.trim();
            textarea.value = existing
                ? existing + ', ' + ghostColors.join(', ')
                : ghostColors.join(', ');
            textarea.dispatchEvent(new Event('input'));
        });

        // Insert below the parse info line, inside the left column
        const parseInfo = document.getElementById('gp-bulk-parse-info');
        if (parseInfo && parseInfo.parentElement) {
            parseInfo.parentElement.appendChild(ghostFetchBtn);
        }

        refreshColorQueue();

        // Watch ghost palette for changes (load/unload template) to refresh queue ghost counts
        const ghostPalette = document.getElementById('ghostColorPalette');
        if (ghostPalette) {
            new MutationObserver(() => {
                if (document.getElementById('gp-bulk-queue-list')) refreshColorQueue();
            }).observe(ghostPalette, { childList: true });
        }
    }

    // ─── Ghost modal injection ─────────────────────────────────────────────────────

    function injectGhostButton() {
        if (document.getElementById('gp-ghost-buy-btn')) return;

        // The "Match My Palette" button is a reliable anchor inside the ghost modal
        const anchorBtn = document.getElementById('filterByUserPaletteBtn');
        if (!anchorBtn) return;

        const btn = document.createElement('button');
        btn.id = 'gp-ghost-buy-btn';
        btn.className = 'px-3 py-2 text-white text-sm rounded-lg shadow transition cursor-pointer';
        btn.style.background = '#7c3aed';
        btn.title = "Find ghost-image colors you don't own yet and open the bulk-purchase flow";
        btn.textContent = 'Bulk Purchase Colors';

        btn.addEventListener('mouseover', () => { btn.style.background = '#6d28d9'; });
        btn.addEventListener('mouseout', () => { btn.style.background = '#7c3aed'; });
        btn.addEventListener('click', handlePurchaseUnowned);

        // Insert after the existing button row so it appears as a natural addition
        anchorBtn.parentElement.appendChild(btn);
    }

    async function handlePurchaseUnowned() {
        const btn = document.getElementById('gp-ghost-buy-btn');
        if (btn) {
            btn.disabled = true;
            btn.textContent = 'Checking…';
        }

        try {
            // Read directly from the rendered palette DOM — reliable source of truth.
            // ghost22.js sets swatch.title = `${colorData.hex}\n${totalCount} pixels`
            // so the first line before \n is always the canonical hex value.
            const ghostColors = getGhostColorsFromDOM();

            if (ghostColors.length === 0) {
                if (_pw.showAlert) {
                    _pw.showAlert('Info', 'No ghost palette colors found. Make sure a ghost image is loaded and its color palette is visible in the modal.');
                }
                return;
            }

            // Fetch fresh ownership data from the server
            const ownedSet = await fetchOwnedHexSet();
            const unowned = ghostColors.filter(h => !ownedSet.has(h));

            if (unowned.length === 0) {
                if (_pw.showAlert) {
                    _pw.showAlert('Info', 'You already own all colors used in this ghost image!');
                }
                return;
            }

            // Close the ghost modal
            const _pw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
            if (typeof _pw.toggleGhostModal === 'function') _pw.toggleGhostModal(false);

            // Open the profile panel if it is currently hidden
            const profileOverlay = document.getElementById('profileOverlay');
            if (profileOverlay && profileOverlay.classList.contains('hidden') &&
                    typeof _pw.toggleProfile === 'function') {
                _pw.toggleProfile();
            }

            // Give the profile panel time to animate in, then populate the textarea
            setTimeout(() => {
                injectProfileSection();

                const textarea = document.getElementById('gp-bulk-textarea');
                if (textarea) {
                    // Always wipe first so repeated presses don't accumulate stale colors
                    textarea.value = unowned.join(', ');
                    textarea.dispatchEvent(new Event('input'));
                    textarea.scrollIntoView({ behavior: 'smooth', block: 'center' });
                }

                // Flash the card with a yellow glow to draw the user's eye
                const card = document.getElementById('gp-bulk-profile-card');
                if (card) {
                    card.style.transition = 'box-shadow 0.15s ease';
                    card.style.boxShadow  = '0 0 0 3px #fbbf24, 0 0 18px 6px rgba(251,191,36,0.55)';
                    setTimeout(() => { card.style.boxShadow = 'none'; }, 900);
                }
            }, 300);

        } finally {
            if (btn) {
                btn.disabled = false;
                btn.textContent = 'Bulk Purchase Colors';
            }
        }
    }

    // ─── DOM observation and entry point ──────────────────────────────────────────

    /**
     * Watch the DOM for relevant containers and inject our UI whenever they appear.
     * Both the profile panel and the ghost modal already exist in the HTML on load,
     * but the observer also handles any future dynamic additions gracefully.
     */
    function observeAndInject() {
        // Try immediately (elements may already be in the DOM)
        injectProfileSection();
        injectGhostButton();

        // Re-check on every DOM change (guards against dynamic re-renders)
        const observer = new MutationObserver(() => {
            injectProfileSection();
            injectGhostButton();
        });

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

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

            })();
            _featureStatus.bulkPurchaseColors = 'ok';
            console.log('[GeoPixelcons++] ✅ Bulk Purchase Colors loaded');
        } catch (err) {
            _featureStatus.bulkPurchaseColors = 'error';
            dbgPush(`Bulk Purchase Colors init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Bulk Purchase Colors' });
            console.error('[GeoPixelcons++] ❌ Bulk Purchase Colors failed:', err);
        }
    }

    // ============================================================
    //  EXTENSION: Auto-open Menus on Hover [extAutoHoverMenus]
    // ============================================================
    if (_settings.extAutoHoverMenus) {
        try {
            (function _ext_autoHoverMenus() {

    const VERTICAL_ZONE_PX = 250;
    const PER_BUTTON_COOLDOWN_MS = 400;

    const buttonsState = new WeakMap();
    let trackedButtons = [];

    function isMenuOpen(info) {
        const { button, parent } = info;
        if (!button) return false;
        // aria-expanded is the most reliable cross-version signal
        if (button.getAttribute('aria-expanded') === 'true') return true;
        if (parent && parent.getAttribute('aria-expanded') === 'true') return true;
        // active class check
        if (button.classList.contains('active') || (parent && parent.classList.contains('active'))) return true;
        // Live dropdown lookup — re-query each check so dynamically-injected menus are found
        const dd = info.dropdown ||
            (parent ? (parent.querySelector('.dropdown-menu') ||
                       parent.querySelector('[role="menu"]') ||
                       null) : null);
        if (dd) {
            if (dd.offsetParent !== null) return true;
            if (dd.classList.contains('show') || dd.classList.contains('open')) return true;
        }
        // Fallback: any visible child list or [class*="dropdown"] sibling
        if (parent) {
            const candidates = parent.querySelectorAll('ul, [class*="dropdown"], [class*="menu"]');
            for (const el of candidates) {
                if (el !== button && el.offsetParent !== null && el.childElementCount > 0) return true;
            }
        }
        return false;
    }

    function tryOpen(info) {
        // _gpcOpened: we opened this menu and the mouse hasn't fully left the
        // button+dropdown area yet. Guard against toggle-close even when
        // isMenuOpen() fails to detect the open state (e.g. modGroupBtn/menuGroupBtn).
        if (info._gpcOpened) return;
        const now = Date.now();
        const last = buttonsState.get(info.button) || 0;
        if (now - last < PER_BUTTON_COOLDOWN_MS) return;
        if (isMenuOpen(info)) return;
        try {
            info.button.click();
            buttonsState.set(info.button, now);
            info._gpcOpened = true;
        } catch (_) {}
    }

    function scanAndAttach() {
        const controlsLeft = document.getElementById('controls-left');
        if (!controlsLeft) {
            setTimeout(scanAndAttach, 500);
            return;
        }

        const buttons = Array.from(
            controlsLeft.querySelectorAll('button[id$="GroupBtn"], button[id$="plusplusBtn"]')
        );

        // Detach any previously attached listeners before rebuilding
        trackedButtons.forEach(info => {
            if (info.button && info._hoverHandler) {
                info.button.removeEventListener('mouseenter', info._hoverHandler);
            }
            if (info._parentEl && info._parentLeaveHandler) {
                info._parentEl.removeEventListener('mouseleave', info._parentLeaveHandler);
            }
        });

        trackedButtons = buttons.map(button => {
            // Use `div.relative` specifically — menuGroupBtn and modGroupBtn have
            // `relative` in their OWN class list, so `button.closest('.relative')`
            // would return the button itself (wrong parent, no dropdown found).
            const parent = button.closest('div.relative') || button.parentElement;
            const dropdown = parent
                ? (parent.querySelector('.dropdown-menu') ||
                   parent.querySelector('[role="menu"]') ||
                   parent.querySelector('ul') ||
                   null)
                : null;
            const info = { button, parent, dropdown, _gpcOpened: false };

            // mouseenter on the button: open the menu if it isn't already ours.
            info._hoverHandler = () => {
                tryOpen(info);
            };
            button.addEventListener('mouseenter', info._hoverHandler);

            // mouseleave on the parent container (button + dropdown together):
            // when the pointer leaves the ENTIRE area, the site will close the dropdown
            // on its own, so reset our opened flag so re-hovering can reopen it.
            const parentEl = parent || button;
            info._parentEl = parentEl;
            info._parentLeaveHandler = (e) => {
                // relatedTarget is where the mouse went — if it's outside our container,
                // the user has fully left and we can allow reopening on next entry.
                if (!parentEl.contains(e.relatedTarget)) {
                    info._gpcOpened = false;
                }
            };
            parentEl.addEventListener('mouseleave', info._parentLeaveHandler);

            return info;
        });
    }

    function installMutationObserver() {
        const body = document.body;
        if (!body) return;
        const observer = new MutationObserver(() => {
            clearTimeout(scanDebounceTimer);
            scanDebounceTimer = setTimeout(scanAndAttach, 150);
        });
        // Only watch childList — attribute changes on body fire constantly and caused
        // scanAndAttach to re-register mouseenter handlers in a tight loop in older versions.
        observer.observe(body, { childList: true, subtree: true });
    }

    let scanDebounceTimer = null;

    function init() {
        scanAndAttach();
        installMutationObserver();
    }

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

            })();
            _featureStatus.extAutoHoverMenus = 'ok';
            console.log('[GeoPixelcons++] ✅ Auto-open Menus on Hover loaded');
        } catch (err) {
            _featureStatus.extAutoHoverMenus = 'error';
            dbgPush(`Auto-open Menus on Hover init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Auto-open Menus on Hover' });
            console.error('[GeoPixelcons++] ❌ Auto-open Menus on Hover failed:', err);
        }
    }

    // ============================================================
    //  EXTENSION: Auto-Go to Last Location [extGoToLastLocation]
    // ============================================================
    if (_settings.extGoToLastLocation) {
        try {
            (function _ext_goToLastLocation() {

    const SPAWN_LNG_MIN = -75;
    const SPAWN_LNG_MAX = -73;
    const SPAWN_LAT_MIN = 39;
    const SPAWN_LAT_MAX = 41;

    let hasClicked = false;
    let observer = null;

    function checkAndClick() {
        if (hasClicked) return;

        const button = document.getElementById('lastLocationButton');

        let mapObj = null;
        try {
            mapObj = eval('map');
        } catch (e) {
            return;
        }

        if (button && typeof window.goToLocation === 'function' && mapObj && typeof mapObj.getCenter === 'function') {
            try {
                const center = mapObj.getCenter();
                const lng = center.lng;
                const lat = center.lat;

                if (lng >= SPAWN_LNG_MIN && lng <= SPAWN_LNG_MAX && lat >= SPAWN_LAT_MIN && lat <= SPAWN_LAT_MAX) {
                    hasClicked = true;
                    if (observer) observer.disconnect();
                    button.click();
                }
            } catch (e) {}
        }
    }

    checkAndClick();

    if (!hasClicked) {
        observer = new MutationObserver(() => {
            checkAndClick();
        });

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

        setTimeout(() => {
            if (!hasClicked && observer) {
                observer.disconnect();
            }
        }, 10000);
    }

            })();
            _featureStatus.extGoToLastLocation = 'ok';
            console.log('[GeoPixelcons++] ✅ Auto-Go to Last Location loaded');
        } catch (err) {
            _featureStatus.extGoToLastLocation = 'error';
            dbgPush(`Auto-Go to Last Location init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Auto-Go to Last Location' });
            console.error('[GeoPixelcons++] ❌ Auto-Go to Last Location failed:', err);
        }
    }

    // ============================================================
    //  EXTENSION: Pill Hover Labels [extPillHoverLabels]
    // ============================================================
    if (_settings.extPillHoverLabels) {
        try {
            (function _ext_pillHoverLabels() {

    const PROCESSED_ATTR = 'data-gpc-pill';

    function transformButton(btn) {
        if (btn.hasAttribute(PROCESSED_ATTR)) return;
        // Skip buttons that are GeoPixelcons++ pills already
        if (btn.classList.contains('gpc-pill-btn')) return;
        // Only target round 40px submenu buttons (rounded-full or rounded-xl for GeoPixels++ select buttons)
        if (!btn.classList.contains('w-10') || !btn.classList.contains('h-10')) return;
        if (!btn.classList.contains('rounded-full') && !btn.classList.contains('rounded-xl')) return;
        // Must be inside a dropdown-menu
        if (!btn.closest('.dropdown-menu')) return;
        // Must be inside controls-left
        if (!btn.closest('#controls-left')) return;
        // Skip buttons that are hidden (mod tools, etc.) — they'll be
        // picked up by the MutationObserver when they become visible
        if (btn.classList.contains('hidden')) return;

        btn.setAttribute(PROCESSED_ATTR, '1');

        const label = btn.title || btn.getAttribute('aria-label') || '';
        if (!label) return;

        // Save the original icon content — could be text/emoji or an SVG element
        const svg = btn.querySelector('svg');
        const iconText = btn.textContent.trim();

        // Create icon span
        const iconSpan = document.createElement('span');
        iconSpan.style.cssText = 'width:40px;min-width:40px;display:flex;align-items:center;justify-content:center;flex-shrink:0;line-height:40px;pointer-events:none;';
        if (svg) {
            iconSpan.appendChild(svg.cloneNode(true));
        } else {
            iconSpan.textContent = iconText;
        }

        // Create label span — use CSS var for text color so it follows the active theme
        const labelSpan = document.createElement('span');
        labelSpan.style.cssText = 'white-space:nowrap;font-size:12px;font-weight:600;color:var(--color-gray-700, #374151);opacity:0;transition:opacity .2s .05s;padding-right:12px;pointer-events:none;';
        labelSpan.textContent = label;

        // Restyle the button — use CSS custom properties so GeoPixels++ themes apply
        btn.innerHTML = '';
        btn.style.position = 'relative';
        btn.style.width = '40px';
        btn.style.height = '40px';
        btn.style.borderRadius = '9999px';
        btn.style.background = 'var(--color-white, #fff)';
        btn.style.boxShadow = '0 1px 3px rgba(0,0,0,.12)';
        btn.style.alignItems = 'center';
        btn.style.justifyContent = 'flex-start';
        btn.style.border = 'none';
        btn.style.cursor = 'pointer';
        btn.style.overflow = 'hidden';
        btn.style.transition = 'width .25s cubic-bezier(.4,0,.2,1), background .15s';
        btn.style.padding = '0';
        btn.style.fontSize = '16px';
        btn.style.flexShrink = '0';
        // Only set display to flex if not hidden
        if (!btn.classList.contains('hidden')) {
            btn.style.display = 'flex';
        }
        // Keep original classes needed for visibility toggling but drop sizing
        btn.classList.remove('w-10', 'h-10');

        btn.appendChild(iconSpan);
        btn.appendChild(labelSpan);

        btn.addEventListener('mouseenter', () => {
            const textW = labelSpan.scrollWidth + 12;
            btn.style.width = (40 + textW) + 'px';
            labelSpan.style.opacity = '1';
            btn.style.background = 'var(--color-gray-100, #f3f4f6)';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.width = '40px';
            labelSpan.style.opacity = '0';
            btn.style.background = 'var(--color-white, #fff)';
        });
    }

    function scanAll() {
        const container = document.getElementById('controls-left');
        if (!container) return;
        // Match both rounded-full (native) and rounded-xl (GeoPixels++ select buttons)
        container.querySelectorAll('.dropdown-menu button.rounded-full, .dropdown-menu button.rounded-xl').forEach(transformButton);
        // Re-check already-processed buttons whose content was externally replaced
        // (e.g. togglePrimaryMode replaces innerHTML, destroying our pill structure)
        container.querySelectorAll('.dropdown-menu button[' + PROCESSED_ATTR + ']').forEach(btn => {
            if (!btn.querySelector('span')) {
                // Our spans were destroyed — reset and re-transform
                btn.removeAttribute(PROCESSED_ATTR);
                btn.classList.add('w-10', 'h-10');
                transformButton(btn);
            }
        });
    }

    function init() {
        const container = document.getElementById('controls-left');
        if (!container) {
            setTimeout(init, 500);
            return;
        }

        scanAll();

        // Watch for dynamically added buttons
        const observer = new MutationObserver(() => {
            clearTimeout(debounce);
            debounce = setTimeout(scanAll, 150);
        });
        let debounce = null;
        observer.observe(container, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
    }

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

            })();
            _featureStatus.extPillHoverLabels = 'ok';
            console.log('[GeoPixelcons++] ✅ Pill Hover Labels loaded');
        } catch (err) {
            _featureStatus.extPillHoverLabels = 'error';
            dbgPush(`Pill Hover Labels init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Pill Hover Labels' });
            console.error('[GeoPixelcons++] ❌ Pill Hover Labels failed:', err);
        }
    }

    // ============================================================
    //  EXTENSION: Janitor View [extJanitorView]
    // ============================================================
    if (_settings.extJanitorView) {
        try {
            (function _ext_janitorView() {

    function revealModBtn() {
        const btn = document.getElementById('modGroupBtn');
        if (btn && btn.classList.contains('hidden')) {
            btn.classList.remove('hidden');
            return true;
        }
        return false;
    }

    function init() {
        if (revealModBtn()) return;

        // Button may not exist yet — watch for it
        const observer = new MutationObserver(() => {
            if (revealModBtn()) {
                observer.disconnect();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class']
        });

        // Safety cleanup
        setTimeout(() => observer.disconnect(), 30000);
    }

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

            })();
            _featureStatus.extJanitorView = 'ok';
            console.log('[GeoPixelcons++] ✅ Janitor View loaded');
        } catch (err) {
            _featureStatus.extJanitorView = 'error';
            dbgPush(`Janitor View init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Janitor View' });
            console.error('[GeoPixelcons++] ❌ Janitor View failed:', err);
        }
    }

    // ============================================================
    //  FEATURE: Theme Editor [themeEditor]
    // ============================================================
    if (_settings.themeEditor) {
        try {
            (function _init_themeEditor() {

    // ─── Constants ───────────────────────────────────────────────────
    const TE_STORAGE_KEY = 'geoThemeEditor_themes';
    const TE_ACTIVE_KEY = 'geoThemeEditor_active';
    const TE_BASE_URL = 'https://geopixels.net';
    const TE_MINOR_ROAD_KEY = 'geoThemeEditor_minorRoadsHidden_v1';
    const TE_FEEDBACK_KEY = 'geoThemeEditor_themeFeedback_v2';

    const HIDE_MINOR_ROAD_KEYS = {
        dark: ['highway_path::line-color','highway_minor::line-color','highway_name_other::text-color','highway_name_other::text-halo-color'],
        light: ['road_path_pedestrian::line-color','road_minor::line-color','highway-name-minor::text-color']
    };

    const BUNDLED_THEMES = {"default_light":{"base":"light","name":"Default","overrides":{}},"default_dark":{"base":"dark","name":"Default Dark","overrides":{}},"fjord":{"base":"dark","name":"Fjord","overrides":{"background::background-color":"#45516E","water::fill-color":"#38435C","waterway::line-color":"#2f3436","water_name::text-color":"#90a3b7","water_name::text-halo-color":"#45516E","landcover_ice_shelf::fill-color":"#2f3647","landcover_glacier::fill-color":"#2f3647","landcover_wood::fill-color":"#3a3d46","landuse_park::fill-color":"#394854","landuse_residential::fill-color":"#3a3e47","building::fill-color":"#1c232f","building::fill-outline-color":"#2d3440","aeroway-area::fill-color":"#374455","aeroway-taxiway::line-color":"#527386","road_area_pier::fill-color":"#45516E","road_pier::line-color":"#45516E","highway_path::line-color":"#485866","highway_minor::line-color":"#6b7f8f","highway_major_inner::line-color":"#8fa0b0","highway_major_casing::line-color":"#5a6b7d","highway_major_subtle::line-color":"#4a5b6d","highway_motorway_inner::line-color":"#a0b0c0","highway_motorway_casing::line-color":"#6a7b8d","highway_motorway_subtle::line-color":"#5a6b7d","railway::line-color":"#5a6b7d","railway_dashline::line-color":"#3a4b5d","railway_transit::line-color":"#5a6b7d","railway_transit_dashline::line-color":"#3a4b5d","boundary_state::line-color":"#7a8b9d","boundary_country_z0-4::line-color":"#8a9baf","boundary_country_z5-::line-color":"#8a9baf","highway_name_other::text-color":"#b0c0d0","highway_name_other::text-halo-color":"#1a2a3a","highway_name_motorway::text-color":"#c0d0e0","place_other::text-color":"#a0b0c0","place_other::text-halo-color":"#1a2a3a","place_village::text-color":"#a0b0c0","place_village::text-halo-color":"#1a2a3a","place_town::text-color":"#b0c0d0","place_town::text-halo-color":"#1a2a3a","place_city::text-color":"#c0d0e0","place_city::text-halo-color":"#1a2a3a","place_city_large::text-color":"#d0e0f0","place_city_large::text-halo-color":"#1a2a3a","place_state::text-color":"#a0b0c0","place_state::text-halo-color":"#1a2a3a","place_country_major::text-color":"#c0d0e0","place_country_major::text-halo-color":"#1a2a3a","place_country_minor::text-color":"#a0b0c0","place_country_minor::text-halo-color":"#1a2a3a","place_country_other::text-color":"#909fa0","place_country_other::text-halo-color":"#1a2a3a"}},"debug_black":{"base":"dark","name":"Debug Black","overrides":{"background::background-color":"#000000","water::fill-color":"#000000","waterway::line-color":"#000000","water_name::text-color":"#000000","water_name::text-halo-color":"#000000","landcover_ice_shelf::fill-color":"#000000","landcover_glacier::fill-color":"#000000","landcover_wood::fill-color":"#000000","landuse_park::fill-color":"#000000","landuse_residential::fill-color":"#000000","building::fill-color":"#000000","building::fill-outline-color":"#000000","aeroway-area::fill-color":"#000000","aeroway-taxiway::line-color":"#000000","road_area_pier::fill-color":"#000000","road_pier::line-color":"#000000","highway_path::line-color":"#000000","highway_minor::line-color":"#000000","highway_major_inner::line-color":"#000000","highway_major_casing::line-color":"#000000","highway_major_subtle::line-color":"#000000","highway_motorway_inner::line-color":"#000000","highway_motorway_casing::line-color":"#000000","highway_motorway_subtle::line-color":"#000000","railway::line-color":"#000000","railway_dashline::line-color":"#000000","railway_transit::line-color":"#000000","railway_transit_dashline::line-color":"#000000","boundary_state::line-color":"#000000","boundary_country_z0-4::line-color":"#000000","boundary_country_z5-::line-color":"#000000","highway_name_other::text-color":"#000000","highway_name_other::text-halo-color":"#000000","highway_name_motorway::text-color":"#000000","place_other::text-color":"#000000","place_other::text-halo-color":"#000000","place_village::text-color":"#000000","place_village::text-halo-color":"#000000","place_town::text-color":"#000000","place_town::text-halo-color":"#000000","place_city::text-color":"#000000","place_city::text-halo-color":"#000000","place_city_large::text-color":"#000000","place_city_large::text-halo-color":"#000000","place_state::text-color":"#000000","place_state::text-halo-color":"#000000","place_country_major::text-color":"#000000","place_country_major::text-halo-color":"#000000","place_country_minor::text-color":"#000000","place_country_minor::text-halo-color":"#000000","place_country_other::text-color":"#000000","place_country_other::text-halo-color":"#000000"}},"debug_white":{"base":"light","name":"Debug White","overrides":{"background::background-color":"#ffffff","water::fill-color":"#ffffff","waterway_river::line-color":"#ffffff","waterway_other::line-color":"#ffffff","water_name_point_label::text-color":"#ffffff","water_name_point_label::text-halo-color":"#ffffff","water_name_line_label::text-color":"#ffffff","water_name_line_label::text-halo-color":"#ffffff","landcover_ice::fill-color":"#ffffff","landcover_wood::fill-color":"#ffffff","park::fill-color":"#ffffff","landuse_residential::fill-color":"#ffffff","building::fill-color":"#ffffff","aeroway_fill::fill-color":"#ffffff","road_path_pedestrian::line-color":"#ffffff","road_minor::line-color":"#ffffff","road_secondary_tertiary::line-color":"#ffffff","road_trunk_primary::line-color":"#ffffff","road_trunk_primary_casing::line-color":"#ffffff","road_motorway::line-color":"#ffffff","road_motorway_casing::line-color":"#ffffff","road_motorway_link::line-color":"#ffffff","road_major_rail::line-color":"#ffffff","road_major_rail_hatching::line-color":"#ffffff","road_transit_rail::line-color":"#ffffff","road_transit_rail_hatching::line-color":"#ffffff","boundary_3::line-color":"#ffffff","boundary_2::line-color":"#ffffff","boundary_disputed::line-color":"#ffffff","highway-name-minor::text-color":"#ffffff","highway-name-major::text-color":"#ffffff","label_other::text-color":"#ffffff","label_other::text-halo-color":"#ffffff","label_village::text-color":"#ffffff","label_village::text-halo-color":"#ffffff","label_town::text-color":"#ffffff","label_town::text-halo-color":"#ffffff","label_city::text-color":"#ffffff","label_city::text-halo-color":"#ffffff","label_city_capital::text-color":"#ffffff","label_city_capital::text-halo-color":"#ffffff","label_state::text-color":"#ffffff","label_state::text-halo-color":"#ffffff","label_country_1::text-color":"#ffffff","label_country_1::text-halo-color":"#ffffff","label_country_2::text-color":"#ffffff","label_country_2::text-halo-color":"#ffffff","label_country_3::text-color":"#ffffff","label_country_3::text-halo-color":"#ffffff"}},"ayu_mirage":{"base":"light","name":"Ayu Mirage","overrides":{"background::background-color":"#f3f4f6","park::fill-color":"#e6eec8","landuse_residential::fill-color":"hsla(35,57%,88%,0.49)","landcover_wood::fill-color":"#e6eec8","landcover_ice::fill-color":"rgba(224, 236, 236, 1)","waterway_river::line-color":"#dbe6f0","waterway_other::line-color":"#dbe6f0","water::fill-color":"#dbe6f0","building::fill-color":"#e6e1cf","building::fill-outline-color":"#f3f4f6","road_path_pedestrian::line-color":"#cfccc6","road_minor::line-color":"#cfccc6","road_secondary_tertiary::line-color":"#cfccc6","road_trunk_primary::line-color":"#ffae57","road_trunk_primary_casing::line-color":"#ffae57","road_motorway::line-color":"#ffae57","road_motorway_casing::line-color":"#ffae57","road_motorway_link::line-color":"#ffae57","road_major_rail::line-color":"#ffae57","road_major_rail_hatching::line-color":"#ffae57","road_transit_rail::line-color":"#cfccc6","road_transit_rail_hatching::line-color":"#cfccc6","boundary_3::line-color":"#5c6773","boundary_2::line-color":"#5c6773","boundary_disputed::line-color":"#5c6773","highway-name-minor::text-color":"#5c6773","highway-name-major::text-color":"#5c6773","label_other::text-color":"#5c6773","label_other::text-halo-color":"#f3f4f6","label_village::text-color":"#5c6773","label_village::text-halo-color":"#f3f4f6","label_town::text-color":"#5c6773","label_town::text-halo-color":"#f3f4f6","label_city::text-color":"#5c6773","label_city::text-halo-color":"#f3f4f6","label_city_capital::text-color":"#5c6773","label_city_capital::text-halo-color":"#f3f4f6","label_state::text-color":"#5c6773","label_state::text-halo-color":"#f3f4f6","label_country_1::text-color":"#5c6773","label_country_1::text-halo-color":"#f3f4f6","label_country_2::text-color":"#5c6773","label_country_2::text-halo-color":"#f3f4f6","label_country_3::text-color":"#5c6773","label_country_3::text-halo-color":"#f3f4f6"}},"cute_pink":{"base":"light","name":"Cute & Pink","overrides":{"background::background-color":"#fff0f5","park::fill-color":"#ffe6f2","landcover_wood::fill-color":"#ffe6f2","waterway_river::line-color":"#cceeff","waterway_other::line-color":"#cceeff","water::fill-color":"#cceeff","road_path_pedestrian::line-color":"#ffb3d9","road_minor::line-color":"#ffb3d9","road_secondary_tertiary::line-color":"#ffb3d9","road_trunk_primary::line-color":"#ffb3d9","road_trunk_primary_casing::line-color":"#ffb3d9","road_motorway::line-color":"#ffb3d9","road_motorway_casing::line-color":"#ffb3d9","road_motorway_link::line-color":"#ffb3d9","road_major_rail::line-color":"#ffb3d9","road_transit_rail::line-color":"#ffb3d9","building::fill-color":"#ffdaeb","building::fill-outline-color":"#fff0f5","boundary_3::line-color":"#993366","boundary_2::line-color":"#993366","boundary_disputed::line-color":"#993366","highway-name-minor::text-color":"#993366","highway-name-major::text-color":"#993366","label_other::text-color":"#993366","label_other::text-halo-color":"#fff0f5","label_village::text-color":"#993366","label_village::text-halo-color":"#fff0f5","label_town::text-color":"#993366","label_town::text-halo-color":"#fff0f5","label_city::text-color":"#993366","label_city::text-halo-color":"#fff0f5","label_city_capital::text-color":"#993366","label_city_capital::text-halo-color":"#fff0f5","label_state::text-color":"#993366","label_state::text-halo-color":"#fff0f5","label_country_1::text-color":"#993366","label_country_1::text-halo-color":"#fff0f5","label_country_2::text-color":"#993366","label_country_2::text-halo-color":"#fff0f5","label_country_3::text-color":"#993366","label_country_3::text-halo-color":"#fff0f5"}},"discord_gold":{"base":"dark","name":"Discord Gold","overrides":{"background::background-color":"#171717","water::fill-color":"#23272A","waterway::line-color":"hsl(232, 23%, 28%)","water_name::text-color":"hsl(38, 60%, 50%)","water_name::text-halo-color":"hsl(232, 5%, 19%)","landcover_ice_shelf::fill-color":"hsl(232, 33%, 34%)","landuse_residential::fill-color":"transparent","landcover_wood::fill-color":"hsla(232, 18%, 30%, 0.57)","landuse_park::fill-color":"hsl(204, 17%, 35%)","building::fill-color":"hsla(232, 47%, 18%, 0.65)","highway_path::line-color":"hsl(211, 29%, 38%)","highway_minor::line-color":"hsl(224, 22%, 45%)","highway_major_casing::line-color":"hsl(224, 22%, 45%)","highway_major_inner::line-color":"#36393F","highway_major_subtle::line-color":"#38393E","highway_motorway_casing::line-color":"hsl(224, 22%, 45%)","highway_motorway_inner::line-color":"hsl(224, 20%, 29%)","highway_motorway_subtle::line-color":"hsla(239, 45%, 69%, 0.2)","railway::line-color":"hsl(200, 10%, 18%)","railway_dashline::line-color":"hsl(224, 20%, 41%)","boundary_state::line-color":"hsla(195, 47%, 62%, 0.26)","boundary_country_z0-4::line-color":"hsl(214, 63%, 76%)","boundary_country_z5-::line-color":"hsl(214, 63%, 76%)","highway_name_other::text-color":"hsl(38, 70%, 60%)","highway_name_other::text-halo-color":"hsl(232, 9%, 23%)","highway_name_motorway::text-color":"hsl(38, 70%, 60%)","place_other::text-color":"hsl(38, 65%, 60%)","place_other::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_village::text-color":"hsl(38, 70%, 45%)","place_village::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_town::text-color":"hsl(38, 75%, 65%)","place_town::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_city::text-color":"hsl(38, 75%, 65%)","place_city::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_city_large::text-color":"hsl(38, 75%, 65%)","place_city_large::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_state::text-color":"rgb(113, 129, 144)","place_state::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_country_other::text-color":"rgb(153, 153, 153)","place_country_other::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_country_minor::text-color":"rgb(153, 153, 153)","place_country_minor::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_country_major::text-color":"rgb(153, 153, 153)","place_country_major::text-halo-color":"hsla(228, 60%, 21%, 0.7)"}},"monokai":{"base":"dark","name":"Monokai","overrides":{"background::background-color":"#000000","water::fill-color":"#2D2E28","waterway::line-color":"hsl(232, 23%, 28%)","water_name::text-color":"hsl(223, 21%, 52%)","water_name::text-halo-color":"hsl(232, 5%, 19%)","landcover_ice_shelf::fill-color":"hsl(70, 15%, 35%)","landuse_residential::fill-color":"transparent","landcover_wood::fill-color":"hsla(232, 18%, 30%, 0.57)","landuse_park::fill-color":"hsl(204, 17%, 35%)","building::fill-color":"hsla(232, 47%, 18%, 0.65)","highway_path::line-color":"hsl(211, 29%, 38%)","highway_minor::line-color":"hsl(70, 20%, 40%)","highway_major_casing::line-color":"hsl(70, 20%, 40%)","highway_major_inner::line-color":"#3A3E38","highway_major_subtle::line-color":"#273a2d","highway_motorway_casing::line-color":"hsl(70, 20%, 40%)","highway_motorway_inner::line-color":"hsl(70, 18%, 28%)","highway_motorway_subtle::line-color":"hsla(239, 45%, 69%, 0.2)","railway::line-color":"hsl(40, 20%, 18%)","railway_dashline::line-color":"hsl(224, 20%, 41%)","boundary_state::line-color":"hsla(195, 47%, 62%, 0.26)","boundary_country_z0-4::line-color":"hsl(214, 63%, 76%)","boundary_country_z5-::line-color":"hsl(214, 63%, 76%)","highway_name_other::text-color":"hsl(223, 31%, 61%)","highway_name_other::text-halo-color":"hsl(232, 9%, 23%)","place_other::text-color":"hsl(195, 37%, 73%)","place_other::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_village::text-color":"hsl(195, 41%, 49%)","place_village::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_town::text-color":"hsl(195, 25%, 76%)","place_town::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_city::text-color":"hsl(195, 25%, 76%)","place_city::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_city_large::text-color":"hsl(195, 25%, 76%)","place_city_large::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_state::text-color":"rgb(140, 130, 100)","place_state::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_country_other::text-color":"rgb(153, 153, 153)","place_country_other::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_country_minor::text-color":"rgb(153, 153, 153)","place_country_minor::text-halo-color":"hsla(228, 60%, 21%, 0.7)","place_country_major::text-color":"rgb(153, 153, 153)","place_country_major::text-halo-color":"hsla(228, 60%, 21%, 0.7)"}},"obsidian":{"base":"dark","name":"Obsidian","overrides":{"background::background-color":"#1c1c1c","water::fill-color":"#262626","waterway::line-color":"#262626","water_name::text-color":"#a0a0a0","water_name::text-halo-color":"#1c1c1c","landcover_ice_shelf::fill-color":"rgb(12,12,12)","landcover_glacier::fill-color":"hsl(0, 1%, 2%)","landuse_residential::fill-color":"hsl(0, 2%, 5%)","landcover_wood::fill-color":"#2a2a2a","landuse_park::fill-color":"#2a2a2a","building::fill-color":"#242424","building::fill-outline-color":"#1c1c1c","highway_path::line-color":"#3d3d3d","highway_minor::line-color":"#3d3d3d","highway_major_casing::line-color":"#555555","highway_major_inner::line-color":"#555555","highway_major_subtle::line-color":"#555555","highway_motorway_casing::line-color":"#555555","highway_motorway_inner::line-color":"#555555","highway_motorway_subtle::line-color":"#555555","railway::line-color":"rgb(35,35,35)","railway_dashline::line-color":"rgb(12,12,12)","boundary_state::line-color":"#a0a0a0","boundary_country_z0-4::line-color":"#a0a0a0","boundary_country_z5-::line-color":"#a0a0a0","highway_name_other::text-color":"#a0a0a0","highway_name_other::text-halo-color":"#1c1c1c","highway_name_motorway::text-color":"#a0a0a0","place_other::text-color":"#a0a0a0","place_other::text-halo-color":"#1c1c1c","place_village::text-color":"#a0a0a0","place_village::text-halo-color":"#1c1c1c","place_town::text-color":"#a0a0a0","place_town::text-halo-color":"#1c1c1c","place_city::text-color":"#a0a0a0","place_city::text-halo-color":"#1c1c1c","place_city_large::text-color":"#a0a0a0","place_city_large::text-halo-color":"#1c1c1c","place_state::text-color":"#a0a0a0","place_state::text-halo-color":"#1c1c1c","place_country_other::text-color":"#a0a0a0","place_country_other::text-halo-color":"#1c1c1c","place_country_minor::text-color":"#a0a0a0","place_country_minor::text-halo-color":"#1c1c1c","place_country_major::text-color":"#a0a0a0","place_country_major::text-halo-color":"#1c1c1c"}},"vintage_sepia":{"base":"light","name":"Vintage Sepia","overrides":{"background::background-color":"#f4e4bc","park::fill-color":"#c5d5a7","landcover_wood::fill-color":"#c5d5a7","waterway_river::line-color":"#d2c29d","waterway_other::line-color":"#d2c29d","water::fill-color":"#d2c29d","road_path_pedestrian::line-color":"#a89f91","road_minor::line-color":"#a89f91","road_secondary_tertiary::line-color":"#a89f91","road_trunk_primary::line-color":"#8f8170","road_trunk_primary_casing::line-color":"#8f8170","road_motorway::line-color":"#8f8170","road_motorway_casing::line-color":"#8f8170","road_motorway_link::line-color":"#8f8170","road_major_rail::line-color":"#8f8170","road_transit_rail::line-color":"#a89f91","building::fill-color":"#e8d5a8","building::fill-outline-color":"#f4e4bc","boundary_3::line-color":"#5b4a42","boundary_2::line-color":"#5b4a42","boundary_disputed::line-color":"#5b4a42","highway-name-minor::text-color":"#5b4a42","highway-name-major::text-color":"#5b4a42","label_other::text-color":"#5b4a42","label_other::text-halo-color":"#f4e4bc","label_village::text-color":"#5b4a42","label_village::text-halo-color":"#f4e4bc","label_town::text-color":"#5b4a42","label_town::text-halo-color":"#f4e4bc","label_city::text-color":"#5b4a42","label_city::text-halo-color":"#f4e4bc","label_city_capital::text-color":"#5b4a42","label_city_capital::text-halo-color":"#f4e4bc","label_state::text-color":"#5b4a42","label_state::text-halo-color":"#f4e4bc","label_country_1::text-color":"#5b4a42","label_country_1::text-halo-color":"#f4e4bc","label_country_2::text-color":"#5b4a42","label_country_2::text-halo-color":"#f4e4bc","label_country_3::text-color":"#5b4a42","label_country_3::text-halo-color":"#f4e4bc"}}};

    const EDITABLE_LAYERS = {
        dark: [
            { group: 'Base', layers: [{ id: 'background', prop: 'background-color', label: 'Background' }] },
            { group: 'Water', layers: [{ id: 'water', prop: 'fill-color', label: 'Water Fill' },{ id: 'waterway', prop: 'line-color', label: 'Waterways' },{ id: 'water_name', prop: 'text-color', label: 'Water Labels' },{ id: 'water_name', prop: 'text-halo-color', label: 'Water Label Halo' }] },
            { group: 'Land & Nature', layers: [{ id: 'landcover_ice_shelf', prop: 'fill-color', label: 'Ice Shelf' },{ id: 'landcover_glacier', prop: 'fill-color', label: 'Glaciers' },{ id: 'landcover_wood', prop: 'fill-color', label: 'Forests / Wood' },{ id: 'landuse_park', prop: 'fill-color', label: 'Parks' },{ id: 'landuse_residential', prop: 'fill-color', label: 'Residential' }] },
            { group: 'Buildings & Areas', layers: [{ id: 'building', prop: 'fill-color', label: 'Building Fill' },{ id: 'building', prop: 'fill-outline-color', label: 'Building Outline' },{ id: 'aeroway-area', prop: 'fill-color', label: 'Airport Area' },{ id: 'road_area_pier', prop: 'fill-color', label: 'Pier Area' }] },
            { group: 'Roads', layers: [{ id: 'highway_path', prop: 'line-color', label: 'Paths' },{ id: 'highway_minor', prop: 'line-color', label: 'Minor Roads' },{ id: 'highway_major_inner', prop: 'line-color', label: 'Major Roads' },{ id: 'highway_major_casing', prop: 'line-color', label: 'Major Road Casing' },{ id: 'highway_major_subtle', prop: 'line-color', label: 'Major Roads (Subtle)' },{ id: 'highway_motorway_inner', prop: 'line-color', label: 'Motorway' },{ id: 'highway_motorway_casing', prop: 'line-color', label: 'Motorway Casing' },{ id: 'highway_motorway_subtle', prop: 'line-color', label: 'Motorway (Subtle)' },{ id: 'road_pier', prop: 'line-color', label: 'Pier Roads' }] },
            { group: 'Railways', layers: [{ id: 'railway', prop: 'line-color', label: 'Railways' },{ id: 'railway_dashline', prop: 'line-color', label: 'Railway Dashes' },{ id: 'railway_transit', prop: 'line-color', label: 'Transit Rail' },{ id: 'railway_transit_dashline', prop: 'line-color', label: 'Transit Dashes' }] },
            { group: 'Boundaries', layers: [{ id: 'boundary_state', prop: 'line-color', label: 'State Borders' },{ id: 'boundary_country_z0-4', prop: 'line-color', label: 'Country Borders (Far)' },{ id: 'boundary_country_z5-', prop: 'line-color', label: 'Country Borders (Near)' }] },
            { group: 'Labels', layers: [{ id: 'highway_name_other', prop: 'text-color', label: 'Road Labels' },{ id: 'highway_name_other', prop: 'text-halo-color', label: 'Road Label Halo' },{ id: 'highway_name_motorway', prop: 'text-color', label: 'Motorway Labels' },{ id: 'place_other', prop: 'text-color', label: 'Hamlet / Neighborhood' },{ id: 'place_other', prop: 'text-halo-color', label: 'Hamlet Halo' },{ id: 'place_village', prop: 'text-color', label: 'Villages' },{ id: 'place_village', prop: 'text-halo-color', label: 'Village Halo' },{ id: 'place_town', prop: 'text-color', label: 'Towns' },{ id: 'place_town', prop: 'text-halo-color', label: 'Town Halo' },{ id: 'place_city', prop: 'text-color', label: 'Cities' },{ id: 'place_city', prop: 'text-halo-color', label: 'City Halo' },{ id: 'place_city_large', prop: 'text-color', label: 'Major Cities' },{ id: 'place_city_large', prop: 'text-halo-color', label: 'Major City Halo' },{ id: 'place_state', prop: 'text-color', label: 'States' },{ id: 'place_state', prop: 'text-halo-color', label: 'State Halo' },{ id: 'place_country_major', prop: 'text-color', label: 'Countries (Major)' },{ id: 'place_country_major', prop: 'text-halo-color', label: 'Country Halo (Major)' },{ id: 'place_country_minor', prop: 'text-color', label: 'Countries (Minor)' },{ id: 'place_country_other', prop: 'text-color', label: 'Countries (Other)' }] },
        ],
        light: [
            { group: 'Base', layers: [{ id: 'background', prop: 'background-color', label: 'Background' }] },
            { group: 'Water', layers: [{ id: 'water', prop: 'fill-color', label: 'Water Fill' },{ id: 'waterway_river', prop: 'line-color', label: 'Rivers' },{ id: 'waterway_other', prop: 'line-color', label: 'Other Waterways' },{ id: 'water_name_point_label', prop: 'text-color', label: 'Water Labels' },{ id: 'water_name_point_label', prop: 'text-halo-color', label: 'Water Label Halo' }] },
            { group: 'Land & Nature', layers: [{ id: 'landcover_ice', prop: 'fill-color', label: 'Ice / Snow' },{ id: 'landcover_wood', prop: 'fill-color', label: 'Forests / Wood' },{ id: 'park', prop: 'fill-color', label: 'Parks' },{ id: 'landuse_residential', prop: 'fill-color', label: 'Residential' }] },
            { group: 'Buildings & Areas', layers: [{ id: 'building', prop: 'fill-color', label: 'Building Fill' },{ id: 'aeroway_fill', prop: 'fill-color', label: 'Airport Area' }] },
            { group: 'Roads', layers: [{ id: 'road_path_pedestrian', prop: 'line-color', label: 'Paths' },{ id: 'road_minor', prop: 'line-color', label: 'Minor Roads' },{ id: 'road_secondary_tertiary', prop: 'line-color', label: 'Secondary / Tertiary' },{ id: 'road_trunk_primary', prop: 'line-color', label: 'Trunk / Primary' },{ id: 'road_trunk_primary_casing', prop: 'line-color', label: 'Road Casing' },{ id: 'road_motorway', prop: 'line-color', label: 'Motorway' },{ id: 'road_motorway_casing', prop: 'line-color', label: 'Motorway Casing' },{ id: 'road_motorway_link', prop: 'line-color', label: 'Motorway Links' }] },
            { group: 'Railways', layers: [{ id: 'road_major_rail', prop: 'line-color', label: 'Railways' },{ id: 'road_major_rail_hatching', prop: 'line-color', label: 'Railway Hatching' },{ id: 'road_transit_rail', prop: 'line-color', label: 'Transit Rail' },{ id: 'road_transit_rail_hatching', prop: 'line-color', label: 'Transit Hatching' }] },
            { group: 'Boundaries', layers: [{ id: 'boundary_3', prop: 'line-color', label: 'State Borders' },{ id: 'boundary_2', prop: 'line-color', label: 'Country Borders' },{ id: 'boundary_disputed', prop: 'line-color', label: 'Disputed Borders' }] },
            { group: 'Labels', layers: [{ id: 'highway-name-minor', prop: 'text-color', label: 'Road Labels' },{ id: 'highway-name-major', prop: 'text-color', label: 'Major Road Labels' },{ id: 'label_other', prop: 'text-color', label: 'Hamlet / Neighborhood' },{ id: 'label_other', prop: 'text-halo-color', label: 'Hamlet Halo' },{ id: 'label_village', prop: 'text-color', label: 'Villages' },{ id: 'label_village', prop: 'text-halo-color', label: 'Village Halo' },{ id: 'label_town', prop: 'text-color', label: 'Towns' },{ id: 'label_town', prop: 'text-halo-color', label: 'Town Halo' },{ id: 'label_city', prop: 'text-color', label: 'Cities' },{ id: 'label_city', prop: 'text-halo-color', label: 'City Halo' },{ id: 'label_city_capital', prop: 'text-color', label: 'Capital Cities' },{ id: 'label_city_capital', prop: 'text-halo-color', label: 'Capital City Halo' },{ id: 'label_state', prop: 'text-color', label: 'States' },{ id: 'label_state', prop: 'text-halo-color', label: 'State Halo' },{ id: 'label_country_1', prop: 'text-color', label: 'Countries (Major)' },{ id: 'label_country_1', prop: 'text-halo-color', label: 'Country Halo (Major)' },{ id: 'label_country_2', prop: 'text-color', label: 'Countries (Minor)' },{ id: 'label_country_3', prop: 'text-color', label: 'Countries (Other)' }] },
        ],
    };

    const SIMPLE_LAYERS = {
        dark: [
            { group: 'Base', layers: [{ label: 'Background', keys: ['background::background-color'] }] },
            { group: 'Water', layers: [{ label: 'Water', keys: ['water::fill-color','waterway::line-color'] },{ label: 'Water Labels', keys: ['water_name::text-color'] },{ label: 'Water Label Halo', keys: ['water_name::text-halo-color'] }] },
            { group: 'Land & Nature', layers: [{ label: 'Nature / Parks', keys: ['landcover_wood::fill-color','landuse_park::fill-color','landcover_ice_shelf::fill-color','landcover_glacier::fill-color'] },{ label: 'Residential', keys: ['landuse_residential::fill-color'] }] },
            { group: 'Buildings & Areas', layers: [{ label: 'Buildings', keys: ['building::fill-color','building::fill-outline-color'] },{ label: 'Airports / Piers', keys: ['aeroway-area::fill-color','road_area_pier::fill-color'] }] },
            { group: 'Roads', layers: [{ label: 'Minor Roads', keys: ['highway_path::line-color','highway_minor::line-color','road_pier::line-color'] },{ label: 'Major Roads', keys: ['highway_major_inner::line-color','highway_major_casing::line-color','highway_major_subtle::line-color'] },{ label: 'Motorways', keys: ['highway_motorway_inner::line-color','highway_motorway_casing::line-color','highway_motorway_subtle::line-color'] }] },
            { group: 'Railways', layers: [{ label: 'All Railways', keys: ['railway::line-color','railway_dashline::line-color','railway_transit::line-color','railway_transit_dashline::line-color'] }] },
            { group: 'Boundaries', layers: [{ label: 'All Borders', keys: ['boundary_state::line-color','boundary_country_z0-4::line-color','boundary_country_z5-::line-color'] }] },
            { group: 'Labels', layers: [{ label: 'Road Labels', keys: ['highway_name_other::text-color','highway_name_motorway::text-color'] },{ label: 'Road Label Halo', keys: ['highway_name_other::text-halo-color'] },{ label: 'Place Labels', keys: ['place_other::text-color','place_village::text-color','place_town::text-color','place_city::text-color','place_city_large::text-color','place_state::text-color','place_country_major::text-color','place_country_minor::text-color','place_country_other::text-color'] },{ label: 'Place Label Halo', keys: ['place_other::text-halo-color','place_village::text-halo-color','place_town::text-halo-color','place_city::text-halo-color','place_city_large::text-halo-color','place_state::text-halo-color','place_country_major::text-halo-color'] }] },
        ],
        light: [
            { group: 'Base', layers: [{ label: 'Background', keys: ['background::background-color'] }] },
            { group: 'Water', layers: [{ label: 'Water', keys: ['water::fill-color','waterway_river::line-color','waterway_other::line-color'] },{ label: 'Water Labels', keys: ['water_name_point_label::text-color','water_name_line_label::text-color'] },{ label: 'Water Label Halo', keys: ['water_name_point_label::text-halo-color','water_name_line_label::text-halo-color'] }] },
            { group: 'Land & Nature', layers: [{ label: 'Nature / Parks', keys: ['landcover_wood::fill-color','park::fill-color','landcover_ice::fill-color'] },{ label: 'Residential', keys: ['landuse_residential::fill-color'] }] },
            { group: 'Buildings & Areas', layers: [{ label: 'Buildings', keys: ['building::fill-color'] },{ label: 'Airports', keys: ['aeroway_fill::fill-color'] }] },
            { group: 'Roads', layers: [{ label: 'Minor Roads', keys: ['road_path_pedestrian::line-color','road_minor::line-color'] },{ label: 'Major Roads', keys: ['road_secondary_tertiary::line-color','road_trunk_primary::line-color','road_trunk_primary_casing::line-color'] },{ label: 'Motorways', keys: ['road_motorway::line-color','road_motorway_casing::line-color','road_motorway_link::line-color'] }] },
            { group: 'Railways', layers: [{ label: 'All Railways', keys: ['road_major_rail::line-color','road_major_rail_hatching::line-color','road_transit_rail::line-color','road_transit_rail_hatching::line-color'] }] },
            { group: 'Boundaries', layers: [{ label: 'All Borders', keys: ['boundary_3::line-color','boundary_2::line-color','boundary_disputed::line-color'] }] },
            { group: 'Labels', layers: [{ label: 'Road Labels', keys: ['highway-name-minor::text-color','highway-name-major::text-color'] },{ label: 'Place Labels', keys: ['label_other::text-color','label_village::text-color','label_town::text-color','label_city::text-color','label_city_capital::text-color','label_state::text-color','label_country_1::text-color','label_country_2::text-color','label_country_3::text-color'] },{ label: 'Place Label Halo', keys: ['label_other::text-halo-color','label_village::text-halo-color','label_town::text-halo-color','label_city::text-halo-color','label_city_capital::text-halo-color','label_state::text-halo-color','label_country_1::text-halo-color'] }] },
        ],
    };

    const BASE_DEFAULTS = {
        dark: {'background::background-color':'#0c0c0c','water::fill-color':'#1b1b1d','waterway::line-color':'#1b1b1d','water_name::text-color':'#000000','water_name::text-halo-color':'#454545','landcover_ice_shelf::fill-color':'#0c0c0c','landcover_glacier::fill-color':'#050505','landcover_wood::fill-color':'#202020','landuse_park::fill-color':'#202020','landuse_residential::fill-color':'#0d0d0d','building::fill-color':'#0a0a0a','building::fill-outline-color':'#1b1b1d','aeroway-area::fill-color':'#000000','road_area_pier::fill-color':'#0c0c0c','road_pier::line-color':'#0c0c0c','highway_path::line-color':'#1b1b1d','highway_minor::line-color':'#181818','highway_major_inner::line-color':'#121212','highway_major_casing::line-color':'#3c3c3c','highway_major_subtle::line-color':'#2a2a2a','highway_motorway_inner::line-color':'#000000','highway_motorway_casing::line-color':'#3c3c3c','highway_motorway_subtle::line-color':'#181818','railway::line-color':'#232323','railway_dashline::line-color':'#0c0c0c','railway_transit::line-color':'#232323','railway_transit_dashline::line-color':'#0c0c0c','boundary_state::line-color':'#363636','boundary_country_z0-4::line-color':'#3b3b3b','boundary_country_z5-::line-color':'#3b3b3b','highway_name_other::text-color':'#504e4e','highway_name_other::text-halo-color':'#000000','highway_name_motorway::text-color':'#5e5e5e','place_other::text-color':'#656565','place_other::text-halo-color':'#000000','place_village::text-color':'#656565','place_village::text-halo-color':'#000000','place_town::text-color':'#656565','place_town::text-halo-color':'#000000','place_city::text-color':'#656565','place_city::text-halo-color':'#000000','place_city_large::text-color':'#656565','place_city_large::text-halo-color':'#000000','place_state::text-color':'#656565','place_state::text-halo-color':'#000000','place_country_other::text-color':'#656565','place_country_other::text-halo-color':'#000000','place_country_minor::text-color':'#656565','place_country_minor::text-halo-color':'#000000','place_country_major::text-color':'#656565','place_country_major::text-halo-color':'#000000'},
        light: {'background::background-color':'#f8f4f0','water::fill-color':'#9ebdff','waterway_river::line-color':'#a0c8f0','waterway_other::line-color':'#a0c8f0','water_name_point_label::text-color':'#495e91','water_name_point_label::text-halo-color':'#ffffff','water_name_line_label::text-color':'#495e91','water_name_line_label::text-halo-color':'#ffffff','landcover_ice::fill-color':'#e0ecec','landcover_wood::fill-color':'#a4d898','park::fill-color':'#d8e8c8','landuse_residential::fill-color':'#e8dece','building::fill-color':'#d4cfc9','aeroway_fill::fill-color':'#e5e4e0','road_path_pedestrian::line-color':'#ffffff','road_minor::line-color':'#ffffff','road_secondary_tertiary::line-color':'#ffeeaa','road_trunk_primary::line-color':'#ffeeaa','road_trunk_primary_casing::line-color':'#e9ac77','road_motorway::line-color':'#ffcc88','road_motorway_casing::line-color':'#e9ac77','road_motorway_link::line-color':'#ffcc88','road_major_rail::line-color':'#bbbbbb','road_major_rail_hatching::line-color':'#bbbbbb','road_transit_rail::line-color':'#bbbbbb','road_transit_rail_hatching::line-color':'#bbbbbb','boundary_3::line-color':'#b3b3b3','boundary_2::line-color':'#696969','boundary_disputed::line-color':'#696969','highway-name-minor::text-color':'#666666','highway-name-major::text-color':'#666666','label_other::text-color':'#333333','label_other::text-halo-color':'#ffffff','label_village::text-color':'#000000','label_village::text-halo-color':'#ffffff','label_town::text-color':'#000000','label_town::text-halo-color':'#ffffff','label_city::text-color':'#000000','label_city::text-halo-color':'#ffffff','label_city_capital::text-color':'#000000','label_city_capital::text-halo-color':'#ffffff','label_state::text-color':'#333333','label_state::text-halo-color':'#ffffff','label_country_1::text-color':'#000000','label_country_1::text-halo-color':'#ffffff','label_country_2::text-color':'#000000','label_country_2::text-halo-color':'#ffffff','label_country_3::text-color':'#000000','label_country_3::text-halo-color':'#ffffff'}
    };

    function getEditableLayers(base) { return EDITABLE_LAYERS[base] || EDITABLE_LAYERS.dark; }
    function getSimpleLayers(base) { return SIMPLE_LAYERS[base] || SIMPLE_LAYERS.dark; }
    function getDefaultColor(key, base) { return (BASE_DEFAULTS[base] || BASE_DEFAULTS.dark)[key] || '#808080'; }

    // ─── Storage Helpers ─────────────────────────────────────────────
    function teLoadThemes() {
        let themes = {};
        try { themes = JSON.parse(localStorage.getItem(TE_STORAGE_KEY)) || {}; } catch {}
        for (const [key, bundled] of Object.entries(BUNDLED_THEMES)) {
            const name = bundled.name;
            if (!themes[name] || !themes[name].bundled) {
                themes[name] = { overrides: { ...bundled.overrides }, base: bundled.base, bundled: true, createdAt: themes[name]?.createdAt || Date.now(), updatedAt: Date.now() };
            }
            if (name === 'Debug White' && !themes[name].overrides['*::all-color']) themes[name].overrides['*::all-color'] = '#ffffff';
            if (name === 'Debug Black' && !themes[name].overrides['*::all-color']) themes[name].overrides['*::all-color'] = '#000000';
        }
        if (!localStorage.getItem(TE_MINOR_ROAD_KEY)) {
            for (const bundled of Object.values(BUNDLED_THEMES)) {
                const theme = themes[bundled.name];
                if (!theme || !theme.bundled || bundled.name === 'Debug White' || bundled.name === 'Debug Black') continue;
                const keys = HIDE_MINOR_ROAD_KEYS[theme.base || 'dark'] || [];
                for (const k of keys) { const cur = theme.overrides[k]; const orig = bundled.overrides[k]; if (cur == null || cur === orig) theme.overrides[k] = 'transparent'; }
                theme.updatedAt = Date.now();
            }
            localStorage.setItem(TE_MINOR_ROAD_KEY, '1');
        }
        if (!localStorage.getItem(TE_FEEDBACK_KEY)) {
            const dg = themes['Discord Gold']; if (dg?.bundled) { dg.overrides['background::background-color'] = '#171717'; dg.overrides['landuse_residential::fill-color'] = 'transparent'; dg.updatedAt = Date.now(); }
            const cp = themes['Cute & Pink']; if (cp?.bundled) { cp.overrides['landuse_residential::fill-color'] = 'transparent'; cp.updatedAt = Date.now(); }
            const mk = themes['Monokai']; if (mk?.bundled) { mk.overrides['background::background-color'] = '#000000'; mk.overrides['landuse_residential::fill-color'] = 'transparent'; mk.overrides['highway_major_subtle::line-color'] = '#273a2d'; mk.updatedAt = Date.now(); }
            localStorage.setItem(TE_FEEDBACK_KEY, '1');
        }
        teSaveThemes(themes);
        return themes;
    }
    function teSaveThemes(themes) { localStorage.setItem(TE_STORAGE_KEY, JSON.stringify(themes)); }
    function teGetActive() { return localStorage.getItem(TE_ACTIVE_KEY) || ''; }
    function teSetActive(name) { localStorage.setItem(TE_ACTIVE_KEY, name); }

    // ─── Color Helpers ───────────────────────────────────────────────
    function colorToHex(color) {
        if (!color || typeof color !== 'string') return '#000000';
        if (/^#[0-9A-Fa-f]{6}$/.test(color)) return color;
        if (/^#[0-9A-Fa-f]{3}$/.test(color)) { const [,r,g,b] = color.match(/^#(.)(.)(.)$/); return '#'+r+r+g+g+b+b; }
        const tmp = document.createElement('div'); tmp.style.color = color; document.body.appendChild(tmp);
        const computed = getComputedStyle(tmp).color; document.body.removeChild(tmp);
        const m = computed.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
        if (!m) return '#000000';
        const hex = n => parseInt(n,10).toString(16).padStart(2,'0');
        return '#'+hex(m[1])+hex(m[2])+hex(m[3]);
    }
    function isColorDark(hex) { const h = colorToHex(hex).replace('#',''); const r = parseInt(h.substring(0,2),16)/255; const g = parseInt(h.substring(2,4),16)/255; const b = parseInt(h.substring(4,6),16)/255; return (0.299*r+0.587*g+0.114*b) < 0.4; }

    // ─── Base Style Templates ────────────────────────────────────────
    let lightStyleTemplate = null, darkStyleTemplate = null;
    async function fetchStyle(endpoint) { let text = await fetch(TE_BASE_URL + endpoint).then(r => r.text()); text = text.replaceAll('http://localhost:5039', TE_BASE_URL); return JSON.parse(text); }
    async function getBaseStyle(base) {
        if (base === 'dark') { if (!darkStyleTemplate) { try { darkStyleTemplate = await fetchStyle('/styleDark'); } catch { return null; } } return JSON.parse(JSON.stringify(darkStyleTemplate)); }
        if (!lightStyleTemplate) { try { lightStyleTemplate = await fetchStyle('/style'); } catch { return null; } } return JSON.parse(JSON.stringify(lightStyleTemplate));
    }

    // ─── Style Builder ───────────────────────────────────────────────
    function buildStyle(baseStyle, overrides) {
        const style = JSON.parse(JSON.stringify(baseStyle));
        const allColor = overrides['*::all-color'];
        for (const layer of style.layers) {
            if (allColor && layer.paint) {
                if (layer.paint['background-color'] != null) layer.paint['background-color'] = allColor;
                if (layer.paint['fill-color'] != null) layer.paint['fill-color'] = allColor;
                if (layer.paint['fill-outline-color'] != null) layer.paint['fill-outline-color'] = allColor;
                if (layer.paint['line-color'] != null) layer.paint['line-color'] = allColor;
                if (layer.paint['text-color'] != null) layer.paint['text-color'] = allColor;
                if (layer.paint['text-halo-color'] != null) layer.paint['text-halo-color'] = allColor;
                if (layer.type === 'raster' && layer.paint['raster-opacity'] != null) layer.paint['raster-opacity'] = 0;
                if (layer.type === 'symbol') layer.paint['icon-opacity'] = 0;
            }
            const check = (prop) => { const key = layer.id+'::'+prop; if (overrides[key]) { if (!layer.paint) layer.paint = {}; layer.paint[prop] = overrides[key]; } };
            if (layer.type === 'background') check('background-color');
            if (layer.type === 'fill') { check('fill-color'); check('fill-outline-color'); }
            if (layer.type === 'line') check('line-color');
            if (layer.type === 'symbol') { check('text-color'); check('text-halo-color'); }
        }
        return style;
    }

    function readColorsFromStyle(style, base) {
        const overrides = {};
        for (const group of getEditableLayers(base || 'dark')) {
            for (const entry of group.layers) {
                const layer = style.layers.find(l => l.id === entry.id);
                if (layer?.paint?.[entry.prop] != null) { let val = layer.paint[entry.prop]; if (typeof val === 'object' && !Array.isArray(val) && val.stops) val = val.stops[val.stops.length-1][1]; if (typeof val === 'string') overrides[entry.id+'::'+entry.prop] = colorToHex(val); }
            }
        }
        return overrides;
    }

    function readAllColorsFromStyle(style) {
        const propMap = { background: ['background-color'], fill: ['fill-color','fill-outline-color'], line: ['line-color'], symbol: ['text-color','text-halo-color'] };
        const overrides = {};
        for (const layer of style.layers) { const props = propMap[layer.type]; if (!props || !layer.paint) continue; for (const p of props) { let val = layer.paint[p]; if (val == null) continue; if (typeof val === 'object' && !Array.isArray(val) && val.stops) val = val.stops[val.stops.length-1][1]; if (typeof val === 'string') overrides[layer.id+'::'+p] = colorToHex(val); } }
        return overrides;
    }

    // ─── Map Integration ─────────────────────────────────────────────
    function teGetMap() {
        try { const m = (0, eval)('map'); if (m && typeof m.setStyle === 'function') return m; } catch {}
        if (typeof unsafeWindow !== 'undefined') { try { const m = unsafeWindow.eval('map'); if (m && typeof m.setStyle === 'function') return m; } catch {} }
        return null;
    }
    function teGetUserConfig() { if (typeof unsafeWindow !== 'undefined' && unsafeWindow.userConfig) return unsafeWindow.userConfig; if (window.userConfig) return window.userConfig; try { return JSON.parse(localStorage.getItem('userConfig')); } catch { return null; } }
    function inferBase(style) { if (!style?.layers) return 'dark'; const ids = new Set(style.layers.map(l => l.id)); if (ids.has('road_minor') || ids.has('highway-name-minor') || ids.has('boundary_3')) return 'light'; return 'dark'; }

    function setStyleCustomInPage(style) {
        const json = JSON.stringify(style).replace(/</g, '\\u003c');
        const code = 'styleCustom = ' + json;
        try { if (window.wrappedJSObject?.eval) { window.wrappedJSObject.eval(code); return true; } } catch {}
        try { const s = document.createElement('script'); s.textContent = '(function(){try{'+code+'}catch(e){}})();'; (document.head||document.documentElement).appendChild(s); s.remove(); return true; } catch {}
        try { (0, eval)(code); return true; } catch {}
        return false;
    }

    function applyStyleInPlace(map, style) {
        const liveStyle = map.getStyle(); if (!liveStyle?.layers) return false;
        const liveIds = new Set(liveStyle.layers.map(l => l.id));
        const allowed = new Set(['background-color','fill-color','fill-outline-color','line-color','text-color','text-halo-color','icon-opacity','raster-opacity']);
        for (const layer of style.layers||[]) { if (!liveIds.has(layer.id) || !layer.paint) continue; for (const [prop,value] of Object.entries(layer.paint)) { if (!allowed.has(prop)) continue; try { map.setPaintProperty(layer.id, prop, value); } catch {} } }
        return true;
    }

    function triggerRepaint() { try { (0, eval)('try{if(typeof drawCachedTilesOnMap==="function")drawCachedTilesOnMap()}catch(e){};try{if(typeof synchronize==="function")synchronize("partial")}catch(e){};try{if(typeof refresh==="function")refresh()}catch(e){}'); } catch {} }

    function applyStyleToMap(style, targetBase) {
        localStorage.setItem('customTheme', JSON.stringify(style));
        const uc = teGetUserConfig() || {}; uc.theme = 'custom'; localStorage.setItem('userConfig', JSON.stringify(uc));
        const map = teGetMap(); if (!map) { teShowToast('Map not found — please wait for the page to load.', true); return; }
        const liveStyle = map.getStyle ? map.getStyle() : null;
        const liveBase = inferBase(liveStyle); const desiredBase = targetBase || inferBase(style);
        if (liveStyle && liveBase === desiredBase) { applyStyleInPlace(map, style); setStyleCustomInPage(style); return; }
        const applyFull = (attempt = 0) => {
            try { if (map.isStyleLoaded && !map.isStyleLoaded()) { if (attempt < 40) return setTimeout(() => applyFull(attempt+1), 75); }
                map.setStyle(style); setStyleCustomInPage(style);
                const kick = () => { triggerRepaint(); setTimeout(triggerRepaint, 120); setTimeout(triggerRepaint, 450); };
                try { map.once('styledata', kick); } catch {} try { map.once('idle', kick); } catch {}
            } catch (e) { if (attempt < 40) return setTimeout(() => applyFull(attempt+1), 75);
                const json = JSON.stringify(style).replace(/</g, '\\u003c');
                try { const s = document.createElement('script'); s.textContent = '(function(){try{styleCustom='+json+';applyTheme("custom")}catch(e){}})();'; (document.head||document.documentElement).appendChild(s); s.remove(); triggerRepaint(); setTimeout(triggerRepaint,120); setTimeout(triggerRepaint,450); } catch { teShowToast('Failed to switch base style.', true); }
            }
        };
        applyFull();
    }

    function persistTheme(style) { localStorage.setItem('customTheme', JSON.stringify(style)); const uc = teGetUserConfig() || {}; uc.theme = 'custom'; localStorage.setItem('userConfig', JSON.stringify(uc)); }

    // ─── Toast ───────────────────────────────────────────────────────
    function teShowToast(msg, isError) {
        const existing = document.getElementById('gte-toast'); if (existing) existing.remove();
        const toast = document.createElement('div'); toast.id = 'gte-toast'; toast.textContent = msg;
        Object.assign(toast.style, { position:'fixed',bottom:'20px',left:'50%',transform:'translateX(-50%)',background:isError?'#f38ba8':'#a6e3a1',color:'#1e1e2e',padding:'8px 18px',borderRadius:'8px',fontSize:'12px',fontWeight:'600',zIndex:'100001',boxShadow:'0 4px 12px rgba(0,0,0,.3)',transition:'opacity .3s',fontFamily:"'Segoe UI',system-ui,sans-serif" });
        document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 2500);
    }

    // ─── HTML Utils ──────────────────────────────────────────────────
    function teEscHTML(str) { const d = document.createElement('div'); d.textContent = str; return d.innerHTML; }
    function teEscAttr(str) { return str.replace(/"/g,'&quot;').replace(/'/g,'&#39;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }

    // ─── CSS ─────────────────────────────────────────────────────────
    function teInjectCSS() {
        if (document.getElementById('gte-style')) return;
        const css = document.createElement('style'); css.id = 'gte-style';
        css.textContent = `
            #gte-modal { position:fixed; z-index:100000; background:#1e1e2e; color:#cdd6f4; border:1px solid #45475a; border-radius:12px; box-shadow:0 8px 32px rgba(0,0,0,.55); width:380px; max-height:80vh; display:flex; flex-direction:column; font-family:'Segoe UI',system-ui,sans-serif; font-size:13px; user-select:none; }
            #gte-modal.gte-hidden { display:none; }
            #gte-titlebar { display:flex; align-items:center; justify-content:space-between; padding:10px 14px; background:#181825; border-radius:12px 12px 0 0; cursor:grab; flex-shrink:0; }
            #gte-titlebar:active { cursor:grabbing; }
            #gte-titlebar h2 { margin:0; font-size:14px; font-weight:600; color:#cba6f7; }
            #gte-close-btn { background:none; border:none; color:#6c7086; cursor:pointer; font-size:18px; line-height:1; padding:0 4px; }
            #gte-close-btn:hover { color:#f38ba8; }
            #gte-tabs { display:flex; border-bottom:1px solid #313244; flex-shrink:0; }
            .gte-tab { flex:1; padding:8px 0; text-align:center; background:none; border:none; color:#6c7086; cursor:pointer; font-size:12px; font-weight:500; border-bottom:2px solid transparent; transition:color .15s,border-color .15s; }
            .gte-tab:hover { color:#bac2de; }
            .gte-tab.gte-active { color:#cba6f7; border-bottom-color:#cba6f7; }
            #gte-body { overflow-y:auto; padding:12px 14px; flex:1; }
            #gte-body::-webkit-scrollbar { width:6px; }
            #gte-body::-webkit-scrollbar-thumb { background:#45475a; border-radius:3px; }
            .gte-panel { display:none; }
            .gte-panel.gte-active { display:block; }
            .gte-group-header { font-size:11px; font-weight:700; text-transform:uppercase; color:#89b4fa; margin:12px 0 6px; letter-spacing:.5px; }
            .gte-group-header:first-child { margin-top:0; }
            .gte-color-row { display:flex; align-items:center; justify-content:space-between; padding:4px 0; }
            .gte-color-label { font-size:12px; color:#a6adc8; }
            .gte-color-input-wrap { display:flex; align-items:center; gap:6px; }
            .gte-color-input { -webkit-appearance:none; appearance:none; width:32px; height:24px; border:1px solid #45475a; border-radius:4px; cursor:pointer; background:none; padding:0; }
            .gte-color-input::-webkit-color-swatch-wrapper { padding:0; }
            .gte-color-input::-webkit-color-swatch { border:none; border-radius:3px; }
            .gte-hex-display { font-family:'Cascadia Code','Consolas',monospace; font-size:11px; color:#6c7086; width:62px; text-align:right; }
            .gte-hex-display.gte-hidden-color { color:#f38ba8; font-style:italic; }
            .gte-vis-btn { background:none; border:none; cursor:pointer; font-size:14px; padding:0 2px; line-height:1; opacity:0.6; transition:opacity .15s; }
            .gte-vis-btn:hover { opacity:1; }
            .gte-vis-btn.gte-layer-hidden { opacity:0.35; }
            .gte-reset-btn { background:none; border:none; cursor:pointer; font-size:11px; padding:0 2px; line-height:1; opacity:0.5; transition:opacity .15s; color:#89b4fa; }
            .gte-reset-btn:hover { opacity:1; }
            .gte-name-row { display:flex; gap:8px; margin-bottom:12px; }
            .gte-name-input { flex:1; padding:6px 10px; border-radius:6px; background:#313244; color:#cdd6f4; border:1px solid #45475a; font-size:13px; outline:none; }
            .gte-name-input:focus { border-color:#cba6f7; }
            .gte-name-input::placeholder { color:#585b70; }
            .gte-preview-row { display:flex; align-items:center; gap:8px; margin-bottom:10px; padding:6px 8px; background:#313244; border-radius:6px; }
            .gte-preview-row input[type=checkbox] { accent-color:#cba6f7; width:15px; height:15px; cursor:pointer; }
            .gte-preview-row label { font-size:12px; color:#a6adc8; cursor:pointer; user-select:none; }
            .gte-mode-toggle { display:flex; align-items:center; gap:8px; margin-bottom:10px; padding:6px 8px; background:#313244; border-radius:6px; }
            .gte-mode-toggle span { font-size:12px; color:#a6adc8; }
            .gte-mode-toggle span.gte-mode-active { color:#cba6f7; font-weight:600; }
            .gte-mode-switch { position:relative; width:36px; height:18px; background:#585b70; border-radius:9px; cursor:pointer; transition:background .2s; border:none; padding:0; }
            .gte-mode-switch.gte-on { background:#cba6f7; }
            .gte-mode-switch::after { content:''; position:absolute; top:2px; left:2px; width:14px; height:14px; background:#fff; border-radius:50%; transition:transform .2s; }
            .gte-mode-switch.gte-on::after { transform:translateX(18px); }
            .gte-btn { padding:7px 14px; border:none; border-radius:6px; font-size:12px; font-weight:600; cursor:pointer; transition:filter .15s; }
            .gte-btn:hover { filter:brightness(1.15); }
            .gte-btn-primary { background:#cba6f7; color:#1e1e2e; }
            .gte-btn-secondary { background:#45475a; color:#cdd6f4; }
            .gte-btn-danger { background:#f38ba8; color:#1e1e2e; }
            .gte-btn-sm { padding:4px 10px; font-size:11px; }
            .gte-btn-row { display:flex; gap:8px; margin-top:12px; flex-wrap:wrap; }
            .gte-theme-card { display:flex; align-items:center; justify-content:space-between; padding:8px 10px; margin-bottom:6px; background:#313244; border-radius:8px; border:1px solid transparent; transition:border-color .15s; }
            .gte-theme-card.gte-active-theme { border-color:#a6e3a1; }
            .gte-theme-card-name { display:flex; align-items:center; gap:6px; font-size:13px; font-weight:500; color:#cdd6f4; overflow:hidden; max-width:180px; }
            .gte-theme-name-text { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
            .gte-theme-badge { font-size:10px; font-weight:700; letter-spacing:0.2px; text-transform:uppercase; border-radius:999px; padding:2px 6px; line-height:1; flex-shrink:0; border:1px solid transparent; }
            .gte-theme-badge-light { color:#1e1e2e; background:#f9e2af; border-color:#eabf5d; }
            .gte-theme-badge-dark { color:#cdd6f4; background:#313244; border-color:#585b70; }
            .gte-theme-card-actions { display:flex; gap:4px; }
            .gte-empty-msg { color:#585b70; font-size:12px; text-align:center; padding:20px 0; }
            .gte-io-section { margin-top:14px; padding-top:12px; border-top:1px solid #313244; }
        `;
        document.head.appendChild(css);
    }

    // ─── Modal ───────────────────────────────────────────────────────
    let teModal = null;

    function teBuildModal() {
        const modal = document.createElement('div'); modal.id = 'gte-modal'; modal.className = 'gte-hidden';
        modal.innerHTML = '<div id="gte-titlebar"><h2>\ud83c\udfa8 Theme Editor</h2><button id="gte-close-btn">&times;</button></div><div id="gte-tabs"><button class="gte-tab gte-active" data-panel="editor">Editor</button><button class="gte-tab" data-panel="manager">My Themes</button></div><div id="gte-body"><div id="gte-panel-editor" class="gte-panel gte-active"></div><div id="gte-panel-manager" class="gte-panel"></div></div>';
        document.body.appendChild(modal);
        modal.style.top = '60px'; modal.style.right = '20px';
        modal.querySelector('#gte-close-btn').addEventListener('click', () => modal.classList.add('gte-hidden'));
        modal.querySelectorAll('.gte-tab').forEach(tab => tab.addEventListener('click', () => {
            modal.querySelectorAll('.gte-tab').forEach(t => t.classList.remove('gte-active'));
            modal.querySelectorAll('.gte-panel').forEach(p => p.classList.remove('gte-active'));
            tab.classList.add('gte-active'); modal.querySelector('#gte-panel-'+tab.dataset.panel).classList.add('gte-active');
            if (tab.dataset.panel === 'manager') renderManager();
        }));
        // Dragging
        let ox=0,oy=0,sx=0,sy=0;
        const handle = modal.querySelector('#gte-titlebar');
        handle.addEventListener('mousedown', e => { if (e.target.tagName==='BUTTON') return; e.preventDefault(); sx=e.clientX; sy=e.clientY; document.addEventListener('mousemove',drag); document.addEventListener('mouseup',dragEnd); });
        function drag(e) { ox=sx-e.clientX; oy=sy-e.clientY; sx=e.clientX; sy=e.clientY; modal.style.top=Math.max(0,Math.min(window.innerHeight-50,modal.offsetTop-oy))+'px'; modal.style.left=Math.max(0,Math.min(window.innerWidth-100,modal.offsetLeft-ox))+'px'; modal.style.right='auto'; }
        function dragEnd() { document.removeEventListener('mousemove',drag); document.removeEventListener('mouseup',dragEnd); }
        return modal;
    }

    // ─── Editor Panel ────────────────────────────────────────────────
    let curOverrides = {}, curEditName = '', curBase = 'dark', livePreview = false, previewTimer = null, simpleMode = true;

    function scheduleLivePreview() {
        if (!livePreview) return; clearTimeout(previewTimer);
        previewTimer = setTimeout(async () => { const base = await getBaseStyle(curBase); if (!base) return; applyStyleToMap(buildStyle(base, curOverrides), curBase); }, 120);
    }

    function renderEditor(overrides, editName, base) {
        curOverrides = overrides || {}; curEditName = editName || ''; curBase = base || 'dark';
        const panel = document.getElementById('gte-panel-editor'); panel.innerHTML = '';

        // Name + base selector
        const nameRow = document.createElement('div'); nameRow.className = 'gte-name-row';
        nameRow.innerHTML = '<input type="text" class="gte-name-input" id="gte-theme-name" placeholder="Theme name\u2026" value="'+teEscAttr(curEditName)+'" maxlength="50"><select id="gte-base-select" class="gte-name-input" style="flex:0 0 auto;width:auto;padding:6px 8px;"><option value="dark" '+(curBase==='dark'?'selected':'')+'>Dark base</option><option value="light" '+(curBase==='light'?'selected':'')+'>Light base</option></select>';
        nameRow.querySelector('#gte-base-select').addEventListener('change', e => { curBase = e.target.value; renderEditor(curOverrides, curEditName, curBase); scheduleLivePreview(); });
        panel.appendChild(nameRow);

        // Live preview
        const previewRow = document.createElement('div'); previewRow.className = 'gte-preview-row';
        previewRow.innerHTML = '<input type="checkbox" id="gte-live-preview" '+(livePreview?'checked':'')+'><label for="gte-live-preview">Live preview</label>';
        previewRow.querySelector('#gte-live-preview').addEventListener('change', e => { livePreview = e.target.checked; if (livePreview) scheduleLivePreview(); });
        panel.appendChild(previewRow);

        // Simple/Full toggle
        const modeRow = document.createElement('div'); modeRow.className = 'gte-mode-toggle';
        modeRow.innerHTML = '<span class="'+(simpleMode?'gte-mode-active':'')+'">Simple</span><button type="button" class="gte-mode-switch '+(simpleMode?'':'gte-on')+'" id="gte-mode-switch"></button><span class="'+(simpleMode?'':'gte-mode-active')+'">Full</span>';
        modeRow.querySelector('#gte-mode-switch').addEventListener('click', () => { simpleMode = !simpleMode; renderEditor(curOverrides, curEditName, curBase); });
        panel.appendChild(modeRow);

        // Color rows
        const layerGroups = simpleMode ? getSimpleLayers(curBase) : getEditableLayers(curBase);
        for (const group of layerGroups) {
            const header = document.createElement('div'); header.className = 'gte-group-header'; header.textContent = group.group; panel.appendChild(header);
            for (const entry of group.layers) {
                const keys = simpleMode ? entry.keys : [entry.id+'::'+entry.prop];
                const firstKey = keys[0];
                const currentColor = curOverrides[firstKey] || getDefaultColor(firstKey, curBase);
                const isHidden = currentColor === 'transparent';
                const displayColor = isHidden ? getDefaultColor(firstKey, curBase) : currentColor;
                const row = document.createElement('div'); row.className = 'gte-color-row';
                row.innerHTML = '<span class="gte-color-label">'+teEscHTML(simpleMode ? entry.label : entry.label)+'</span><div class="gte-color-input-wrap"><button type="button" class="gte-vis-btn '+(isHidden?'gte-layer-hidden':'')+'" title="Toggle visibility">'+(isHidden?'\ud83d\udeab':'\ud83d\udc41\ufe0f')+'</button><span class="gte-hex-display '+(isHidden?'gte-hidden-color':'')+'">'+(isHidden?'hidden':currentColor)+'</span><input type="color" class="gte-color-input" value="'+displayColor+'" '+(isHidden?'disabled':'')+'><button type="button" class="gte-reset-btn" title="Reset to default">\u21bb</button></div>';
                const colorInput = row.querySelector('.gte-color-input'), hexDisplay = row.querySelector('.gte-hex-display'), visBtn = row.querySelector('.gte-vis-btn'), resetBtn = row.querySelector('.gte-reset-btn');
                colorInput.addEventListener('input', e => { for (const k of keys) curOverrides[k] = e.target.value; hexDisplay.textContent = e.target.value; scheduleLivePreview(); });
                visBtn.addEventListener('click', () => {
                    const nowHidden = curOverrides[firstKey] === 'transparent';
                    if (nowHidden) { const restored = colorInput.value; for (const k of keys) curOverrides[k] = restored; hexDisplay.textContent = restored; hexDisplay.classList.remove('gte-hidden-color'); colorInput.disabled = false; visBtn.textContent = '\ud83d\udc41\ufe0f'; visBtn.classList.remove('gte-layer-hidden'); }
                    else { for (const k of keys) curOverrides[k] = 'transparent'; hexDisplay.textContent = 'hidden'; hexDisplay.classList.add('gte-hidden-color'); colorInput.disabled = true; visBtn.textContent = '\ud83d\udeab'; visBtn.classList.add('gte-layer-hidden'); }
                    scheduleLivePreview();
                });
                resetBtn.addEventListener('click', () => { const def = getDefaultColor(firstKey, curBase); for (const k of keys) curOverrides[k] = def; colorInput.value = def; hexDisplay.textContent = def; hexDisplay.classList.remove('gte-hidden-color'); colorInput.disabled = false; visBtn.textContent = '\ud83d\udc41\ufe0f'; visBtn.classList.remove('gte-layer-hidden'); scheduleLivePreview(); });
                panel.appendChild(row);
            }
        }

        // Action buttons
        const btnRow = document.createElement('div'); btnRow.className = 'gte-btn-row';
        btnRow.innerHTML = '<button class="gte-btn gte-btn-primary" id="gte-save-apply">Save &amp; Apply</button><button class="gte-btn gte-btn-secondary" id="gte-load-current">Load Current</button><button class="gte-btn gte-btn-secondary" id="gte-export-json">Export JSON</button><button class="gte-btn gte-btn-secondary" id="gte-import-json-btn">Import JSON</button><input type="file" id="gte-import-json-file" accept=".json" style="display:none">';
        panel.appendChild(btnRow);

        panel.querySelector('#gte-save-apply').addEventListener('click', async () => {
            const nameInput = document.getElementById('gte-theme-name'); const name = nameInput.value.trim();
            if (!name) { nameInput.style.borderColor = '#f38ba8'; nameInput.focus(); setTimeout(() => nameInput.style.borderColor = '', 1500); return; }
            const themes = teLoadThemes(); themes[name] = { overrides: { ...curOverrides }, base: curBase, createdAt: themes[name]?.createdAt || Date.now(), updatedAt: Date.now() };
            teSaveThemes(themes); teSetActive(name); curEditName = name;
            const base = await getBaseStyle(curBase); if (!base) return;
            const style = buildStyle(base, curOverrides); applyStyleToMap(style, curBase); persistTheme(style);
            teShowToast('Theme "'+name+'" saved & applied!');
        });
        panel.querySelector('#gte-load-current').addEventListener('click', async () => {
            const map = teGetMap(); if (!map) return teShowToast('Map not ready.', true);
            const style = map.getStyle(); if (!style) return teShowToast('No style loaded.', true);
            renderEditor(readColorsFromStyle(style, curBase), curEditName, curBase); teShowToast('Loaded colors from current map.');
        });
        panel.querySelector('#gte-export-json').addEventListener('click', async () => {
            const base = await getBaseStyle(curBase); if (!base) return;
            const style = buildStyle(base, curOverrides); const name = document.getElementById('gte-theme-name').value.trim() || 'custom_theme'; style.name = name;
            const blob = new Blob([JSON.stringify(style, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = name.replace(/[^a-z0-9_-]/gi, '_')+'.json'; a.click(); URL.revokeObjectURL(a.href);
        });
        panel.querySelector('#gte-import-json-btn').addEventListener('click', () => panel.querySelector('#gte-import-json-file').click());
        panel.querySelector('#gte-import-json-file').addEventListener('change', async e => {
            const file = e.target.files[0]; if (!file) return;
            try { const text = await file.text(); const style = JSON.parse(text); if (!style.layers || !Array.isArray(style.layers)) return teShowToast('Invalid theme JSON.', true);
                const overrides = readAllColorsFromStyle(style); const name = style.name || file.name.replace(/\.json$/i, '');
                const bg = overrides['background::background-color']; const detectedBase = bg ? (isColorDark(bg) ? 'dark' : 'light') : 'dark';
                renderEditor(overrides, name, detectedBase); teShowToast('Imported "'+name+'" — adjust and Save & Apply.');
            } catch { teShowToast('Failed to parse JSON file.', true); }
            e.target.value = '';
        });
    }

    // ─── Manager Panel ───────────────────────────────────────────────
    function renderManager() {
        const panel = document.getElementById('gte-panel-manager');
        const themes = teLoadThemes(); const activeTheme = teGetActive();
        const names = Object.keys(themes).sort((a, b) => {
            const pa = a === 'Default' ? 0 : a === 'Default Dark' ? 1 : 2;
            const pb = b === 'Default' ? 0 : b === 'Default Dark' ? 1 : 2;
            return pa !== pb ? pa - pb : a.localeCompare(b);
        });
        let html = '';
        if (names.length === 0) { html = '<div class="gte-empty-msg">No saved themes yet.<br>Use the Editor tab to create one!</div>'; }
        else { for (const name of names) { const theme = themes[name]; const isActive = name === activeTheme; const isBundled = theme.bundled; const isLight = (theme.base||'dark') === 'light';
            html += '<div class="gte-theme-card '+(isActive?'gte-active-theme':'')+'"><span class="gte-theme-card-name" title="'+teEscAttr(name)+'"><span class="gte-theme-name-text">'+teEscHTML(name)+(isActive?' \u2713':'')+(isBundled?' \ud83d\udccc':'')+'</span><span class="gte-theme-badge '+(isLight?'gte-theme-badge-light':'gte-theme-badge-dark')+'">'+(isLight?'Light':'Dark')+'</span></span><div class="gte-theme-card-actions"><button class="gte-btn gte-btn-primary gte-btn-sm" data-action="apply" data-name="'+teEscAttr(name)+'">Apply</button><button class="gte-btn gte-btn-secondary gte-btn-sm" data-action="edit" data-name="'+teEscAttr(name)+'">Edit</button>'+(!isBundled?'<button class="gte-btn gte-btn-danger gte-btn-sm" data-action="delete" data-name="'+teEscAttr(name)+'">Delete</button>':'<span class="gte-btn gte-btn-danger gte-btn-sm" style="opacity:0.5;cursor:not-allowed;" title="Built-in">Delete</span>')+'</div></div>'; } }
        html += '<div class="gte-io-section"><button class="gte-btn gte-btn-secondary" id="gte-restore-default" style="width:100%">Restore Default Theme</button></div>';
        panel.innerHTML = html;
        panel.querySelectorAll('[data-action]').forEach(btn => btn.addEventListener('click', async () => {
            const action = btn.dataset.action, name = btn.dataset.name;
            if (action === 'apply') await teApplyByName(name);
            if (action === 'edit') teEditTheme(name);
            if (action === 'delete') teDeleteTheme(name);
        }));
        const restoreBtn = panel.querySelector('#gte-restore-default');
        if (restoreBtn) restoreBtn.addEventListener('click', async () => {
            teSetActive(''); localStorage.removeItem('customTheme');
            const uc = teGetUserConfig(); if (uc) uc.theme = 'default'; localStorage.setItem('userConfig', JSON.stringify(uc));
            try { const base = await getBaseStyle('light'); if (base) { const map = teGetMap(); if (map) map.setStyle(base); } } catch {}
            renderManager(); teShowToast('Restored default theme.');
        });
    }

    async function teApplyByName(name) {
        const themes = teLoadThemes(); const theme = themes[name]; if (!theme) return;
        const base = await getBaseStyle(theme.base || 'dark'); if (!base) return;
        const style = buildStyle(base, theme.overrides); applyStyleToMap(style, theme.base || 'dark');
        teSetActive(name); persistTheme(style); renderManager(); teShowToast('Applied "'+name+'".');
    }

    function teEditTheme(name) {
        const themes = teLoadThemes(); const theme = themes[name]; if (!theme) return;
        teModal.querySelectorAll('.gte-tab').forEach(t => t.classList.remove('gte-active'));
        teModal.querySelectorAll('.gte-panel').forEach(p => p.classList.remove('gte-active'));
        teModal.querySelector('[data-panel="editor"]').classList.add('gte-active');
        teModal.querySelector('#gte-panel-editor').classList.add('gte-active');
        renderEditor(theme.overrides, name, theme.base || 'dark');
    }

    function teDeleteTheme(name) {
        const themes = teLoadThemes(); if (themes[name]?.bundled) { teShowToast('Cannot delete built-in themes.', true); return; }
        if (!confirm('Delete theme "'+name+'"?')) return;
        delete themes[name]; teSaveThemes(themes); if (teGetActive() === name) teSetActive(''); renderManager(); teShowToast('Deleted "'+name+'".');
    }

    // ─── Init ────────────────────────────────────────────────────────
    teInjectCSS();
    teModal = teBuildModal();
    renderEditor({}, '', 'dark');

    // Expose API for dropdown flyout
    _themeEditor = {
        loadThemes: teLoadThemes,
        getActiveThemeName: teGetActive,
        applyThemeByName: teApplyByName,
        toggleModal: () => {
            const isHidden = teModal.classList.contains('gte-hidden');
            if (isHidden) { teModal.classList.remove('gte-hidden'); if (!document.getElementById('gte-theme-name')) renderEditor({}, ''); }
            else teModal.classList.add('gte-hidden');
        }
    };

    // Re-apply active theme on load
    (async () => {
        let tries = 0;
        while (!teGetMap() && tries < 60) { await new Promise(r => setTimeout(r, 500)); tries++; }
        const activeName = teGetActive();
        if (activeName) {
            const themes = teLoadThemes();
            if (themes[activeName]) {
                const themeBase = themes[activeName].base || 'dark';
                const base = await getBaseStyle(themeBase);
                if (base) { const style = buildStyle(base, themes[activeName].overrides); applyStyleToMap(style, themeBase); persistTheme(style); }
            }
        }
    })();

            })();
            _featureStatus.themeEditor = 'ok';
            console.log('[GeoPixelcons++] \u2705 Theme Editor loaded');
        } catch (err) {
            _featureStatus.themeEditor = 'error';
            dbgPush(`Theme Editor init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Theme Editor' });
            console.error('[GeoPixelcons++] \u274c Theme Editor failed:', err);
        }
    }

    // ============================================================
    //  MAP MARKERS
    // ============================================================
    if (_settings.mapMarkers) {
        try {
            (() => {
                const MM_META_KEY = 'gpc_mapMarkers_v1';
                const MM_OLD_IMG_KEY = (id) => 'gpc_mmImg_' + id; // legacy GM key (migration only)
                const HANDLE_R    = 6; // fixed px radius — consistent at all zoom levels

                let markers  = [];
                const imgCache    = new Map(); // id → HTMLImageElement (for image-type markers)
                const dataUrlCache = new Map(); // id → dataUrl (GIF markers only)
                // ── Canvas (image markers) ───────────────────────────────
                const mmCanvas = document.createElement('canvas');
                mmCanvas.id = 'gpc-markers-canvas';
                mmCanvas.className = 'inset-0 absolute pointer-events-none';
                document.body.appendChild(mmCanvas);

                // ── DOM overlay (GIF markers) ────────────────────────────
                const overlayEls = new Map(); // id → HTMLImageElement
                const mmOverlay = document.createElement('div');
                mmOverlay.id = 'gpc-markers-overlay';
                Object.assign(mmOverlay.style, {
                    position: 'absolute', inset: '0', pointerEvents: 'none',
                    overflow: 'hidden', zIndex: '2',
                });
                // Append after map container is available; done in waitForMapMM callback below

                let mmModal    = null;
                let placingId  = null; // id of marker currently being placed
                let editingId  = null; // id of marker currently being edited
                let _drag      = null; // active drag state
                let mmCompact  = true; // compact card view toggle
                const expandedCards = new Set(); // marker ids expanded in compact view
                let listDragSrc = null; // drag-to-sort source index

                // ── IndexedDB storage for image/media data ────────────────
                // Replaces GM_setValue for image blobs to stay below the 64 MiB
                // Chrome extension message-passing limit.
                const MM_IDB_NAME  = 'gpc_mapMarkers_imgs';
                const MM_IDB_STORE = 'images';
                let _mmDb = null;

                function openMmDb() {
                    if (_mmDb) return Promise.resolve(_mmDb);
                    return new Promise((resolve, reject) => {
                        const req = indexedDB.open(MM_IDB_NAME, 1);
                        req.onupgradeneeded = (e) => {
                            e.target.result.createObjectStore(MM_IDB_STORE);
                        };
                        req.onsuccess = (e) => { _mmDb = e.target.result; resolve(_mmDb); };
                        req.onerror   = (e) => reject(e.target.error);
                    });
                }
                async function mmDbGet(id) {
                    const db = await openMmDb();
                    return new Promise((resolve) => {
                        const req = db.transaction(MM_IDB_STORE, 'readonly').objectStore(MM_IDB_STORE).get(id);
                        req.onsuccess = () => resolve(req.result || null);
                        req.onerror   = () => resolve(null);
                    });
                }
                async function mmDbSet(id, dataUrl) {
                    const db = await openMmDb();
                    return new Promise((resolve, reject) => {
                        const req = db.transaction(MM_IDB_STORE, 'readwrite').objectStore(MM_IDB_STORE).put(dataUrl, id);
                        req.onsuccess = () => resolve();
                        req.onerror   = (e) => reject(e.target.error);
                    });
                }
                async function mmDbDelete(id) {
                    const db = await openMmDb();
                    return new Promise((resolve) => {
                        const tx = db.transaction(MM_IDB_STORE, 'readwrite');
                        tx.objectStore(MM_IDB_STORE).delete(id);
                        tx.oncomplete = () => resolve();
                        tx.onerror    = () => resolve();
                    });
                }

                // ── Metadata (small JSON — stays in GM_setValue) ──────────
                function loadMeta() {
                    try { markers = JSON.parse(GM_getValue(MM_META_KEY, '[]')); }
                    catch { markers = []; }
                }
                function saveMeta() { GM_setValue(MM_META_KEY, JSON.stringify(markers)); }

                function loadImg(id, dataUrl) {
                    return new Promise(resolve => {
                        const img = new Image();
                        img.onload  = () => { imgCache.set(id, img); resolve(img); };
                        img.onerror = () => resolve(null);
                        img.src = dataUrl;
                    });
                }

                // ── Migration: move old GM_setValue image data → IndexedDB ─
                async function migrateOldGmKeys() {
                    let migrated = 0;
                    for (const m of markers) {
                        const oldVal = GM_getValue(MM_OLD_IMG_KEY(m.id), null);
                        if (oldVal) {
                            try {
                                await mmDbSet(m.id, oldVal);
                                try { GM_deleteValue(MM_OLD_IMG_KEY(m.id)); } catch { GM_setValue(MM_OLD_IMG_KEY(m.id), ''); }
                                migrated++;
                            } catch (err) {
                                console.warn('[GeoPixelcons++] Map Markers: migration failed for', m.id, err);
                            }
                        }
                    }
                    if (migrated > 0) console.log(`[GeoPixelcons++] Map Markers: migrated ${migrated} image(s) from GM_setValue to IndexedDB`);
                }

                async function preloadAll() {
                    await migrateOldGmKeys();
                    for (const m of markers) {
                        if (isGifType(m)) {
                            if (!dataUrlCache.has(m.id)) {
                                const d = await mmDbGet(m.id);
                                if (d) dataUrlCache.set(m.id, d);
                            }
                            // Also load into imgCache so naturalWidth/naturalHeight
                            // are available for aspect-ratio calculations
                            if (!imgCache.has(m.id)) {
                                const d = dataUrlCache.get(m.id) || await mmDbGet(m.id);
                                if (d) await loadImg(m.id, d);
                            }
                        } else if (!imgCache.has(m.id)) {
                            const d = await mmDbGet(m.id);
                            if (d) await loadImg(m.id, d);
                        }
                    }
                }

                // ── Media type helpers ────────────────────────────────────
                function isImageType(m) { return !m.mediaType || m.mediaType === 'image'; }
                function isGifType(m)   { return m.mediaType === 'gif'; }

                // ── Grid helpers ──────────────────────────────────────────
                function getGSize() {
                    const w = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
                    return w.gridSize || (typeof gridSize !== 'undefined' ? gridSize : 25);
                }

                // SW-corner grid rect → screen rect { x, y, w, h }
                // Follows the same half-cell offset convention as geo++ censors.
                function markerScreenRect(m) {
                    const g = getGSize();
                    const nwMerc = [(m.gridX - 0.5) * g, (m.gridY - 0.5 + m.gridH) * g];
                    const seMerc = [(m.gridX - 0.5 + m.gridW) * g, (m.gridY - 0.5) * g];
                    const nw = map.project(turf.toWgs84(nwMerc));
                    const se = map.project(turf.toWgs84(seMerc));
                    return { x: nw.x, y: nw.y, w: se.x - nw.x, h: se.y - nw.y };
                }

                // CSS-pixel variant used exclusively for DOM overlay positioning.
                // map.project() returns CSS pixels relative to the map container — use directly.
                function markerCssRect(m) {
                    const g = getGSize();
                    const nwMerc = [(m.gridX - 0.5) * g, (m.gridY - 0.5 + m.gridH) * g];
                    const seMerc = [(m.gridX - 0.5 + m.gridW) * g, (m.gridY - 0.5) * g];
                    const nw = map.project(turf.toWgs84(nwMerc));
                    const se = map.project(turf.toWgs84(seMerc));
                    return { x: nw.x, y: nw.y, w: se.x - nw.x, h: se.y - nw.y };
                }

                function handlePositions(r) {
                    return {
                        nw: { x: r.x,          y: r.y          },
                        n:  { x: r.x + r.w / 2, y: r.y          },
                        ne: { x: r.x + r.w,     y: r.y          },
                        e:  { x: r.x + r.w,     y: r.y + r.h / 2 },
                        se: { x: r.x + r.w,     y: r.y + r.h   },
                        s:  { x: r.x + r.w / 2, y: r.y + r.h   },
                        sw: { x: r.x,           y: r.y + r.h   },
                        w:  { x: r.x,           y: r.y + r.h / 2 },
                    };
                }

                function screenToGrid(cx, cy) {
                    const g  = getGSize();
                    const br = mmCanvas.getBoundingClientRect();
                    const ll = map.unproject([cx - br.left, cy - br.top]);
                    const mc = turf.toMercator([ll.lng, ll.lat]);
                    return { gridX: Math.round(mc[0] / g), gridY: Math.round(mc[1] / g) };
                }

                // ── DOM overlay helpers (GIF markers) ─────────────────────
                function updateOverlayEl(m) {
                    if (!isGifType(m)) return;
                    let el = overlayEls.get(m.id);
                    if (!m.visible || m.gridX == null) {
                        if (el) el.style.display = 'none';
                        return;
                    }
                    if (!el) {
                        el = document.createElement('img');
                        Object.assign(el.style, { position: 'absolute', display: 'block', objectFit: 'fill',
                            maxWidth: 'none', maxHeight: 'none', imageRendering: 'pixelated' });
                        overlayEls.set(m.id, el);
                        mmOverlay.appendChild(el);
                    }
                    // Always sync src in case dataUrlCache was populated after element creation
                    const src = dataUrlCache.get(m.id) || '';
                    if (el.src !== src) el.src = src;
                    if (!src) { el.style.display = 'none'; return; }
                    const r = markerCssRect(m);
                    el.style.display = (r.w > 0 && r.h > 0) ? 'block' : 'none';
                    el.style.left    = r.x + 'px';
                    el.style.top     = r.y + 'px';
                    el.style.width   = r.w + 'px';
                    el.style.height  = r.h + 'px';
                    el.style.opacity = m.opacity ?? 1;
                }

                function updateAllOverlayEls() {
                    for (const m of markers) updateOverlayEl(m);
                }

                function removeOverlayEl(id) {
                    const el = overlayEls.get(id);
                    if (el) { el.remove(); overlayEls.delete(id); }
                    dataUrlCache.delete(id);
                }

                // ── Drawing ───────────────────────────────────────────────
                function redraw() {
                    const pix = document.getElementById('pixel-canvas');
                    if (!pix) return;
                    if (typeof map === 'undefined' || !map || typeof map.project !== 'function') return;
                    mmCanvas.width  = pix.width;
                    mmCanvas.height = pix.height;
                    const ctx = mmCanvas.getContext('2d');
                    ctx.imageSmoothingEnabled = false;
                    ctx.clearRect(0, 0, mmCanvas.width, mmCanvas.height);

                    for (const m of markers) {
                        if (!m.visible || m.gridX == null) continue;
                        if (!isImageType(m)) continue; // gif/video use DOM overlay
                        const img = imgCache.get(m.id);
                        if (!img || !img.complete) continue;
                        const r = markerScreenRect(m);
                        if (r.w <= 0 || r.h <= 0) continue;
                        if (r.x + r.w < 0 || r.x > mmCanvas.width ||
                            r.y + r.h < 0 || r.y > mmCanvas.height) continue;
                        ctx.globalAlpha = m.opacity ?? 1;
                        ctx.drawImage(img, r.x, r.y, r.w, r.h);
                        ctx.globalAlpha = 1;
                        if (editingId === m.id) drawHandles(ctx, r);
                    }

                    // Edit handles for non-image markers (still use canvas overlay)
                    if (editingId) {
                        const em = markers.find(x => x.id === editingId);
                        if (em && em.gridX != null && !isImageType(em)) drawHandles(ctx, markerScreenRect(em));
                    }

                    // Placement preview
                    if (placingId && _drag && _drag.preview) {
                        const p   = _drag.preview;
                        const pm  = markers.find(m => m.id === placingId);
                        const pi  = pm && isImageType(pm) && imgCache.get(pm.id);
                        if (pi) { ctx.globalAlpha = 0.55; ctx.drawImage(pi, p.x, p.y, p.w, p.h); ctx.globalAlpha = 1; }
                        ctx.strokeStyle = '#3b82f6'; ctx.lineWidth = 2;
                        ctx.setLineDash([6, 3]); ctx.strokeRect(p.x, p.y, p.w, p.h); ctx.setLineDash([]);
                    }

                    updateAllOverlayEls();
                }

                function drawHandles(ctx, r) {
                    ctx.save();
                    ctx.strokeStyle = '#3b82f6'; ctx.lineWidth = 1.5;
                    ctx.setLineDash([5, 3]); ctx.strokeRect(r.x, r.y, r.w, r.h); ctx.setLineDash([]);
                    const hp = handlePositions(r);
                    for (const pos of Object.values(hp)) {
                        ctx.fillStyle   = '#fff';
                        ctx.strokeStyle = '#3b82f6';
                        ctx.lineWidth   = 2;
                        ctx.beginPath();
                        ctx.arc(pos.x, pos.y, HANDLE_R, 0, Math.PI * 2);
                        ctx.fill(); ctx.stroke();
                    }
                    ctx.restore();
                }

                // ── Map wiring ────────────────────────────────────────────
                function waitForMapMM(cb) {
                    let t = 0;
                    function chk() {
                        if (typeof map !== 'undefined' && map && map.on) cb();
                        else if (t++ < 200) setTimeout(chk, 100);
                    }
                    chk();
                }
                waitForMapMM(() => {
                    map.getContainer().appendChild(mmOverlay);
                    ['move', 'rotate', 'zoom'].forEach(ev => map.on(ev, redraw));
                    new ResizeObserver(redraw).observe(map.getContainer());
                    map.once('load', redraw);
                    redraw();
                });

                // ── Mode helpers ──────────────────────────────────────────
                function enterPlace(id) {
                    exitEdit();
                    placingId = id;
                    mmCanvas.style.pointerEvents = 'auto';
                    mmCanvas.style.cursor = 'crosshair';
                    mmNotify('Drag to define image size. Hold Shift to lock aspect ratio.');
                }
                function exitPlace() {
                    placingId = null; _drag = null;
                    mmCanvas.style.pointerEvents = 'none'; mmCanvas.style.cursor = '';
                    redraw();
                }
                function enterEdit(id) {
                    exitPlace();
                    editingId = id;
                    mmCanvas.style.pointerEvents = 'auto'; mmCanvas.style.cursor = 'default';
                    redraw();
                }
                function exitEdit() {
                    editingId = null; _drag = null;
                    mmCanvas.style.pointerEvents = 'none'; mmCanvas.style.cursor = '';
                    redraw();
                    refreshModal(); // sync card buttons ("Done" → "Edit")
                }

                // ── Canvas events ─────────────────────────────────────────
                mmCanvas.addEventListener('wheel', e =>
                    map.getCanvas().dispatchEvent(new WheelEvent(e.type, e)));

                mmCanvas.addEventListener('mousedown', e => {
                    if (e.button !== 0) return;
                    e.preventDefault(); e.stopPropagation();

                    if (placingId) {
                        _drag = { type: 'place', startGrid: screenToGrid(e.clientX, e.clientY), preview: null };
                        return;
                    }
                    if (editingId) {
                        const m = markers.find(x => x.id === editingId);
                        if (!m || m.gridX == null) return;
                        const br  = mmCanvas.getBoundingClientRect();
                        const sx  = e.clientX - br.left, sy = e.clientY - br.top;
                        const hp  = handlePositions(markerScreenRect(m));
                        let hit   = null;
                        for (const [name, pos] of Object.entries(hp)) {
                            const dx = sx - pos.x, dy = sy - pos.y;
                            if (Math.sqrt(dx * dx + dy * dy) <= HANDLE_R + 3) { hit = name; break; }
                        }
                        if (hit) {
                            _drag = { type: 'handle', handle: hit, markerId: editingId,
                                      startGrid: screenToGrid(e.clientX, e.clientY), orig: { ...m } };
                        } else {
                            // Inside the image body → move drag; outside → exit edit
                            const r = markerScreenRect(m);
                            if (sx >= r.x && sx <= r.x + r.w && sy >= r.y && sy <= r.y + r.h) {
                                mmCanvas.style.cursor = 'grabbing';
                                _drag = { type: 'move', markerId: editingId,
                                          startGrid: screenToGrid(e.clientX, e.clientY),
                                          origPos: { gridX: m.gridX, gridY: m.gridY } };
                            } else {
                                exitEdit();
                            }
                        }
                    }
                });

                document.addEventListener('mousemove', e => {
                    // Cursor feedback in edit mode when no drag is active
                    if (!_drag && editingId) {
                        const br = mmCanvas.getBoundingClientRect();
                        const sx = e.clientX - br.left, sy = e.clientY - br.top;
                        if (sx >= 0 && sy >= 0 && sx <= mmCanvas.width && sy <= mmCanvas.height) {
                            const m = markers.find(x => x.id === editingId);
                            if (m && m.gridX != null) {
                                const hp = handlePositions(markerScreenRect(m));
                                let onHandle = false;
                                for (const pos of Object.values(hp)) {
                                    if (Math.hypot(sx - pos.x, sy - pos.y) <= HANDLE_R + 3) { onHandle = true; break; }
                                }
                                if (onHandle) {
                                    mmCanvas.style.cursor = 'nwse-resize';
                                } else {
                                    const r = markerScreenRect(m);
                                    mmCanvas.style.cursor = (sx >= r.x && sx <= r.x + r.w && sy >= r.y && sy <= r.y + r.h) ? 'grab' : 'default';
                                }
                            }
                        }
                    }

                    if (!_drag) return;

                    if (_drag.type === 'place' && placingId) {
                        const cur = screenToGrid(e.clientX, e.clientY);
                        const sg  = _drag.startGrid;
                        const m   = markers.find(x => x.id === placingId);
                        const img = m && imgCache.get(m.id);
                        let gW = Math.abs(cur.gridX - sg.gridX) + 1;
                        let gH = Math.abs(cur.gridY - sg.gridY) + 1;
                        if ((e.shiftKey || (m && m.lockAspect)) && img) {
                            const ar = img.naturalWidth / img.naturalHeight;
                            if (gW / gH > ar) gH = Math.max(1, Math.round(gW / ar));
                            else              gW = Math.max(1, Math.round(gH * ar));
                        }
                        const swX = Math.min(sg.gridX, cur.gridX);
                        const swY = Math.min(sg.gridY, cur.gridY);
                        const g   = getGSize();
                        const nwS = map.project(turf.toWgs84([(swX - 0.5) * g, (swY - 0.5 + gH) * g]));
                        const seS = map.project(turf.toWgs84([(swX - 0.5 + gW) * g, (swY - 0.5) * g]));
                        _drag.preview = { x: nwS.x, y: nwS.y, w: seS.x - nwS.x, h: seS.y - nwS.y };
                        _drag.pending = { gridX: swX, gridY: swY, gridW: gW, gridH: gH };
                        redraw();

                    } else if (_drag.type === 'handle') {
                        const m = markers.find(x => x.id === _drag.markerId);
                        if (!m) return;
                        applyHandleDrag(m, _drag.handle, screenToGrid(e.clientX, e.clientY),
                                        e.shiftKey, _drag.orig, imgCache.get(m.id));
                        redraw();
                    } else if (_drag.type === 'move') {
                        const m = markers.find(x => x.id === _drag.markerId);
                        if (!m) return;
                        const cur = screenToGrid(e.clientX, e.clientY);
                        m.gridX = _drag.origPos.gridX + (cur.gridX - _drag.startGrid.gridX);
                        m.gridY = _drag.origPos.gridY + (cur.gridY - _drag.startGrid.gridY);
                        redraw();
                    }
                });

                document.addEventListener('mouseup', e => {
                    if (!_drag) return;
                    if (_drag.type === 'place' && placingId) {
                        if (!_drag.pending) {
                            mmNotify('Please drag to define the image size.', true);
                            _drag = null; redraw();
                        } else {
                            const m = markers.find(x => x.id === placingId);
                            if (m) { Object.assign(m, _drag.pending); saveMeta(); }
                            const placedId = placingId;
                            exitPlace();
                            enterEdit(placedId); // auto-enter edit after placing
                            refreshModal();
                            mmNotify('Placed! Drag to move, handles to resize.');
                        }
                    } else if (_drag && _drag.type === 'handle') {
                        saveMeta(); refreshModal(); _drag = null;
                    } else if (_drag && _drag.type === 'move') {
                        mmCanvas.style.cursor = 'grab';
                        saveMeta(); refreshModal(); _drag = null;
                    }
                });

                // ── Handle drag math ──────────────────────────────────────
                // Absolute-cursor approach: the dragged edge/corner snaps to cursor grid position.
                // The opposite anchor (corner or edge) stays fixed from orig.
                // In grid coords: gridX=west, gridX+gridW=east, gridY=south, gridY+gridH=north.
                // Screen: NW=top-left, SE=bottom-right (Y increases southward on screen).
                function applyHandleDrag(m, handle, cur, shiftKey, orig, img) {
                    const ar   = img ? img.naturalWidth / img.naturalHeight : null;
                    const lock = shiftKey || m.lockAspect;
                    const E    = orig.gridX + orig.gridW; // fixed east edge
                    const N    = orig.gridY + orig.gridH; // fixed north edge
                    let nx = orig.gridX, ny = orig.gridY, nw = orig.gridW, nh = orig.gridH;

                    switch (handle) {
                        // Corners: both axes change; opposite corner fixed
                        case 'nw': // screen top-left → grid (west, north)
                            nx = cur.gridX; nw = E - cur.gridX; nh = cur.gridY - orig.gridY;
                            if (lock && ar) { if (nw / nh > ar) { nh = nw / ar; } else { nw = nh * ar; nx = E - nw; } }
                            break;
                        case 'ne': // screen top-right → grid (east, north)
                            nw = cur.gridX - orig.gridX; nh = cur.gridY - orig.gridY;
                            if (lock && ar) { if (nw / nh > ar) nh = nw / ar; else nw = nh * ar; }
                            break;
                        case 'sw': // screen bottom-left → grid (west, south)
                            nx = cur.gridX; nw = E - cur.gridX; ny = cur.gridY; nh = N - cur.gridY;
                            if (lock && ar) { if (nw / nh > ar) { nh = nw / ar; ny = N - nh; } else { nw = nh * ar; nx = E - nw; } }
                            break;
                        case 'se': // screen bottom-right → grid (east, south)
                            nw = cur.gridX - orig.gridX; ny = cur.gridY; nh = N - cur.gridY;
                            if (lock && ar) { if (nw / nh > ar) { nh = nw / ar; ny = N - nh; } else nw = nh * ar; }
                            break;
                        // Edges: single axis only, no aspect ratio applied
                        case 'n': nh = cur.gridY - orig.gridY; break;
                        case 's': ny = cur.gridY; nh = N - cur.gridY; break;
                        case 'e': nw = cur.gridX - orig.gridX; break;
                        case 'w': nx = cur.gridX; nw = E - cur.gridX; break;
                    }
                    if (nw < 1) nw = 1; if (nh < 1) nh = 1;
                    m.gridX = Math.round(nx); m.gridY = Math.round(ny);
                    m.gridW = Math.round(nw); m.gridH = Math.round(nh);
                }

                // ── Modal ─────────────────────────────────────────────────
                function openModal() {
                    if (mmModal) {
                        mmModal.style.display = mmModal.style.display === 'none' ? 'flex' : 'none';
                        return;
                    }
                    mmModal = document.createElement('div');
                    Object.assign(mmModal.style, {
                        position: 'fixed', top: '60px', left: '60px', zIndex: '10000',
                        background: 'var(--color-white,#fff)', borderRadius: '12px',
                        boxShadow: '0 8px 32px rgba(0,0,0,.2)',
                        display: 'flex', flexDirection: 'column', width: '330px', maxHeight: '82vh',
                        userSelect: 'none', overflow: 'hidden',
                    });

                    // Header / drag handle
                    const hdr = document.createElement('div');
                    Object.assign(hdr.style, {
                        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                        padding: '11px 14px', background: 'var(--color-gray-50,#f9fafb)',
                        borderBottom: '1px solid var(--color-gray-200,#e5e7eb)',
                        borderRadius: '12px 12px 0 0', cursor: 'grab', flexShrink: '0',
                    });
                    hdr.innerHTML = '<span style="font-weight:700;font-size:13px;color:var(--color-gray-800,#1f2937);">\ud83d\udccc Map Markers</span>';
                    // Compact toggle
                    const compactBtn = document.createElement('button');
                    compactBtn.title = 'Toggle compact view';
                    compactBtn.textContent = mmCompact ? '\u25a1' : '\u2630';
                    Object.assign(compactBtn.style, { background: 'none', border: 'none', cursor: 'pointer',
                        fontSize: '13px', color: 'var(--color-gray-500,#6b7280)', padding: '1px 5px', borderRadius: '4px',
                        marginLeft: 'auto', marginRight: '4px' });
                    compactBtn.onclick = () => {
                        mmCompact = !mmCompact;
                        compactBtn.textContent = mmCompact ? '\u25a1' : '\u2630';
                        refreshModal();
                    };
                    hdr.appendChild(compactBtn);
                    const xBtn = document.createElement('button');
                    xBtn.textContent = '\u2715';
                    Object.assign(xBtn.style, { background: 'none', border: 'none', cursor: 'pointer',
                        fontSize: '13px', color: 'var(--color-gray-500,#6b7280)', padding: '1px 5px', borderRadius: '4px' });
                    xBtn.onclick = () => { mmModal.style.display = 'none'; exitPlace(); exitEdit(); };
                    hdr.appendChild(xBtn);
                    mmModal.appendChild(hdr);

                    // Draggable header
                    let dragOff = null;
                    hdr.addEventListener('mousedown', e => {
                        if (e.target === xBtn || e.target === compactBtn) return;
                        dragOff = { x: e.clientX - mmModal.offsetLeft, y: e.clientY - mmModal.offsetTop };
                        hdr.style.cursor = 'grabbing';
                    });
                    document.addEventListener('mousemove', e => {
                        if (!dragOff) return;
                        mmModal.style.left = (e.clientX - dragOff.x) + 'px';
                        mmModal.style.top  = (e.clientY - dragOff.y) + 'px';
                    });
                    document.addEventListener('mouseup', () => { dragOff = null; hdr.style.cursor = 'grab'; });

                    // ── Shared helpers: register a new marker and enter place mode
                    async function addMarkerFromDataUrl(dataUrl, name, mediaType) {
                        const mt  = mediaType || 'image';
                        const id  = 'mm_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
                        let ar = 1;
                        if (mt === 'gif') {
                            // Use a temporary img to read first-frame dimensions
                            const img = await loadImg(id, dataUrl);
                            ar = img ? img.naturalWidth / img.naturalHeight : 1;
                            dataUrlCache.set(id, dataUrl);
                        } else {
                            const img = await loadImg(id, dataUrl);
                            ar = img ? img.naturalWidth / img.naturalHeight : 1;
                        }
                        const dW = 50;
                        markers.push({ id, name, mediaType: mt,
                            gridX: null, gridY: null, gridW: dW,
                            gridH: Math.max(1, Math.round(dW / ar)),
                            visible: true, opacity: 1, lockAspect: true });
                        await mmDbSet(id, dataUrl);
                        saveMeta();
                        enterPlace(id);
                        refreshModal();
                    }

                    // Add media row — file upload + image URL + video URL
                    const addRow = document.createElement('div');
                    Object.assign(addRow.style, { padding: '9px 14px', display: 'flex', gap: '6px', flexWrap: 'wrap',
                        borderBottom: '1px solid var(--color-gray-100,#f3f4f6)', flexShrink: '0' });
                    function mkAddBtn(label, bg) {
                        const b = document.createElement('button');
                        b.textContent = label;
                        Object.assign(b.style, { flex: '1 1 40%', padding: '7px', background: bg,
                            color: '#fff', border: 'none', borderRadius: '7px', cursor: 'pointer',
                            fontWeight: '700', fontSize: '12px' });
                        return b;
                    }
                    const addBtn = mkAddBtn('+ Image / GIF', '#3b82f6');
                    const fileInput = document.createElement('input');
                    fileInput.type = 'file';
                    fileInput.accept = 'image/png,image/jpeg,image/webp,image/gif';
                    fileInput.style.display = 'none';
                    fileInput.addEventListener('change', async () => {
                        const file = fileInput.files[0];
                        if (!file) return;
                        const dataUrl = await new Promise(res => {
                            const r = new FileReader(); r.onload = ev => res(ev.target.result); r.readAsDataURL(file);
                        });
                        const isGif = file.type === 'image/gif';
                        await addMarkerFromDataUrl(dataUrl, file.name.replace(/\.[^/.]+$/, ''), isGif ? 'gif' : 'image');
                        fileInput.value = '';
                    });
                    addBtn.onclick = () => fileInput.click();

                    const urlBtn = mkAddBtn('\ud83d\udd17 Image URL', '#8b5cf6');
                    urlBtn.onclick = () => {
                        const url = prompt('Enter image URL:');
                        if (!url || !url.trim()) return;
                        mmNotify('Loading\u2026');
                        GM_xmlhttpRequest({
                            method: 'GET', url: url.trim(), responseType: 'blob',
                            onload: (resp) => {
                                const mime = resp.response.type;
                                if (!mime.startsWith('image/')) {
                                    mmNotify('URL does not point to an image.', true);
                                    return;
                                }
                                const isGif = mime === 'image/gif';
                                const reader = new FileReader();
                                reader.onload = async (ev) => {
                                    const name = url.split('/').pop().replace(/\?.*$/, '').replace(/\.[^/.]+$/, '') || 'image';
                                    await addMarkerFromDataUrl(ev.target.result, name, isGif ? 'gif' : 'image');
                                };
                                reader.readAsDataURL(resp.response);
                            },
                            onerror:   () => mmNotify('Failed to load image from URL.', true),
                            ontimeout: () => mmNotify('Request timed out.', true),
                        });
                    };

                    addRow.appendChild(addBtn); addRow.appendChild(fileInput);
                    addRow.appendChild(urlBtn);
                    mmModal.appendChild(addRow);

                    // Scrollable list
                    const list = document.createElement('div');
                    list.id = 'gpc-mm-list';
                    Object.assign(list.style, { flex: '1', overflowY: 'auto', padding: '6px 0' });
                    mmModal.appendChild(list);

                    document.body.appendChild(mmModal);
                    renderList(list);
                }

                function refreshModal() {
                    const l = document.getElementById('gpc-mm-list');
                    if (l) renderList(l);
                    redraw();
                }

                function renderList(list) {
                    list.innerHTML = '';
                    if (!markers.length) {
                        list.innerHTML = '<div style="text-align:center;color:var(--color-gray-400,#9ca3af);padding:22px 14px;font-size:12px;">No markers yet.<br>Upload media above.</div>';
                        return;
                    }
                    let dragOverIdx = null;
                    for (let i = 0; i < markers.length; i++) {
                        const m = markers[i];
                        const wrapper = document.createElement('div');
                        wrapper.setAttribute('data-mm-idx', String(i));
                        wrapper.style.cssText = 'position:relative;';
                        wrapper.appendChild(makeCard(m));

                        // ── Drag-to-sort ───────────────────────────────
                        wrapper.addEventListener('dragstart', e => {
                            listDragSrc = i;
                            e.dataTransfer.effectAllowed = 'move';
                            wrapper.style.opacity = '0.45';
                        });
                        wrapper.addEventListener('dragend', () => {
                            wrapper.style.opacity = '';
                            list.querySelectorAll('[data-mm-drop-indicator]').forEach(el => el.remove());
                        });
                        wrapper.addEventListener('dragover', e => {
                            if (listDragSrc == null || listDragSrc === i) return;
                            e.preventDefault(); e.dataTransfer.dropEffect = 'move';
                            list.querySelectorAll('[data-mm-drop-indicator]').forEach(el => el.remove());
                            const ind = document.createElement('div');
                            ind.setAttribute('data-mm-drop-indicator', '1');
                            ind.style.cssText = 'height:2px;background:#3b82f6;margin:0 10px;border-radius:2px;';
                            const rect = wrapper.getBoundingClientRect();
                            const insertBefore = e.clientY < rect.top + rect.height / 2;
                            if (insertBefore) wrapper.insertAdjacentElement('beforebegin', ind);
                            else             wrapper.insertAdjacentElement('afterend', ind);
                            dragOverIdx = insertBefore ? i : i + 1;
                        });
                        wrapper.addEventListener('dragleave', () => {
                            // Remove indicator only if mouse truly left the list area
                        });
                        wrapper.addEventListener('drop', e => {
                            e.preventDefault();
                            list.querySelectorAll('[data-mm-drop-indicator]').forEach(el => el.remove());
                            if (listDragSrc == null || listDragSrc === dragOverIdx) { listDragSrc = null; return; }
                            const moved = markers.splice(listDragSrc, 1)[0];
                            const dest  = dragOverIdx > listDragSrc ? dragOverIdx - 1 : dragOverIdx;
                            markers.splice(dest, 0, moved);
                            listDragSrc = null; dragOverIdx = null;
                            saveMeta(); refreshModal();
                        });

                        list.appendChild(wrapper);
                    }
                }

                function makeThumb(m, size) {
                    const s = size || 34;
                    const checkers = 'repeating-conic-gradient(#ccc 0% 25%,transparent 0% 50%) 0 0/8px 8px';
                    const sharedStyle = { width: s + 'px', height: s + 'px', borderRadius: '4px',
                        flexShrink: '0', border: '1px solid var(--color-gray-200,#e5e7eb)', background: checkers };
                    if (isGifType(m)) {
                        const el = document.createElement('img');
                        el.src = dataUrlCache.get(m.id) || '';
                        Object.assign(el.style, { ...sharedStyle, objectFit: 'cover', display: 'block' });
                        return el;
                    }
                    // image type: canvas
                    const cvs = document.createElement('canvas');
                    cvs.width = s; cvs.height = s;
                    Object.assign(cvs.style, sharedStyle);
                    const img = imgCache.get(m.id);
                    if (img) {
                        const tc = cvs.getContext('2d');
                        tc.globalAlpha = m.opacity ?? 1;
                        tc.drawImage(img, 0, 0, s, s);
                        tc.globalAlpha = 1;
                    }
                    return cvs;
                }

                function mediaTypeLabel(m) {
                    return isGifType(m) ? '\ud83c\udf9e' : '\ud83d\uddbc';
                }

                function makeCard(m) {
                    const isP = placingId === m.id, isE = editingId === m.id;
                    const isExpanded = !mmCompact || expandedCards.has(m.id);
                    const cardBg     = isE ? 'var(--color-blue-50,#eff6ff)'
                                     : isP ? 'var(--color-green-50,#f0fdf4)'
                                     :       'var(--color-gray-50,#f9fafb)';
                    const cardBorder = isE ? 'var(--color-blue-200,#bfdbfe)'
                                     : isP ? 'var(--color-green-200,#bbf7d0)'
                                     :       'var(--color-gray-200,#e5e7eb)';

                    const card = document.createElement('div');
                    // draggable is NOT set on the card — only the grab handle initiates a drag
                    Object.assign(card.style, {
                        margin: '4px 10px', padding: mmCompact ? '6px 9px' : '9px', borderRadius: '8px',
                        display: 'flex', flexDirection: 'column', gap: '5px',
                        background: cardBg, border: '1px solid ' + cardBorder, cursor: 'default',
                    });

                    // ── Compact top row (always shown) ────────────────────
                    const topRow = document.createElement('div');
                    Object.assign(topRow.style, { display: 'flex', alignItems: 'center', gap: '7px' });

                    // Drag handle — only this element is draggable
                    const dragHdl = document.createElement('span');
                    dragHdl.textContent = '\u2807';
                    dragHdl.title = 'Drag to reorder';
                    dragHdl.draggable = true;
                    dragHdl.style.cssText = 'color:var(--color-gray-400,#9ca3af);cursor:grab;font-size:14px;flex-shrink:0;user-select:none;';
                    topRow.appendChild(dragHdl);

                    topRow.appendChild(makeThumb(m, 34));

                    const infoCol = document.createElement('div');
                    Object.assign(infoCol.style, { flex: '1', minWidth: '0', display: 'flex', flexDirection: 'column', gap: '2px' });

                    const nameLbl = document.createElement('div');
                    nameLbl.textContent = mediaTypeLabel(m) + ' ' + m.name;
                    nameLbl.style.cssText = 'font-size:12px;font-weight:700;color:var(--color-gray-800,#1f2937);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
                    infoCol.appendChild(nameLbl);

                    const coordLbl = document.createElement('div');
                    coordLbl.style.cssText = 'font-size:10px;color:' +
                        (m.gridX != null ? 'var(--color-green-600,#16a34a)' : 'var(--color-gray-400,#9ca3af)') + ';';
                    coordLbl.textContent = m.gridX != null
                        ? `(${m.gridX}, ${m.gridY + m.gridH}) \u2014 ${m.gridW}\u00d7${m.gridH}`
                        : 'Not placed';
                    infoCol.appendChild(coordLbl);
                    topRow.appendChild(infoCol);

                    // Eye + expand/collapse in compact mode
                    const eyeBtn2 = document.createElement('button');
                    eyeBtn2.textContent = m.visible ? '\ud83d\udc41' : '\ud83d\udeab';
                    eyeBtn2.title = m.visible ? 'Hide' : 'Show';
                    Object.assign(eyeBtn2.style, { background: 'none', border: 'none', cursor: 'pointer',
                        fontSize: '14px', padding: '1px', flexShrink: '0' });
                    eyeBtn2.onclick = () => { m.visible = !m.visible; saveMeta(); refreshModal(); };
                    topRow.appendChild(eyeBtn2);

                    if (mmCompact) {
                        const chevron = document.createElement('button');
                        chevron.textContent = isExpanded ? '\u25b2' : '\u25bc';
                        chevron.title = isExpanded ? 'Collapse' : 'Expand controls';
                        Object.assign(chevron.style, { background: 'none', border: 'none', cursor: 'pointer',
                            fontSize: '10px', color: 'var(--color-gray-500,#6b7280)', padding: '1px', flexShrink: '0' });
                        chevron.onclick = (e) => {
                            e.stopPropagation();
                            if (expandedCards.has(m.id)) expandedCards.delete(m.id);
                            else expandedCards.add(m.id);
                            refreshModal();
                        };
                        topRow.appendChild(chevron);
                        // Click anywhere on compact card (except buttons) also toggles expand
                        card.addEventListener('click', (e) => {
                            if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return;
                            if (expandedCards.has(m.id)) expandedCards.delete(m.id);
                            else expandedCards.add(m.id);
                            refreshModal();
                        });
                    }
                    card.appendChild(topRow);

                    // ── Full controls (hidden in compact unless expanded) ──
                    if (isExpanded) {
                        // Name input row
                        const nameRow = document.createElement('div');
                        Object.assign(nameRow.style, { display: 'flex', alignItems: 'center', gap: '7px' });
                        const nameEl = document.createElement('input');
                        nameEl.type = 'text'; nameEl.value = m.name;
                        Object.assign(nameEl.style, { flex: '1', padding: '3px 6px',
                            border: '1px solid var(--color-gray-300,#d1d5db)', borderRadius: '4px',
                            fontSize: '12px', fontWeight: '600', color: 'var(--color-gray-800,#1f2937)',
                            background: 'var(--color-white,#fff)' });
                        nameEl.onchange = () => { m.name = nameEl.value; saveMeta(); refreshModal(); };
                        nameRow.appendChild(nameEl);
                        card.appendChild(nameRow);

                        // Opacity + aspect lock
                        {
                            const r2 = document.createElement('div');
                            Object.assign(r2.style, { display: 'flex', alignItems: 'center', gap: '6px' });
                            const opLbl = document.createElement('span');
                            opLbl.textContent = 'Opacity';
                            opLbl.style.cssText = 'font-size:11px;color:var(--color-gray-500,#6b7280);flex-shrink:0;';
                            r2.appendChild(opLbl);
                            const opSlider = document.createElement('input');
                            opSlider.type = 'range'; opSlider.min = '0'; opSlider.max = '1';
                            opSlider.step = '0.05'; opSlider.value = m.opacity ?? 1;
                            opSlider.style.cssText = 'flex:1;cursor:pointer;';
                            opSlider.oninput  = () => { m.opacity = parseFloat(opSlider.value); redraw(); };
                            opSlider.onchange = () => saveMeta();
                            r2.appendChild(opSlider);
                            const lockBtn = document.createElement('button');
                            lockBtn.textContent = m.lockAspect ? '\ud83d\udd12' : '\ud83d\udd13';
                            lockBtn.title = m.lockAspect ? 'Aspect locked' : 'Aspect free';
                            Object.assign(lockBtn.style, { background: 'none', border: 'none', cursor: 'pointer',
                                fontSize: '14px', padding: '1px', flexShrink: '0' });
                            lockBtn.onclick = () => { m.lockAspect = !m.lockAspect; saveMeta(); refreshModal(); };
                            r2.appendChild(lockBtn);
                            if (isImageType(m)) {
                                const resetArBtn = document.createElement('button');
                                resetArBtn.textContent = '\u21ba';
                                resetArBtn.title = 'Reset to natural aspect ratio';
                                Object.assign(resetArBtn.style, { background: 'none', border: 'none', cursor: 'pointer',
                                    fontSize: '14px', padding: '1px', flexShrink: '0' });
                                resetArBtn.onclick = () => {
                                    const imgEl = imgCache.get(m.id);
                                    if (!imgEl) return;
                                    m.gridH = Math.max(1, Math.round(m.gridW / (imgEl.naturalWidth / imgEl.naturalHeight)));
                                    saveMeta(); redraw(); refreshModal();
                                };
                                r2.appendChild(resetArBtn);
                            }
                            card.appendChild(r2);
                        }

                        // Action buttons
                        const r3 = document.createElement('div');
                        r3.style.cssText = 'display:flex;gap:5px;';
                        function mkBtn(txt, bg, fn) {
                            const b = document.createElement('button');
                            b.textContent = txt;
                            Object.assign(b.style, { flex: '1', padding: '5px 3px', border: 'none',
                                borderRadius: '6px', cursor: 'pointer', fontSize: '11px', fontWeight: '700',
                                background: bg, color: '#fff', transition: 'opacity .12s' });
                            b.onmouseenter = () => b.style.opacity = '0.82';
                            b.onmouseleave = () => b.style.opacity = '1';
                            b.onclick = fn; return b;
                        }
                        r3.appendChild(mkBtn(isP ? '\u23f8 Stop' : '\ud83d\udccd Place', isP ? '#f59e0b' : '#10b981', () => {
                            if (isP) exitPlace(); else enterPlace(m.id); refreshModal();
                        }));
                        if (m.gridX != null) {
                            r3.appendChild(mkBtn(isE ? '\u2713 Done' : '\u270f\ufe0f Edit', isE ? '#6366f1' : '#3b82f6', () => {
                                if (isE) exitEdit(); else enterEdit(m.id); refreshModal();
                            }));
                        }
                        r3.appendChild(mkBtn('\ud83d\uddd1 Remove', '#ef4444', () => {
                            if (!confirm('Remove this marker?')) return;
                            const i = markers.findIndex(x => x.id === m.id);
                            if (i !== -1) markers.splice(i, 1);
                            imgCache.delete(m.id);
                            removeOverlayEl(m.id);
                            expandedCards.delete(m.id);
                            mmDbDelete(m.id).catch(() => {});
                            saveMeta();
                            if (editingId === m.id) exitEdit();
                            if (placingId === m.id) exitPlace();
                            refreshModal();
                        }));
                        card.appendChild(r3);
                    }

                    return card;
                }

                function mmNotify(msg, isErr) {
                    const ex = document.getElementById('gpc-mm-toast'); if (ex) ex.remove();
                    const t = document.createElement('div'); t.id = 'gpc-mm-toast'; t.textContent = msg;
                    Object.assign(t.style, { position: 'fixed', top: '68px', left: '50%',
                        transform: 'translateX(-50%)', padding: '8px 18px', borderRadius: '8px',
                        fontSize: '13px', fontWeight: '700', zIndex: '100002',
                        boxShadow: '0 4px 12px rgba(0,0,0,.2)', transition: 'opacity .3s',
                        fontFamily: 'system-ui,sans-serif',
                        background: isErr ? '#fca5a5' : '#bbf7d0',
                        color:      isErr ? '#7f1d1d' : '#166534' });
                    document.body.appendChild(t);
                    setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 300); }, 2600);
                }

                // ── Bootstrap ─────────────────────────────────────────────
                loadMeta();
                preloadAll().then(redraw);

                _mapMarkers = { openModal };
            })();
            _featureStatus.mapMarkers = 'ok';
            console.log('[GeoPixelcons++] \u2705 Map Markers loaded');
        } catch (err) {
            _featureStatus.mapMarkers = 'error';
            dbgPush(`Map Markers init failed: ${err && err.message ? err.message : String(err)}`, { error: err, uiComponent: 'Map Markers' });
            console.error('[GeoPixelcons++] \u274c Map Markers failed:', err);
        }
    }

    // ============================================================
    //  AUTO-SCREENSHOT ON PAINT (fetch interceptor)
    // ============================================================
    if (_settings.regionScreenshot) {
        try {
            const _targetWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
            const _origFetch = _targetWindow.fetch.bind(_targetWindow);
            _targetWindow.fetch = async function(...args) {
                const response = await _origFetch(...args);
                try {
                    const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '';
                    if (url.includes('/PlacePixels') && isAutoScreenshotEnabled() && _regionScreenshot) {
                        const coords = loadCachedCoords();
                        if (coords && response.ok) {
                            // Small delay to let the tile cache update
                            setTimeout(() => {
                                _regionScreenshot.silentDownload(coords);
                            }, 800);
                        }
                    }
                } catch {}
                return response;
            };
            console.log('[GeoPixelcons++] \u2705 Auto-screenshot fetch hook installed');
        } catch (err) {
            console.error('[GeoPixelcons++] \u274c Auto-screenshot hook failed:', err);
        }
    }

    console.log('[GeoPixelcons++] v' + VERSION + ' initialized. Features:', _featureStatus);
})();