GeoPixelcons++

Unified GeoPixels enhancement suite - by Pixelcons

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         GeoPixelcons++
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @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_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.0.2';

    // ============================================================
    //  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', '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'] },
    ];

    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'] },
    ];

    const DEFAULT_SETTINGS = { useEmojiIcon: 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();
    let _themeEditor = null; // Populated by theme editor module
    let _regionScreenshot = null; // Populated by region screenshot module
    let _regionsHighscore = null; // Populated by regions highscore 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;
        }

        // ---- 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
                },
            };
            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 statusDot = status === 'error' ? '🟡' : (enabled ? '🟢' : '🔴');

            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);
            });
            const dotSpan = document.createElement('span');
            dotSpan.style.cssText = 'font-size:10px;';
            dotSpan.textContent = statusDot;
            labelWrap.appendChild(iconSpan);
            labelWrap.appendChild(nameSpan);
            labelWrap.appendChild(dotSpan);

            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');
                labelWrap.querySelector('span:last-of-type').textContent = input.checked ? '🟢' : '🔴';
                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)));

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

        // Desc text
        const emojiDesc = document.createElement('div');
        emojiDesc.style.cssText = `font-size:12px;color:${dark ? '#6c7086' : '#94a3b8'};padding:0 14px;`;
        emojiDesc.textContent = 'When enabled, replaces the GeoPixelcons++ button icon with the 😢 emoji.';
        settingsPanel.appendChild(emojiDesc);

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

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

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

    // 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';

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

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

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

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

        // Insert before the palette
        paletteDiv.parentNode.insertBefore(searchContainer, paletteDiv);

        // Search and highlight function
        function performSearch() {
            const searchValue = searchInput.value.trim();
            const hideUnmatched = checkbox.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 search is empty, exit early
            if (!searchValue) {
                return;
            }

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

            if (searchTerms.length === 0) {
                return;
            }

            // Find matching buttons
            const matchingButtons = [];

            colorButtons.forEach(btn => {
                const colorTitle = btn.getAttribute('title');
                if (!colorTitle) return;

                const colorHex = colorTitle.toUpperCase();

                // Check if any search term is a substring of this color
                const isMatch = searchTerms.some(term => colorHex.includes(term));

                if (isMatch) {
                    btn.classList.add('color-search-glow');
                    matchingButtons.push(btn);
                }
            });

            // Hide unmatched if checkbox is selected
            if (hideUnmatched && searchTerms.length > 0) {
                colorButtons.forEach(btn => {
                    if (!matchingButtons.includes(btn)) {
                        btn.classList.add('hidden');
                    }
                });
            }
        }

        // Add event listeners
        searchInput.addEventListener('input', performSearch);
        checkbox.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;
            }

            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';
            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);

        // --- "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';
                });
            } 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 = '';
                });
            }

            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';
            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) {
            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; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--color-gray-300, #eee); padding-bottom: 10px; }
        .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;
        }
        .gp-to-delete-btn:hover { background: #dc2626; }

        .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-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) => reject('DB Error');
    });

    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) => {
                const tx = db.transaction(STORE_NAME, 'readonly');
                const store = tx.objectStore(STORE_NAME);
                const req = store.getAll();
                req.onsuccess = () => resolve(req.result.reverse());
            });
        },
        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) {
            console.error("exportToZip failed:", error);
            gpLog(`exportToZip: ERROR - ${error.message}`);
            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) {
                    // 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) {
                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);
        let attempts = 0;
        const interval = setInterval(() => {
            const placeBtn = document.getElementById('initiatePlaceGhostBtn');
            if (placeBtn && !placeBtn.disabled) {
                clearInterval(interval);
                localStorage.setItem('ghostImageCoords', JSON.stringify(coords));
                
                // Get function using safe helper
                const initializeGhostFromStorage = getPageVariable('initializeGhostFromStorage');
                
                if (typeof initializeGhostFromStorage === 'function') {
                    gpLog("Calling initializeGhostFromStorage to place template");
                    initializeGhostFromStorage();
                    notifyUser("Auto-Place", `Position detected: ${coords.gridX}, ${coords.gridY}`);
                } else {
                    gpLog("ERROR: initializeGhostFromStorage function not found");
                    notifyUser("Warning", `Position set to ${coords.gridX}, ${coords.gridY} but auto-place failed. Click 'Place on Map' manually.`);
                }
            }
            if (++attempts > 50) {
                clearInterval(interval);
                gpLog("Timeout waiting for place button to be ready");
            }
        }, 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) {
        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(r => decoded.cleanCanvas.toBlob(r, 'image/png'));
                finalFile = new File([cleanBlob], file.name || "ghost.png", { type: "image/png" });
            } else {
                gpLog("No encoded position found in image");
            }

            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 {
                // Clear old coordinates if this template has no encoded position
                localStorage.removeItem('ghostImageCoords');
                gpLog("No encoded position found, cleared old coordinates");
            }

        } catch (e) {
            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);
        }, 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);
        } catch (e) {
            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) {
                    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) {
                    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">
                    <span class="gp-to-title">Image History (${images.length})</span>
                    <div class="flex gap-2">
                        <button id="gp-export-zip" class="gp-to-btn gp-to-btn-orange text-xs">💾 Export JSON</button>
                        <button id="gp-import-zip" class="gp-to-btn gp-to-btn-green text-xs">📁 Import JSON</button>
                        <button id="gp-clear-all" class="gp-to-btn gp-to-btn-red text-xs">Clear All</button>
                        <button id="gp-close-hist" class="gp-to-btn gp-to-btn-gray">Close</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>
        `;
        document.body.appendChild(modal);

        const grid = modal.querySelector('#gp-history-grid');
        images.forEach(imgData => {
            const card = document.createElement('div');
            card.className = 'gp-to-card';
            card.innerHTML = `
                <button class="gp-to-delete-btn" title="Delete">✖</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 = (e) => {
                if (e.target.closest('.gp-to-delete-btn')) return;
                processAndLoadImage(imgData.blob, false);
                modal.remove();
            };
            card.querySelector('.gp-to-delete-btn').onclick = async () => {
                await HistoryManager.delete(imgData.id);
                card.remove();
            };
            grid.appendChild(card);
        });

        modal.querySelector('#gp-export-zip').onclick = async () => {
            await exportToZip();
        };

        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');

    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';
            console.error('[GeoPixelcons++] ❌ Ghost Template Manager 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()
    };

    // --- 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);
                    members[fullName] = { xp, coords };
                }
            }
        });
        return members;
    }

    function saveGuildSnapshot(members, forceNew = false) {
        const now = getVirtualNow();
        let history = GM_getValue('guild_xp_history', []);
        const lastEntry = history[history.length - 1];
        const lastBucketStart = lastEntry ? (lastEntry.bucketStartTime || lastEntry.timestamp) : 0;

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

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

        if (history.length > CONFIG.maxSnapshots) history = history.slice(history.length - CONFIG.maxSnapshots);
        GM_setValue('guild_xp_history', history);
        return history;
    }

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

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

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

        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 memberName = memberNames[i];
            const match = memberName.match(/#(\d+)$/);
            if (match) {
                const userId = match[1];
                const data = await fetchUserProfile(userId);
                if (data) { allUsersData.push(data); successCount++; }
                else failCount++;
            } else {
                failCount++;
            }

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

    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.id}"`, c.type, diff, oldVal, newVal];
                })
            ];
            csvContent = csvRows.map(e => e.join(",")).join("\n");

        } else {
            // Export Full History (Legacy behavior)
            let csv = 'Snapshot,Timestamp,User,XP\n';
            snapshots.forEach((snap, idx) => {
                const timestamp = new Date(snap.timestamp).toLocaleString();
                for (const [user, data] of Object.entries(snap.members)) {
                    const xp = data.xp || data;
                    csv += `${idx + 1},"${timestamp}","${user}",${xp}\n`;
                }
            });
            // Add current
            const now = new Date(getVirtualNow()).toLocaleString();
            for (const [user, data] of Object.entries(currentMembers)) {
                const xp = data.xp || data;
                csv += `Current,"${now}","${user}",${xp}\n`;
            }
            csvContent = csv;
        }
        
        // Open CSV Modal
        const csvOverlay = document.createElement('div');
        csvOverlay.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.id);
                    return markerInfo && markerInfo.inTerritory;
                });
            } else if (filterMode === 'out-of-territory') {
                changes = changes.filter(c => {
                    const markerInfo = playerMarkerData.find(m => m.name === c.id);
                    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.id;
                    
                    userTd.appendChild(nameSpan);

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

                    // Map Button
                    if (change.coords) {
                        const mapBtn = document.createElement('button');
                        mapBtn.className = 'member-icon-btn map-icon';
                        mapBtn.setAttribute('data-player-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.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.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 [name, data] of Object.entries(members)) {
            const coords = getCoords(data);
            if (coords) {
                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);

            console.log('[Guild Modal] v3.0 - 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';
            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);
    }

    // ============================================

    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
    // ============================================

    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';
            if (paintIsTop) {
                dropdown.style.bottom = 'auto';
                dropdown.style.top = '100%';
                dropdown.style.marginBottom = '0';
                dropdown.style.marginTop = '8px';
            } else {
                dropdown.style.top = 'auto';
                dropdown.style.bottom = '100%';
                dropdown.style.marginTop = '0';
                dropdown.style.marginBottom = '8px';
            }
            // 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;
                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;
            }

            /* 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 handles */
            .brush-swap-resize-handle {
                position: absolute;
                width: 12px;
                height: 12px;
                z-index: 1001;
            }
            .brush-swap-resize-handle::after {
                content: '';
                position: absolute;
                width: 6px;
                height: 6px;
                border-style: solid;
                border-color: var(--color-gray-400, #9ca3af);
                opacity: 0;
                transition: opacity 0.15s;
            }
            #brush-swap-dropdown:hover .brush-swap-resize-handle::after {
                opacity: 0.6;
            }
            .brush-swap-resize-handle:hover::after {
                opacity: 1 !important;
            }
            .brush-swap-resize-tl {
                top: 0; left: 0;
                cursor: nw-resize;
            }
            .brush-swap-resize-tl::after {
                top: 2px; left: 2px;
                border-width: 2px 0 0 2px;
            }
            .brush-swap-resize-tr {
                top: 0; right: 0;
                cursor: ne-resize;
            }
            .brush-swap-resize-tr::after {
                top: 2px; right: 2px;
                border-width: 2px 2px 0 0;
            }
        `;
        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

        // Resize handles (top-left and top-right corners)
        const resizeTL = document.createElement('div');
        resizeTL.className = 'brush-swap-resize-handle brush-swap-resize-tl';
        dropdown.appendChild(resizeTL);
        const resizeTR = document.createElement('div');
        resizeTR.className = 'brush-swap-resize-handle brush-swap-resize-tr';
        dropdown.appendChild(resizeTR);

        // 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.maxWidth = storedSize.w + 'px';
                dropdown.style.maxHeight = storedSize.h + 'px';
            }
        }

        function setupResize(handle, isLeft) {
            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;

                function onMove(ev) {
                    const dx = ev.clientX - startX;
                    const dy = ev.clientY - startY;
                    // Top edge: dragging up increases height
                    const newH = Math.max(150, startH - dy);
                    // Left handle: dragging left increases width; right: dragging right increases
                    const newW = Math.max(200, isLeft ? startW - dx : startW + dx);
                    dropdown.style.maxWidth = newW + 'px';
                    dropdown.style.maxHeight = newH + 'px';
                }

                function onUp() {
                    document.removeEventListener('mousemove', onMove);
                    document.removeEventListener('mouseup', onUp);
                    // Persist
                    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);
                document.addEventListener('mouseup', onUp);
            });
        }

        setupResize(resizeTL, true);
        setupResize(resizeTR, false);
        
        const itemsContainer = document.createElement('div');
        itemsContainer.className = 'brush-swap-items';
        dropdown.appendChild(itemsContainer);

        // 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
                setTimeout(() => {
                    addBrushDimensionDropdown();
                }, 50);
            };
            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';
            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';
            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 = 10000 * 10000; // 100M 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 ~4000×4000 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 = 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 ~4000×4000 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 = 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';
            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 = [];

        swatches.forEach(btn => {
            let hex = null;

            // 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\n…")
            if (!hex && btn.title) {
                const candidate = btn.title.split(/[\r\n]+/)[0].trim().toUpperCase();
                if (/^#[0-9A-F]{6}$/.test(candidate)) hex = candidate;
            }

            if (hex && /^#[0-9A-F]{6}$/.test(hex) && !seen.has(hex)) {
                seen.add(hex);
                colors.push(hex);
            }
        });

        return colors;
    }

    // ─── 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) {
        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;

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

        // --- Populate list (original order, owned shown in-place) ---
        const list = document.getElementById('gp-bulk-list');
        list.innerHTML = '';
        colors.forEach(hex => {
            list.appendChild(buildColorRow(hex, ownedSet.has(hex) ? 'owned' : 'pending'));
        });

        // --- 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(' · ');

        // --- 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%';
        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';

        unowned.forEach(hex => list.appendChild(buildQueueRow(hex, false)));

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

    /** Build a single color row for the profile queue. */
    function buildQueueRow(hex, isOwned) {
        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);

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

    // ─── 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';
            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 = 200;

    const buttonsState = new WeakMap();
    let trackedButtons = [];
    let latestMouse = null;
    let rafScheduled = false;

    function onMouseMove(e) {
        latestMouse = { x: e.clientX, y: e.clientY };
        if (!rafScheduled) {
            rafScheduled = true;
            requestAnimationFrame(processMouse);
        }
    }

    function processMouse() {
        rafScheduled = false;
        if (!latestMouse) return;
        const { x, y } = latestMouse;

        // Check if any menu is currently open
        const anyMenuOpen = trackedButtons.some(info => isMenuOpen(info));

        trackedButtons.forEach(info => {
            if (!info.button || !info.parent) return;
            const rect = info.button.getBoundingClientRect();
            const left = Math.floor(rect.left);
            const right = Math.ceil(rect.right);
            const top = Math.floor(rect.top);
            const bottom = Math.ceil(rect.bottom);

            const insideButton = (x >= left) && (x <= right) && (y >= top) && (y <= bottom);

            if (anyMenuOpen) {
                // When a menu is open, switch to another button only if
                // the mouse is directly over that button (no extended zone).
                if (insideButton) tryOpen(info);
            } else {
                // No menu open — only open when hovering directly over the button
                if (insideButton) tryOpen(info);
            }
        });
    }

    function isMenuOpen(info) {
        const { button, parent, dropdown } = info;
        if (!button || !dropdown) return false;
        const visible = dropdown.offsetParent !== null;
        const hasActive = button.classList.contains('active') || parent.classList.contains('active');
        const hasShow = dropdown.classList.contains('show') || dropdown.classList.contains('open');
        return visible || hasActive || hasShow;
    }

    function tryOpen(info) {
        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);
        } 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"]')
        );

        trackedButtons = buttons.map(button => {
            const parent = button.closest('.relative') || button.parentElement;
            const dropdown = parent ? parent.querySelector('.dropdown-menu') : null;
            return { button, parent, dropdown };
        });

        if (trackedButtons.length > 0) {
            document.addEventListener('mousemove', onMouseMove, { passive: true });
        }
    }

    function installMutationObserver() {
        const body = document.body;
        if (!body) return;
        const observer = new MutationObserver(() => {
            clearTimeout(scanDebounceTimer);
            scanDebounceTimer = setTimeout(scanAndAttach, 150);
        });
        observer.observe(body, { childList: true, subtree: true, attributes: 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';
            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';
            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';
            console.error('[GeoPixelcons++] ❌ Pill Hover Labels 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';
            console.error('[GeoPixelcons++] \u274c Theme Editor 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);
})();