GeoPixelcons++

Unified GeoPixels enhancement suite - by Pixelcons

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // ============================================================
    //  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,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAIABJREFUeAFUu3dUHEm27qu/37r3vXvnjLkz03POmDPn9EwbmZZ62k23WqZbLW/xtvBWCARCQg7vPTIIbyQkPBTeFhRQBYU3ZfDeFZR3mRmx460s1Ofeq/UpVmSSUq21f/HtvSOyOPTf/99fffrxvz1LdxDy7o0L7472hw71PBjquSfi3R/qvj/UHSrqDhPx7ot4YaKeB6Le0CF+2DA/VMS/L+q7P8QPHuLfG+KHsPO+UFHffVHfPVHfvaG++4N99wZ77wl5wYKu4IGOwP62wL7W4J6WsLqK2MbGyu5eHo/P6+3r5Q/w+wX9A6wG+gf6+/r7evr72gf6y7u6MupbHpa3eRa0OmQ2WSbVX4/jXotruhbfciWu7TKr9stxHZfiOi/EtJ+Pav8hvOXMo/rv79edDK3+9l716Qe15yNrryfX22Q1euQ333/XlNTQ8aZP2Cwa6hYN9Q4Je4YGewcFPYPCniHhwaR7UNAhGKjmvq0vTux7HSUqfSx681hU9lRUFi4qixwqezr05sng68eDpQ+FJQ8FxQ8FhQ8FhWGC/DBB/gNB3gNB7oOBnPsDr+4LskMF2feE2feF2aHC7NDBl6GDL+4Jnt8TPAsZyArpy7jLTwtqj/fL8Lf58j//+N/+n/926Oypv2+tJWNTLNBRwEQDEw90wvsJE0+YWELHmy/jgIkDFAUoBlCEeRIOzGOzHhLmEaEeEuoJUI+I6TFQT8D0GAwRYHxMDE9A/5TSpO1tl+3vDVK0msGIAUCAMeCDCQ1Ig7FMo+vYUORJdkO7F9wb5+0bNixrN624+47tWpdeo1u/wWtQ5z+svzNhvCc2PpijHi4YnyxTT1dN4atUxDodscY8WWWeLtFhC3SwmLo9bnQf1Drw1Dbte7btezbNG7bc2cC+1YzpjaaNvXmKlgNSEUYJWAVIDrALaAtgE5gNo1I6OzQ/UKIfSsfCGBiKgqEEdhyMBmEkCMNhIJIVPxp6o6AnCnhmdUVDVzR0xkJnDKuOOOiIZdUeB21xuDUGWmKhOQY3ReOGCNwQwXCjNirjTn7610PD/feBTiAo3qw4guIISgCUCDiRoESCksxjAmHiCI4nOA5wHEExBEcTHElQOEER7Mg8JcwTQj8l1GNCPSHUU2IKB+opmMJpfcbudrlSPUVjAwMYACMCRsAGVmgfMeNyVfXc7lPegmvjkk3tukXtrmOn3rVP7yky3p0xPloyxe7QKSqUqcPPjPi5CT83oRc0fkHh5xQ8p/AzE8o0ogwjzjBAqgGn6lGyFidqIFED8WqcoIJoOXq4Rt+VGT2G9Dcbt67XrlrULjs1SBPHN5s3lbM0LQd6B5gdzOwA3iZ4A/A6oDVaL1kYXhS+NQxnYVE0DMXCUBwMxYAwAgQRIIiGvhjoiSFmAS8GumNJVxzpioPOWNIZBx1xxCxoiyNt8dAah5uiSXMsNMWQhmjMjUIN0XR9ZHGY6yFKF8vGGicCMo/sPJlAMsHJgJIITmEZsDCSCE5in4QkMJMgOMasOIJj2QmKIjQbdEJFECoK6EjGlLq9Wbi20WtklMgcegxAA9YD1gCa0+prFnYfdc871S/aVMx6Nq47dGpcB/UhUn3kFp2ixpkG/MzIPDMxLyjmhQm9pFA2jbMplMNgVuwcv6LY8aUJnhvY5zMNkGnA6VpI0kCC5j2GODWO0+AENY5X4Zg9/HCJ8hszuLZt3XjV6cRd8utcKlxQ9it0K5jZALSJ0TrG68CsA6xivGbQzsyKFoWlpvcYoshQDAgiYSCC8GNIbxzpYQW8WOiKYaP/MwDSGc8CaI+H9kTSFk9a46ApxqxYaIgGbjTmxjANkaKXYYcAsbEmkGJWKoFUAmmEpBKSRkgGgQwC6ewlpLP3IZWd42SCkwhOIJBAgMXGznE84FiCYgkTA3SySpG7vNyk1e8wbJph/yDAJkAqwKN7qpzJNc968bU3i5ff7Vq37r1bU20w6LVcn6em64wmNuI0k4twHoIcBnIRzmUgj8H5DOTTkM/gAhoXMJBH4zwacmmUY8LZRvzSCC+M8MyIswyQqmOVYiaRpIEkLSRrIEWNkjQ4XWEM53UHJXm/rb7j/qrItc9g3Sh3bV6LFKy2bmtlFL2MmRWMVjCsAFrDaAkxi3r1tES4NVhKD6WCMIoFIAgnfbGkN4H0xpOeWODFEl486Y6D7njSnUA648wA4kl7HGlPJK0JpCWONMeS5jhoioOGWGiIxdwYxI0affXwEOB0c4gz2HCzygLyjJBMc/QzCcki7OXBnUzz/YMxw4wq5cArPyNJApRCm3JWlst35VIMDADGBBiCKTb0SLSryBxedK4SXyxevPxWeaNOHjipyNJSpQZTnVFXZDLWUqYxxBTRdD7CBQgKERQwB8JFDBTQuJCBIvYmLmBwITtCPoJ8hsWQS8GrAzcYcaYOMvQkXUcOMKRpSboWp2nQ812lz7Po8sYgoy7MuM55U+bu3zLuJMK2PcimRefI3bzfvVy7ppo20csHDDAsYbyEYJZGc8pd8QRPKShAgmTSH0P6Egk/ycwgAXrioTeR8BIJLwG6EtjoH4wdCdDOLn/WAWz+iYOmWGiMg8ZY1BiLG6LHch8dInAQ5SxzcJ8DeWnWcyDPgDw3KxtINpDnP5N4Rv73PMtMKOvAH4BfKpRlS8u9FK3FgMEceiMgDaCJfVWWaIFTI7tQtHrpneZa5fad4b0sFZVLM3k0yqNQPoXyGVRMoxKGKaahmDGLxiUMLkaomMGlCBezc1zCQCGDi9iHDxiwVPIYsxso/NKM4bkRMvQsgwwdYSdanKmhMucXgtODllaTEXUfK72QzFk14x2c/tRnQu84iR0GwKoN2zRStjVbdztXq1fVYopZQQwLAMMcYhZokBkp8frCkrDW2P8S9yUDPwl6kwg/kY1+zwGARBZAVyLpSiKdiaQjEdoT4CAFtcSy0TcnItwUgxvjcEPMeO6jQ+YQvzSHOJuQHEJygR3/T+URkmN+4NXPj+USkkfIKzOqF4S8IJBFM6+XV7m7+4uIzTnswseAjIDmtbq88SW3Wun54tULb3WXKrb8BdsvFKZCispnmEKaKqKYAoouoplimi4xR7yIYUoQq9escClGZQi9Y6AM4dcIShgoYuANRccMj+Vs7byhmSKGyWcY1g00zqZxDgXZJjYdPdfDMz1k6eCFjk4fH3pRHmQwJGAqCMk95EL7+TZbw4Rd/TufO23DHpOU2xjjNIytuvCtBmTJZawqth90r7Xt6mYRWkCMjMZzNBbTeIrGUp1+emZwm1/A8DOgL5H0JpmVbHZAInQnkG4zgP/NgC3CwGah95WABdAUixtjx3IfHzoIujmgBYQUElJMyGtC3phVYr4sJuRgUkRIASH5BA4eKzygBZCrVFUurfaZKB0+yPcEM4D2aIo7veBb3HHt1fjV1/sX3iod29ezdrTvaKrSoHoxLgwtfRGQGRX0KjG8oTJ3QVap1782Um91qqzx/sj6oiflz59WZcd11RUszVQbNGUM/Zqh3jDoNcOaIG9rx72q06Fe6PiuLbB9IHdXWUAxeTSTQ+FcGt6nIyPK1VPPtxV3qxud7nmXVoT09vjtzAW1ldyKD3d0crMLfhro+/jR155RX4cWfvuo+MfkRuu3Ey5d+zbtxhtc+kat0bZiPVa0OaClJAyeQXiaQVMmmKRg3Igmd7ckg/W6vheYn4T5iZgfz7qhNwnYRJTEYuhKJJ2J0JEIHQmkPeGAAduMtsSy0W+ORY0xY3ksgIP4lhFS/rNqgNQRUktIJSFvzXpHyDtCysxsSn/+J8WEFGL8ZmOjfkcuRYAIAcL+ZbvMyT1ljkh6KjD7n0/6j93t/iaw4urTtJyx/kajIm9m8EGiW1WZs0QQPNbt01ltX1lgG/Xkhzth14PTQ+6G3+TzAldnQ/c37u+u35uevFNZ65z43Ole2p24lop3e7tvKLoQ4ce9Anfe2q1BylZodOtT3iwTuFf1xk/NlRhpNq1RTJ5c5VvJtXteap9T517R78Od9KmecEgqvnr/kU9FV/Cg3KFp+Vb57KV8yam0yR/TpbcK1i9liL970vq5f/5xnxdfP353vURi02CwqNa41C7nzu4LTcwYg8YZNEwjEYX5Km3dlOTZy4zchIfPo0Pfpd3b68zEZgDASyI8sxVYEySwuagzkU1EbYlmDLHQHA/N8agpdjTv0SFzZCsIqQZSA6T2Z9UBYWVmUG4eKwmpJqTqZyolAEU0U7W40qTW7bI9Dht9ttncoajG5e2QjsVzsc1n4kRfREku3E19+TpsXhI8yOfEpTilZbj2cT14FbcG6q6sTjrrt/2XJx225lzVm/7avXuG/YBtmfXS8GX5zHVq1Ypes2Y27OgtL/3G/cFe35gUh8iK3Hcaze3aFvsu+ZUO6jwPX+iGy+30jWb19dJpx5KOuKFJ/4o6i/SiOy3TgUO7XsNKbwnlM228VTHu0Cj2Hld7z2PPWeCIsdMU5kwwbqO0deP22czh7+IGf3w2dz5//2qJ5vJz6cmHlSf8c05GNttV7Fq+3fVv2aja1FbLFu9kl164n/pdWM7ZhJZLL6ZuFi9fL9u+USB78vQpw0+DniToSQQeK8JLNtfkBOhIwG1sRwTmPQG0xLNqjh81p6Bqwsb9fcTNk/qfR6558n9RMTvjHUCJwVi3vMZ/X2/ZzINNgCcV6qyxVYfq5WuVuu8iWs9m73zpV9LcGGjadTRuu472OEQFf1VX6jjT77Q778SoPEHvDgYXbHAHgysYXJHWyXzJwTp3xZLdvOCScYWDtuzxmh1es6OX7fRSt9EOzwfRHPe0LJcB/Y9lW1e60CUeuciDnzrxxSbq/PP+c09f+jSO3B7eCZyjAhZQgIRyal+2Khfa89a8Z42+q+C7DF4ycJ1CTuPoZpfSqmXLbZJ2msCOvaYL+dJvonsvFuyeLaPOlhiuvFZey544FVr6z7ulZyIav7id8UNclVXVvG2X7lqH4VI7c6kNX+Ca/vlG+VMN7R+ejvoySG8K4ScfFAboSSTdSe8LcnsC25K2JUBbArSa1RI/ai7CDYQ0AWkwiwuE+39M/s/LAyq1QKoBqpTqls2dKQZTAEAIRoCVmGla3QvuWrpRuXurgbFp0ZyKbv4mQ/6lU0pcpNWa9PazhFNNFY5LM77rcx609g42emGjKxhcCCtnYnAGPQepnbHeBYyuYHQBHQftuS0OXFJIHGCbg9dsYNUelu3VQ9cUwz7+jzw4XYqPA6pvVe5e6kaX2rFFs+67mFqbgo5A0XbIIh2yzgQvM5yO9fMvx6+85HkJdnxmTQHr2H8Nbi+Bl5TJltP3pXrnMeZq1fr5/An3Edp+EtuNIKtm/TcxA2fSps+X0SffoDPFpu+TRy+mdNwolVh16C91Upc68U8dcK4NX2imv3y2ejhu+WSp8Ydqg3NIgrrz2UFLCr0HrVGyGQCbgsxNURKwJkj8GUDCSA6bgpoJaSGk1awW87yZkKafdTA/eKYRSB3GjQpV987+AgbELnsABtAWTRVLt10b1i25hlstJvcBdfqK6lRM4z+z9r6yenrr2o9ZSZeXpSFz0xyT7h6m7xLKF4yu2Ghe+HoXoucQvTPRO2ONE2sFoxsxubEMtE5Y7roiuCqfsIUNDqzawao1zNlvtV+JjPe0bZMfDmo/zMm9XqOyaNg9G/E6qHs2UKJ5tIkebaN7i9S1Mtn53AXryjX/ngWXEWXgBr6zQe6sgP8yuMvw4znDvXnKaQY5jaHLb9ZOZwhdB2m7UXCYgFsd1JnUqZORA2fz97/OkF2rUV5uoS+0o/MtcK4RfmjGF1rRmZKtY48F30cIv0qeP1tlOlej9oh7PdTOVXRmYV4yZhvTZMIWg2TWAV3J0JkILIZk0p5kLgYJ0JIwkvv4EJAOQjqAdB6IkC7zZQch7QBtQNqBtBHSRqCNkGaEW/cUQqVq/SD6wB4twLzOmDm+bcfdsWllrDsMoWJdroZJnZb99Gz83Ivtby9cri93XZT4rC94YyYM4xBMBwLlA0Zvo9oNjB7ARt+F6FyJzgVpHImeAwY3QnmB0Z3oOVjliHZd5zovyUfsYNUJVlgTGEXWj+5fcWre+XvEyoecd194ZN1KevtwbOfeov7pLoqSo3sy48Vi6dU3G3ZdlF216M6UMnAV3d3AgesQwAJAblJwmQbHGXCYIU5isBdRFwoXfnw2Yt/PWA1ju3F8q4s5my755gn/ar3pXDs63wQ/cc3Rb8IXufp/xI2c8i99cDft8lVvi+KNszX0uUqFT8rb6W3dcF/vVttLzEuF3jTSk8KWge5k4KVAVwob/Y4k0p7EmoD1QdKw2QHdhPyXeIQciL0DpAtIO4F2sy0aMO7Y2hlQqrYxm3YIBmwieEylixRs2tTv27YjJ74+ZpUqNqJ3GN9r5d/Ml17xDxEJ7s2MOsvX/TF6gvFDvfY2ZQzAJh/dngsy+ILBHXTORO8KehYA6wA9h1a4AO1LKA9idAWtM96zp9ddx6p+1IlZE5BlezRhmxF9xalK9tfwtc/D+i0jX0RMbT9Y1kXvo3gFCpkzXn47b8lVOQ0xNi0b4aNL3jO6u5sQuI4D1iBgGQUsM24yzJkBpxniNEMcp8FZAjYDprNZkzfK5m/1IYtBsBmB6+3oVLLs64jhS3XUhVY41wTnm/Cpgs0vg+vdfRPa4tKWsp77e4XfqNeermd+KpcHpZXNqpmZPeP4sGi9NQfx04CfCr2ppMcsXgrpTiGdKaTDbAIzBlHuw0OEDBAiJMAnhE9Ir1nsBIBHSI/ZFu2EtCLMW98UKlQ7GLOZBwAMgAf2tHfbl6y5avtOxlOkS9+j3yD8Fth9k3/xW9cHgdKZJ+MCW/l6KEZpGEfptbe1Kl/M3DGqvbS7LmD0YmuA3hUMbmBwJTpX0DhjjdvK6HWk9yOMN6HcwMDBSkdmx1EndRC9O8csOMOyAzNpW/X8utWzzqP3By+GZiVL5Q8X1AkKJlGFHi4br9csWjXrOGPYcRQ5VQ3FLav9lujgTQhmlz/2W2DuLNKeMuwqZqN/IGcxdpchmw79N3ED9h2am73YchCsRXChnv4mfOS75LmfGtCFRuZkuvRH35yXEekz+flrBflbpW89g55faTT9UIvOFa3fzyqb1dDzapDsU+Nj46stBag3A3rTCUsinfSkAi+VdKeSrhToSCYdydCaKHrF1gARwDCBEUKGCQgI9JuRDBDCB+gBwgPSgXDv2rpAqdplA0/YQQ+4T671a563adTZ8eiASX2OFr3BuAxwOYNqab3PE7d5yZOJAfu1hRDEtCCUTZvuKXY9AQUhg+/uihPSeoHJHem9CB0ERh+zFVyw2gk0roPcC8YdH0B+hPHEJlesc2bkdsyG01zr9cWOW3jBGU87zzS5WT5IPuGa8EK6HTojj9uj01QofM3kzFu3ala7TWLOBPIepe53joXNawLW8Z1NFLbBhK4z/vOmgEWT1zzylAJHAk7TxJHFAO7zyHWKuVmjOJU5bCtEN/hgNYQsBvDFMuOJu92Xi5Tfxw/e8Ex48zx3rL1luq9b2Fj7Oi7B+l7+hUb6h3p85rkkKr9KpmZkGixVI4mSnpgWr7YXob5n0JdB+BmEn05606EnFVgGaaQzFTqSReazoFECowTGCIwCiDAIAQZZT5BBQgSE9GE8sLYxqFLvshWXzTygxqhXrvZrmrVr0tv30KFSbZEJlQEqA3iLUSVDJzYVTYw9mR12Ew+7YKaOQVUM9Wh7zR7TgdjotyFz1Ox4Ufteyi03TMUA5CM6AtNhQAWBzhOr3Xpe/yjrsQbqNkY+iEqg9U+wmmNaszJIHfryf9KMcUDqoh3zuXrrTEJ7/9Pp7dgtQ4aaid6iQqc1N+vWXUexpwy7TWOH9tXkqYWwBdWTLdPdTRS7a8rcNz5dNgSINY83jA+XjXfnjc5iNhE5isFJgrwXkJ2AOv1sxqJx+5YAWwqQpdB0uYU+n719nJPtFxQ73tlFG/UYMI1Mk4L+8vTsKw/Lf2pAP3Hhq4jekNzaKS0tVjISJZaomGklMyWeW+0qxf3PoD+TsFZIJ70ZwEsn3enQlUY6kofznhxio4/HCIwDNmMgI2ZPDAEeBBjAINzaHlSqdsztJsEEqzHu3VcHtErtmnVOfPrRvPY1gysRXcPQ7xhcgag82UhrT9i27Dafex1RcRilISp0b83JpPEGyke35jHcaYm1vttip901f8S8wqgK08lAR4IpGKncmD0XUdXN3sIbpn1PoP1pOhcx5Ujjiffs6CW7qZqrs9ybWOaiGPN5nBhSvLoXtaxJ2KPfGlD8msmiRuo+zPjNY08x4y0Gq7dDJXJdzpoyZ98YvI0Tt41VGkP6iv728Ga1nqlQGqs0yENCcySEI8XOM+C1gN0lzK0mzenMMTsRc72fsRJRlgPMuTLD+fD2jnd12pVlDDQCanNjrTwnpyY998dw7jkuOl/PHA2ss37RnyfZm9IwYjWWaJgZFZpSMpMS2QavlBnIMvsgk/Rmkp50swnYXCTKf3wIYIygMQITBMYPfEDYjCQiMIRBuLsnUqg2AfDBGYMaoz6FJqRT5tCqd+4zRa0ayhCqQWgO03OYKUdUFaV9VR+l2Qjtrr5mUN4H9ABQAKXykK/ZAeVt2nbtKr0yP+6N9z1Gm600e6GYjgSUhqlQMPmD3gMrOMZ1x7HqW+3JFsZFdzD5YlMAYwxCGlfQONIrlppxh4H8izqR09sXFs2zM/Fzu6HLxsfbTMIW4zuw7tS57TGDglaQ3yz2mwan1z15e4bsTXXyjiFkC0dsGMu0prhFvQtvJU1OJ6wbilSMh5RylmGPBewyCxwZ9p7H9kLq1DOxZdO2xSBYCtGtfuNFLn2pQHsvvnJnbJpS7WPGQFF6nUqRnvryfELfDzX4h3LdJ54lnIoVJ668+GcGM2p6Wo3ECmZ8RrrV8wb3ZeG+TMLPJL0ZpIf1AXSlDuY/OcQufxbApBnAQTo6iP7QvmL0IPrmfh9pMBpSaR50z7p16twGTAmbpreYqQBUh5llzCxhVM3Q6X1Vq0sRM702S2McjO9jyg8ZPDdnLbHBB2s9t4TOmXdP0Pv30JpXWdJJoyIUGdyB8gOTNzF6EZ0rljupZHaTtRZjz32WOqyRygtMXsTgBhoOaDl4246W2fNzz+/1uCek+ldv7z+eV4dtoLB1uL/A2NRNeU8xd5aYO0uUnwx58LWu+W3pu4aITcOjTTpkGweu0I/X6aApo2W1NHAVBazge6u0hwxxZsF1lnGfAxcpeM5h53H6Olf9w8txm2FkIQDrQeYWjzldTN1Mnuip7t6bFBt3dpBBS5k0gtHJC2mCs1VwukT1qdOLa8mt1k0m5/qtslnFtJYWaxipBktVzJSCGZ+a3ukpQb1ZwM8C1gQHDFIG8x8fYtc+mjADmDAXg1ECwwBDau2EUrVhPuFhy64ao0GN5mmP1KNL4zloSt42lWOmkuAqgqsw1DEshlqTprTlsXbVp6/qOqMNwnQAmDz1O+7yJXts8NBJnVpTrvQVOaO9AGohIDf0n8a9u0jHIWz0PYnBgw3xjvOG8OZ0rcV66eOBl7eMS85g8GF/xDrAFfad6Tmb8fLL7Tm2b3qakuZ2H6xQj7dwyBLcGVM5ts17SOjgDXRnmfEeZ25mCQJyK6JX9U/WTWYA4L+EAhaw74jx0qvBgCXGfwn7LmL3WeDMgsuUyXsWuc0SVym4S8Cab/wycdhpQG8hBJtBuMmjf6o0nc/VRGU0yoXj+9NS/eamUasQjk5dTBedqcDf5+x+5l31iVOmV9f+jVojp3ajekkt1iC2IGuZaRWS7DOjY2OK3lLMAnhGejJJTybwkgbz2Rcyk4AnCZ4yZ6ERQkYAhgzGiX3l2n9lHhVGo1ptFF/m0aH2HjQmbxkrAXGBrsNMNYYKwOWAKxDK6K/bWH4w3XVjbcwJmCBk8MRq97VxC1rhgeTu8w02fcmuw+UueOf2Ks+79ulN/UYgo+QQkxfbhmpdiNoJbzkvdl6fqrLcLH4qeHFbNeqKVT5g8AKdK1JxQOnCLNhu853Cgy51KVVBk7thW8zDLbg7h23rpu7MmPzm0N012keCXNp2Pe4kpdXUh83pHy4bn2xR97ax/yLjv4C9RYaf4lv9pym/Jey3BO6zxGUWXEZNjoNqjznsIgZ3MdgOUedfb117N28pwFZCuNXL3GhlTuWZLKK6ZjuG5P0jikmJYWuLExJ3rWT3x0r4Pn3pI6/WL0L452Lq7Hn0lQqDZ+1W24ZOqkOzGpCqmBk1nt6jRocG1PxCxM+A3gzoyYCeVCFbA/AEwdNmTRAyDCCi6Uk5e9JgPtsnWIPxiE6dKJz17FT6iJiEdUMlZmoJ0wiIi1EVoDqGfrO7/7Sd/7zyqW7dQ1R/XbfphUw+oHOhtt2WRBZ4z007ad8Yc1VTGz9S5w4bAb05NuPZoQrZbcOWPbv89e6gcSYqR7zpKGm4Nl5hs1USqRqqFRRZ6Obtsd4TtC60wglULsyyvXrEMzMvKn1+9968PlyO76/iYDFjWzXuNU0HLuPAJeQiNFjH1HZnZj3jNvqPqO4vGMO3Tfe22B2A/xz2HTJ85Z3mNaDyW0T+S8RdxmYet3H6YvmK9xxylRLONDhOMLea1OdejloLGQshsRKQG53U6RLjlZebb/K4O72DU62dsqFhx5jqH9/SZyvQsft9J0JGToQvn/DJ8RWoLDrwpdeawKb1frlRpsOzWixR02Ilmt41jPQ0mPpygJ8B/HTck8qmoPfLH08fOADhyb39WYxptt8nWAd4RKfJHFv07pB7iejYFX0NRrWAmhBuQwwXoXqaCanpPBXecCmqTih6tD1jN9p0Fel8kc4NNC6bY1ZKmRNedVltthcmuRpbsoa5Pszmg6Zkh9V3SUtDPqoFa9B7EoMraJywwh5vOk1UXhircNp4HYdWhztf+atHHBkF+18cqnQjAAAgAElEQVQx+06gcEFr9rIuTsFAx6OZvbBVKlqO7s6DT/8ep23RS8wELSKfGcapcTsyMGb7dVFmU5srbztkWh+5Td3fxP4LTMA89h3U/egY6l42c2cObi+Cxyx2kYLXNPo6adB91OguBedJcJ3G1nzTN/F9znyjhYBYCsiNbupiHXXmlfZBUv1Ot6CmoMorJPVSkuj7N+jMG90R/9bP7kkOP9n5PqLPMrfXWYRuteALBTtxfdtjalqmxVI1SDRoWkGPr+5Ot71l+C8wPwP3pAnNRXjyvQNgEuHJfeUszRjMr1bY4+UpgzZfvOLVvuUzzEQsaqswbgDUgPEgY9rDaIChMial55L6v4pdsYp5rpEHC1uu7YldweiFNU5YxZHxrjIrHqZxx86Uq/KqlMEXD6qe2y4MBuYH39xuKpzu9pBP3sRaT3YXpnai9+zQuuNk5WXRW4fpvCdLA/Wi2mdjVQ7yKVukdkNKR3rHDm0415Y4vtmU+43sPtmmY/bQbQmyrpwKGNF5y1DgAnbqVdkn1Aynp2vqKzMbO6xrpP7929Fy04NNdHuBCVjAPgJNRGSa7f0c31GD7wLynAOOmHiL0RehFbbcXW8J4zgBLpNgM0Cdezll3bBiIQCrAbDgo6st1Le5+lvhLbK2gbBHuZ8Htp0v1n9fis6+2vjYvfFI6MKxaPW55zvfBBf5TlG2/egWF1/OXcydUIq1SKLFUi2a0+AZJTMsnV/sKmX6MnFvqrAg/BDgcbYA4CmMp5Rqmd6oMbf8wACWGXTly1tebUt+w/STOV0Fw9QDaiRMPeARZFJgJGToB+395zJX/hk7m/XuqX7Xu+Wtxd6sIxtQjRO9w5ntvonnXTR9HoVBF4aeP9WJe0V1j00raa0pd6Zr8/jVPnKRNd73Yo/k1M4UG19Of+HFlhfXOxM957kv51rzqhJtugrOzfRab8vsTSsOaN09v+xB8rLSd1geo0CRcuQ7RVuWijyHjbeXcKCYcqpfCr8fr+ZW6PkdGQ0dlqVjDtWTMXL64ToKWkIBc8hvQJ2VlBkRGutRt3ZHSvnNMy5i7DqDbibXXkoVeI6YnMex8xi2H2KuvF27WThq1Q9WbBaCG5306demy6nTQaGZX3HyT6Yrvs1DP7ymj4Z0Hw8cPPp4+1iU7p/J+1+F1fnxdz2mwLqHuVlDWxUt1ixoJBok1cC8FqQqNLlPCYdHdvilmJ8mLIwwt6F4EtCUTj+neL/hAiBo3WRo35X7tsz6iZgHUs07mo17HTANBFcDqkWoEdHVGIcPjJ1KXzgV3iKWhHdxbZtKb8rn7ZCWA1pn/QpnvsvCNOHWkPjDdO5T3Wgb2pOIuAn6pee8Zw/o9YnJvudbA47MhhsYPbHGybRthzbdeHnW5fE358qjYZnPyCeWB0pljfY7w9a7Moet0Wv6hduxr1MfTO0Fzmjj1Uz4FvIbp24VCV3HjLcXkG3Tsk92+7uo8MnCrJnmiqyGFssCgUVW8+NV/YMNKngF35Yynt2bkY9jisJCLgekW5WN2TTN3WpdtR9SuZV0O0dX3arZc5tE9sPYcRRfr927/KzHVshYslkIbvYw52vpH7Pl396tP5ki/2c2/XW24Wz29qee9UdDZMdj1EeidScSlOefSW7k8z1mkNs4tujA197pPCoWe3eNUi2a1eJ5DUjVeHxXL+zrNAzkCYsjDxHM7oRpRra5u4wPNlwASpoaUquD2qR+g6aQGV2pkanDNLv82a4fVwOuAFwFUAmQJp7/NmHcNeXF3npEaPDNsU7PvXkbRu1IdC5707brPLu52svCHLf95gK8M4VVkunOgn1J/lBhDJaLV6Vt4jZP9ZgTaLyw1tW4Yc9s+AyVeXNTvYYLH8HqANodQ5vDvUXeSy3X0JI9mrWS8ThP62sCR3ZD50zxavrhGuM/ZLySI3CfoIPG1BfjK4MCH+60VBglw4x6Nb+9yyqv627am+Bp5cM1KnQF/MSMW9tiRnqOrrsuPuGZU8Wir8jgOmqw6ZNbFPOtXYIuJfRzenR2g8h+BCxa9Geim9wHKKs+sB4Ayz58uYH57qXyu6eC7zLVX7+kv8/Rf3aPd8SPd/Tx5vFY/bEo3ZFI+eUCxdnoau9xxmcGOwrRzWZ8oWD7acf6pBrJ1GhOh2c1IFHD2IZioqtSWBrDHkUgNLEtn6UYE9v1A5gAzWjUUX2zXj3auxP6Ag1VBwwXMBdQE7AV+D0AzAIolivOxLTkV0WVlnh5hDzYmApWLTnSSgfQuizyryoFzvp+r8EMH/1YC6jEjFKiXxNNdqSNvE6CvWlaK5tojxDX3qAWHbDak97m6BY9RyuChkpju5+HopU+Wj6G5COz3QXDpRbqEWsktWt5YxEvnPQVbD9YMiaomXvLjEfH7o38Qcu2rct5It/MJu7L5/RYL6hWkHalspvHKeh4kZVzu3v+/prp/jryldCu7UuJmUVGft1sb4tfKteuettzxOQ2jryHjGnZb1wCEs8lDVyvWnHoN1p30CfDuR6DGos+ZCkgFgP4Wjv+Nt/wdcTw6UzFlxm6b1OX/+5SdeSe7LNo1bFY49Eo7ZEoxXeZu98/rfYf1XnOYLcJZNuHrzfiS/kLr6VKqQ5JNSwAqRpPqNDgjKS2IPUQ4BGNfk6lVWDMfq0BEbyo07yZ23Rt2b4zZnqloGoB1ZvFZQsAqjYDqAWoAqjCUGuirjxOGB6Ksg8KdQ2L2Z65o1xwpvYciMZ1suG8UeS9UsWZex2LtsdppYTen6V2psY6CkYrMrF8EjRz82M1ra/sTWOezKIj3nLfn3KaqLmjmWzmZoXSSzxGPs7IJ5iNke5XvtKqa8y4c36mZcLkhidv88k6Fa9AwfO0Xfncj6l91l3Gc8l9YQkl27wmvDUHmiWsXRWMDN4pauutfHu3oMl/QhmyRPmLGZeuzcCEAu1AE7U5VVVUeDtfZFW+5i2kOENMUEF7zcs869T+G6U7F3NlFwsWLsa1u7dv2PSC9QCxGIAbXej0a/q7eNk/Y+ZPZsg/8qz9LHj06JOdY9G6Y3GGo5GaIxHKb1IVZ+N7PJoX3CbAfRochhmLLny9hrIrlfK3TVIVXtBimYqRKtHEPvWssOgQzUh2FZuY/VIJ+45lx2Tg7e55cOcCRHTalqkG2MLbALiB4DrWBLjmZwDVgKsB6hHjGeP3pszz1pMatwePNid99qROtNyeqF3Gqn80DHqOvrJX9VetiPvC7/nZXjnvZn09LSLkdWKYZnkYtBJGM8urim17dpWZcMTzDluD1mN1PsyaQNRYIKjIGqwvnu6tUi4OyMcaW1Ju7vGcMtNcoqaUrt3r0XImTsHcnUVXsidvFC1YNuku3H6Zl5VnkokY9TrWLoJuWa1YeZhT0VVWUP76rX/30t05Y+A84zWkt4wq2eyuY/Yk+tWR7Mwc34Ix6/IlTxHl071fV/ou6umzm6/WrLkGi0rN6fBWZ67YcgBb9BMLIdzi47OV9I8vd78IGzhyu+2YP+/TsJWj0eqjMYajsYYj0ZpPI1XHE/au5S1fe9HuOoVcp8FlCmyF+FoLvlS2H925Mq1FMjWWqbFEBeMqXNMnOrS7v0AjirBdPxgxPaVRh7SIfQWm+FVjJUZcjFsx6sB0I2sCtgjXswzMGDCqxkwzZSziJt6JCPzc692diLAVkfvasBO1bQ8qzmj5j7p+X36Ku7Cm6Pt/HLv2w9m4xw+vnPvR6upVf9ub88OtoJGCRqbeGHqd5Dtd6W4c4lBTHt15tuvDtfU5yQlhAe5W19ysr2YlPNyY4Y/XZHVkXE/PundftOvasx6rQLH7OEDMnEkdvlq67fBq/OGdR52lRcuDrUuT/eoNMaNZxdrV2uaW+qJc5Vj3k4JaN/5mkIzxHKKssrq7S/KwfNKolCkXh1KSMuxjKm1KJjwGtA9LOmXcGs9HBddebd6ooS49l9qVDtoJsDUfrAaw9QBcqKPPZCuPuBUd8ag5+mD+SITyaIz+SIzxSKzhSJTucITmSKT8auHOxfga3zHadZK4TBGnUWzTg69z8bXihboFjUyF5lQgVcKMErgDI4fUevVB34kJXtHrs4fnfbuVEQumdwjXAWrESIaYXYy6EF1HcB1BdeYyUAu4DqPqvT2v/PLH6fdPeif9zavR94H/6rDndLsVveMASufR8p/WuB4j+VE/nDgc4OqyIp0oKcx5cP/Bx38/cv38+c66EqyRglaC1RLV+iivNnPo7e3VRsfxt5zewshHLtah3l4cG5uTX37z3T++/OKTv9UVZDS9fBgSdd+vZ9VneD9WiSL3kM8U/V3y6JXiraiUyuHS3PaibFFrbVNZUVtl8fL0gFExv70hLs1Op+aFw72tAcVt3kKV55jpRv50UU6JaW1E2F7GbyitL86+ExB0/LzLaa9ki8cFhenPhEX5Do+rLufJb+StWxXwHYaZW31gOcieC11pxt+/Un8RUPuxH//I4/XD0eojMYb3AGL1n0aqP43c//7Fxg/RNXfG9S7T4DpFXCbAQQjXW+AW1+RVKRlTUqwDlGhGDQ2CkUMHB/2IgJphujflvo2Lj2ZMbyh2jTdg3ITQDDJtA+pGVD0Al2BzPYA6gEaGts0s5dRt20S++CK4+dOQKZfg27uTfsLyC8yWIyicxPVX+c9t255Hn//mi82lWZVq5+WL53qd7qefrv3uN3/mNVdhjQQ0YtDMYO2sYW+K3p8W9+QKKx7Xvgif4LWEPwj19PC4fu3mL37x65NffPPZX/9amZ38KOOZe/v87UkNC2AXeY5SX8WLbNIHB8sqp6renPv6yz/9/oN/+92//scf/3j6qxOjvc2MdqW/q3pF3MsopG1tDZ45Lb5CzfWqVZ/IYt3yyO7isJvNzT//4YO//Omv/+s3//pvf/rog78cOXfxRm9pfmFCgtXThuuvNm6+aHcZYSz52FqIbQfx9VZ0ukD3eXD7J/78ww9XjkZpjsYaj8SajsQaj8YaPo3UfPp0//QL+enYZt+uVdcJ4EyA8zg4jRDrHrjeBDfebb8UbUnVaE7FZiEWAGFP+sGEsUxvuN8iCRkx5GuZGmxO/RjXA27ETAuiG8zFoN7cCx2cAjVT1OW0Wotm6vPAd5/dH/84RHrV47ZyNqTl5UlmgwMKh/Ve26Zkx7u21+LCHyOkq+fWnTtnYe/g+cvffPib3x5NjY9lHaCRgHqK6CSglYCONQRopSbF/Jxk0sHe3kQZX2bn/uo3f/nF//yt3U2rLz/+2+2kDM/OtTtibbSaebLDuIiM38QI7kSU8LOfffO3D//tgz/6ePsHBoSc+v7Cl//47vG9u5RyidEujA026BRipJJ0tDd5ZFR6tcuvxbcKulpepjwNC/Tjt7cfPnzi93/46F9++8m//OrDX/zL7z7+y1+4qQklqVm28X3XslrdJhiLPmQ1iG2G8PUO5nSJ4cuHgsO+vYfDlo9Fqw8AHI2jjsUZP4nUfBq+/026/PKrSduSfrdJ7DxOnMeI8yg4DsGtDrjVhG3L5vp2TXMqWqLCjYLRQ2DufDYYKkMgC+Crnu3Q1cDmGS6Geszm/XrADYC5ZhgsAIJaMdOIUQuDrifWfxGzeCR05pN7smN+3TctL+5MBZfHfa2VuoHCQTPFqYu3z316N/rRfYR0sXEJHx21+MWvP/39n7/882GLqKcRWH0AYPo9AK2EaMVEK2b065kZ6Z988mVEZOIf//zh//jlX3/x6w/v+PrbXL18KyTct2czSKqLVaNHmzRHaDwd3RMXGtmQnPCn332QnJyWl5vHcXb77e+P/Pp3x27dsKZUy6CbRdoFWi0DrRRpZqUzwgeZxZ7ZfdHphcO9jUblSkpSfMnrt6KRiX//6NQv/3DmF7859qc//v0fH/198HVh+KME29Rqz0nKsg8fALjRzfzwxvDPyImPvToOhy2ZG1DDkVjTZwnMsXjTp1HaT8OVx+N2Ld9uXEmvdx9nOOPgMgYuY8h9nHYYgOstyKJeH965zB5Wq3GjcOwQECxHiLu05dO0Gj1rqka4FlhxzaE/AMBlyy/LgCVhNkEdQANCnBcNR4KGPwme//jO6Pc2t8cHH413OXe8ujpRfxXtOlNLnP4in/1Bbv2bHIbRPgmP+ebCw1/98fQvfvv515fD09Keg0ZGWAdME/PaJxrJgRj9xu2Au7//85lff/DF//jlv/33X37yH59c4ji6FOdkWz2I8e7dvTuri9EwYZu0k8Bw6mlrdeYr/2uXrl+5rlIqnz55qlSo/vTXf/z6g69+/4dPFsQjoJ0FzRzRydiaz86lJtVcR3d7Ulrq9FAno9twcHDS6XV6g/Hzby798o/nf/Wv5//296/OfnXSz+L64Otij7hXXlOUZT+yGgLrIWzBRz++M36fMPuha+PhB8vHY1SfxRuPxpsBJJgOx+iORKqOxexdKtm5mFTjM0G7jYPzKHDGsNs4ch4GOz5YtGLL8rWmFZ1Mi5oGxw7pAY9odbfrp+6KDG+NqN7c57DpHnD9f616gHpATZjuRUwjprkYagHqEIpqExy/3fGR/8QJl9KXLz1MhifDPE530U1R2a1VoQVedxWW2ZukrYZlEWOU5xWVnbFO5TxscArOiSkcbWlpxeZVSdTToBWD9n30iUaM9Gvevvc+/If/nz6x/Z+/+c//7/fffv1jSFBwWENtjd2jeI/eveA5bYyaebBOO/Sbzjxu9nNw+/rjT3Jz8rRa9eHDn9+45fa7P3z+69//8zcffFZT/uY9AK2MaKREe4BBhrVSpJ5DmjmdYu3wse/cAmJv2fn+8ref/ubPl37571a//cPn4aFPjv37X/uLCu6kFfqwABhrMwDLfnyuwnQ6dfk/nKs/Dl04Eaf+LMF4LIH+LJH+LMF0NNZwNEp7LEZ55tXWD9GVAZPGAwAuo4QzwspxCKx7iWUzc7thYULDNA+OHZo16JMHJP48ZdY2xQWmEbM73jqE2FVvBlD3vgHFTdg0iZlWoOsxrgOoxbhOrf3Cr/Dvbj0XOUErc48w3F+ReXBfXJDzPAZKrqzwrBQjQeKmJCzl0Wtj6+tLLgEvUys33/LlIRGFqp1F+HnJsz44mKslRC1G2qWYuKwzVoU3vat/9adzZ2/GnbeMK31TWVlSGJD4zL1PGTyvj1WhsHXaps/47YPm+4HhFqdP8To7FUrFH/504oMPLf7lg69/9a9ffXjc8sWzLNDME/PCB7WEaNmkx5JgJQO1dH9r4T8On/33r+/+5sPrv/7j5//rbzc++OzuB38+WfSy4JM//2f6gweBGcV+05TlAG0tApaBAH6qok+nr/+nc+XHIbMn4tTHE03Hk5jjSSyAY/HGYzG6Y9HKUy/kJyPqA4T7bpPgOkZcRsEMABxFYC8Eiy5sUb37dk7ZKBo91LSx614jDRebahHDAgCoZxmwDvg54WBz88PerAeaxQMMuxXAmH0ZUNn5lUdxYrKXTvUIQyCt9GwpPK0QcBZqLXvzL0sa7SRNocxUCx5pNcz0ddTVJCUVZqSXjAp7kGqWqCWgFr/PPGoxMUefqGewWirs7wuOaE8u10Tmb4Qkiq/YPtnb3ZrlNacWvvboV91bMMSqcNgmsuIbvwrrOmf1qLOstKOhVmfQ/e3E9ZPOr3/zkeXvP7a66JmXk5OPNWzagYO1/z70UvNnSUAt0SlWP/788kc/xf7HyaBf//nrv3zl/9HF3L8cuT7Y3ff13/7emffKPyXXb8poO0BZi8BGhG2E5EINOp259XdO+UfBshOxqs+T6OPJ6LMk+liC8bNE07E4/ZGo/W/Sds+nCxzrpt0mwW2CuJorAWeEbYfsh4jNAFh1YJ/6uSrh6KHQ+tF7g7oSDdv2mHseYJc8BvabiJjuwgwXUD052IWxxflAVewuDNcyuEGrO+/lO8gPMf/Gtg9SOmvXXFVie0Wn9XqLXW/OxYqYq0u1Gai/AfHrqcFmZnaAWR/Du1OgnAHVDFaLsUryc+glRCUmKjGopmnFXElpTdKr/pisGQe39NbGFv38KDXa/ayw1FOoDlnQx6jYb+Ba8o2fh3V/wynsKH87PyEwUXorj0j3NIlj4ohT/Ojt5xNdPP57AGzHJWVNoDbnOvMEVGJavWLpePcn7wrv9PEPT952jun71qPlql3YdB+/ITVe09se8vKN3wxlLaCsRzALYBAu1KFTz+Ufubz9JER6PEb5eTJ1PJk5nsx8lkgdT6JZE0SrPk/YsSxZvvqyw30Cu00StwniNgYuI8AZJfYiYj8IVnxsXbeVWtd5yL12PWnZVM8GmqlHmPt+/4V7GaaPoVowwyWsA/5LbIkmqBIzNRjXIlxLUenlqbvrDwHfRXo3Ru2I1c5401VaeVkndDIKPXpygnT8KtzLZbprjT31IO6F1SHYnSLKGaKcwYqZvWUBqMSgnAZz9IlSTJQzsDNOr03MCvnNr99I2lr0Q50Mv4Hpb83JL/EeVIcsGGLU6MkutuyhPn/I/zawO/ZRFL0uNul239W0Bad0Pa3cTWyWJ5Xyd9YXzUE3FwCNeeGzo9j8WRKimkEqaW19e1TBXFytIrZWEVOjcInsrqxqlgt5pu4GJa85ILfKT0zZCigbMwBrIQvgdLbiY9fyT0LEx6IVJ5KpEynoeAp6DyDBeDRWfTR+59ab7QvJ9SyACeI2AV7jTKCYdh0Dp2GwNyciBx7tkF5xKGxc/9bIRp9tbzDUsfkddyBqH5gFjJoxexB0EH22I2L3B2yPVA2oCuMahm426J4V3zXqQoH2oxROWOMMWg4lcxws+IkadadF7ryCUCzm4dF2LOqA8W6YE8DGCOxNmh0wzcadJSFmLxXshJ0rpmFrDM8P4EkeHmpXt5Qrq4uYlkrEa8h/les3pA6Z18ep4OkutugxfvGk7+tggYtbGJoSoA2ZSbmVmJLjHfTiUWwxl1tPqxdBLSXqg6TPOoCFbRb7WYoZ2J9R7yw8jMlOfT3zsmHnSTovJ79qc7DH2FUPXXXLjdV+pa1+EsZaYLIfwbbD2EYIl+rRmVz1J24Vn4bMHI36GUAq+sxcCY4lmI7Faj+LV57P2zwXW+M1SrtNEM44uI8zPhMml1HsOgYOIuwwTOz42KGw/1DWhpGLmUZiPnEz5/1GDK2Ynka0EFFNgBvZkzjEJiJAXMKqBtgvY9XRVAxv+PzdpOJiV8wEY4OnYccedBxQu+pGbfk5Z9A4hxa48fLv4vVhWBDCnBAWh2B9hOxOEMU0VorpfbFpV4z2JFghxQoJZjGYrbA/RbbH8YIApnpA1IH7mnFvE/CbUW9jQeaLgEFV8KwhVoUi9rBlr/HLCMEXIaIrdg8pQRcS9VCjfWvdDTvD/cZNGVLMYhVbaQmbfNhzJ3aiYosN+0H702R/Gvan0O64cnG8Jr8wP+nZCLdpq62Wbq+BthrorB0pKw5uHvVnAVD2I9hOhFgAXHw2T3/Ys/rTkOmjUfufJ5uOp6DjqeizZOZoInUskToWrz+epDv1cvNUeJXfuMl1gri83w2wewLOGLiOg/MwuzXjvB05VG1kD9rMYt/3NgFwMRv0Jsw0AdPEHoUyjcRMiLBlucG8P6hFKGtG8nXgm284KaMjYdjoSyvc9LtOoHdFWxx1v2N//o/MsIORZ9+Y4sBsT5KdCdgah50JsjdFFNNsklFKyrKiE+xuND0M6oh9OJCdMllbtCfrQ+yqnAb5BFoVwZwAi3vxZA9M8vBkt2mgsTAxOWhgP1iqj1OgqD1s02/8Klr0RcjQWetog4CHeS2mtnqqrRaP8GB1HPbfJ32slMyPtSKFFFQSs8/YkQWwNw3ySbw1hqV9aKgN9TbTrTWqt4WmiiLgvsUtVQ1ZWY8Eq7cltN0g7TAKtkMsgMv16Gye8Yh33eGQmcMRe58nUydSWQDHU9CxJPoY2w4ZTiQYzrzc/fZxtV+/ynXM3AiNgevoe7mMgssocR4hnPKRQw0YNWL8fqUDZi/Nq74RcLP5DUyD+TVA48GhtHmsJ+yBaL3BcDHi3WlOyP5eJKNw1W9ysMYD9O4msa2s8sYc1xKN2Bu7bJrjrsr6q9A+m20O8vv7LK8SNxak9T8O3X6WaqooRbXvjLVvRouz0L6YKNiFydJaH4HlIVgUsm6YH9B1VJXExwX1bgeK9TFKFLGHHISGk0mTn98VfGeXqhb0Qn8n09eORd0g7YfNcdifAaWYqGRELaP3xVjJis1v+++XPwtgdxI2xtBED/S3QXcj01LD1FcAtwIaq+nGijdpGY/G931mTPbDyJ4FYE5BXOZMnumYX8PhkOnDEfITKWwNOJGKP09Dx5OYz5Lo40nGzxL03z/b+yGhi9M07zIObmwnil1HiSsbevbSbZS4joFb1dihZsw0m3M9m2oOEs7PhmhmJwwXWIuYW1Km0WwCtg3FuA4xqcJha19LSvdkf95Rv8lhf+Pu/+fqrZ/iWtaw0fwn9/7wnXP22RF0sJAgwSW4jMDg7i4hCdGdBIfgEAgkENzd3W1wC0GCDgOMr9Xd61YPObdu3aquqTWTVFH1PP0+r3Yvnrd4ijn5xfJkwBnOsAUd9LlcZkuyv2B/CvDWEW+NuvqzEG9lb7Z7Mf09bPyBGqpQfRXRULXSUAqwNC9jjbpYRKcL1PEsOpyFh5OSxZ6bhq+1KcnB7bvBS/x35+D1KXSfFhlmrSuFDDx0KfzV1wXnh+HqONyZhIezGNnLFYT/3Dq82iAv165/Th3P916uj5KnK+T5MrxYRRcc/N9+z5PLQ2iiBw63w/5W1N8K+5vRUIu4pbowqyBygee7JGHPQPYsZE4C5iSyaCKfFEqUgtvko5YeJJ6rpEiVUwEtDdLSsAopJZO0ZKnSR6Fm6plN0Zp98YjnPPSax9Bj0DH6yGuB8pqnvBaQZ/38nQ4AOiBsxxsf3NqBzALw3FU7gm0ItFGYhls7aL61gNs8AME2QpxcFEzyovbmHMTn7kjkTfxki8ddOjP0RXNuYMb5pl3UkGEAACAASURBVNVxPtthvySoMzXwamcEXmER+BNrXq8B7sZcRY6koQJWfwc133e/FZyvDUMuB3E51CUHcZewPpzOw9M5yebwZUflUWV2FMPcq2I2cF7w+hy8Oofei2Kzwn2FoK5HwZ39lT/A+hjam8Nad87BCoNd+jq6WgdXW3UpifMfXmwlvV1P/7BcmLHV8UN8OC8jANMMf07B1RG4MIDm+tFMH5ruASOtN42VmXnlIRy+N0fiNAfYc4gxgVhTlEUzqVMoUQppV4jiPHh5pppKqKRCWjpUSQdKKaRSClBJxQSoJJ0yqk7NU5s9F0gPDDqGXkYDwujPI8855NWweKcDwHYAOjDcoBOhDgQ7EGrDKTFow84Aoy+zAIAngmSrCWcDuCLdTgprO+Mut3x/LTGhwA/xfaXzjNMe14E8I7DgJh13vmllz2c5XHzxOioO6kvxXh8oJy9X0dU6xVunrnBAIjpa2O/6wW+t5rZXncx2Q+4qxOjLJOiCgy6W4NmCcG3gsrdqISdh6LXjKxcj1ueOwClB4gl4fYoCliVmpceKAW3q0YuJb/KkmxPgaAGeLsJzjsyfr1JX+A+Jz1a/BrrzCjJBSzXqrEdttTet1ev9tZins3l0tohO59HBNPo5iYOFjVG4NCjuqr9pq0vIqQhdl/pwJM6zkD2P6BPIaZqyaCK1C8TKtwQknqukSlXSkEoGVM2QeYJUoJJKKH0QKX84p//gmb6r8p0jvXAUhHH3nqO85vCz9wLODLwaFu60kWQHBB0QE9AOYQeu+dz6ZAx9G9acW9zJWw982xKQpWOwTXDWNxi5Pmx/feiFJH7kb0/pOGO82GKvyxnMO4sHmMfVzI1C1lmxz0WR72mJ//Andkd2xP58G7hcw3EnVps17HJPl6mzZXi+gi5W8K7EwckSvFiS/prmTjfNFL7qTXSeeW/3K4+ZFWjJ+KfcZ+TmxQlMPAch24RJ6YlaWIdC9IZBcN1waxt5MAdPl9D5MsVbg5fL8HIF8VYgb6M15SXsboDdDai7DnXW71YUnnL60cUidb6A0T+ZpY5m0MEk+jUJNseEQ02C5qr5b+X+hU1hu8CLI3WeRex55DiJWNPU02ZCO1+sFNyuGLUk9+qCliJRTccEqGQCWhqJyUgFih9FtE+8p1+5Jm9++C8Q3ovIZx55zyP/eegzD7znkPfCrQUs3GkHZCfEFiBbmACZ9N8qEtmBUKuMD1ksBJsh+F9PBleKvm2Mby7GznZaS3i+lMhfssiQjLn2ZZmdDznBBRdBu8NirhW3OuC80O+8wO+0yG8ny2m/2H801aMrJ2prrBpccKAsEMQul8uhzpew7FwsguN58e7E7kBlf2ZUc7zDzAfGykf7zVSr38Wu5VEWrOefXbsunh+CV2cw6icwKT9Tj+xSjlqlxW2bBxQOtbUQhxx4vowuVtHlGpagyzV4ucrpqeK2V8OOetheJ+6smyjJIE+XqLMFdLZInS1QJ3Po9ww6mAY748LR1pv2mtlv38wtPD1q58J/Qc8lCXsOuswj+iRiTaGnTYRWnlg5qE0xmiP/+lIlWaKaAdUykWomUEkHmIA0TIBqMt8w/9zg5Y/geYnvEvJboHwXyPxdYem+JGBe6o1DI5kPwOhjNwBk3hh2yIygVeaW/8cEid2AbLXgsTicEDQC2ASlub0lq8MBewuuUBpIHnsQ446Xg56tn3SIJXcwy75pYczn2l189z4t9j8v9Dou9N7KcD4r8eKW+J19DVrI8ulJDxirTj2Y6ZD8mgZH88T+DJfTP1df3JWZ2PgmosDbaC2DvZpsu5Fsv5Zse5Brt1fg3Pjcyj32g1PrafSONPEcxB4Ai2quelSXauy64rNfalGT2h55qVlfuFvz5AkHcVcgNjXs/Enu2u5w/VVv/UHt18UfhcK9GXixhLf/+RI6m4fHs+hoFuyMi8ZaLjprv37KfMJM+a9tasDg78gD5L4kZc8jl3nckpQRIH2cK1QOalKI5si/4akki9SyoPpnqJYJVDMgJiAdKCeJ1VNFOtnnOs+rwxdEvhzku4j8l0D5oajuWBq0IPVaoDxvnXAnBJgDjDtsx/0v0CHLhJcQ0Quk2DNjFSL/LEpWGpK1hduBOL/q5Vgj/eq3DxIFSJeZ5LjTSK75ThMDrLhIhpl73xx+lrlelnodfwn4/cX7sMhnJ4t9UuRx8cWHW+J9WeJzURrQ+9ptKutV95uIztfh7S9Del6FL6e/Osj9sJ7+qivWajvNYSXZejPNYTPF7rTIYSHFeuAfB+/gKHbbRSBH8OIExh5C68Yrtdh+tWfrSi8PVeM594PHVcJHrQKzy8uqLnfmiZMldL4Kcdy5Crgr6GQZHC6RxxxwsYznYs4X4dkCzgMOp4m1oauR9s78XFevd6puP/7jMfQvx7zw2avofeCxSLjMI9cFyJiELBwFSR/nCGiBjQrRS3JveCopYo3PSD0bqWUCtUwkIwAqJYnVUiVamVztF40h41xfDuXHoXw5KGgBBi6SPguYD+wVGhbudEIsQTI/jGSBP9mOQDcgVhHZC/+4X+yHsQWQLZgA3I5vgrD2aOtd4tPVSQ8kDgGnHmDKUTzpXftKhzvMhssugg76Up7dWV3wcLJD63vr7jTbzlSLgUy73mS7ujirnhes2SS3/S9Bg+98iOavoKGUqPtC1n2R1n0hq4uIyvydnA89cVZbabarH203Ux23MuzOiu2nPljMpDFd2Sz3Fq7PlPD5MYw9grbtNxovxtWfr9MSj9US1u8FT92N2paLWNKMHDf2z/uUXrQw2iM+5oCTBVwBPOOA8yUM/cUiOuOg4zlwOCNZHzseaiz/55WL50sNl5J7XgP/9p76j9fEfdeiSI4g6gB4ckjXpT8WIAtDJVo5fEX/Ovkozi0BD7ORRjZSz4ZqmZCWhsMh5WSxSopYM42r98+AZ8eW/zIKWEYBHOS/hGnwXaJ8lyifJeTdiAnA4oOlH5Jt8DYSBS34gWyTIY7TYwq0UDgSbUZ4QLEZkk2k9FVpSt03Fu93IBIHEetMcooxX2Y9mGkMlt3IWfZZnVNPimPDR7PdXk/Rpi+x50Xu+W510K9nw8dyowQdFec1BRMpUfNZL0FjCWwoQQ0lsL4ENpSS9SWS2i+XFTldcfYrac6cZLv1NNvtTJvTL1ZjH54uZDDcHCycK366jwriD0HsIbTvvtH5wFGNW6C9PlF/uXMvePp+9O69iFW5mJ0HkStq0QsPvb8zglNS0vNGuxt429PEwTy5N0f8muWtji60VVQnPS+KCSgP94vyCFXxaPiP9+R/fWb/8pn7y2tc2bc8el0U8YvwXsYEOM8hxiTFnEAWjRKtz1eKfrXyUZwHr7kqKeKHOehhLtLIBmrYD2MhoiWLVZJEGimXppnLzMop/2UYsIwCl6kAbAdUwDLlu4R8lpBP4/ydDgjaACbgT+FBlhg3Y8eLS0CyzIu8zQNk41lkMwRNJFF/vJfw0n6XEwiJCOmJu2TeQTzlWf/i0WEbA6y6CPuYvSmmY98Y0g0feOACTvFkOeS6r3VZiFYCF3+ESCea4WgrOdgA+uvI1u9E01fQWEo2fhW3fCW7qtFwK9FZvVbwrsjf6o29XEWYfusz/eVs2+EPtrMpdoEMC7ucKac+fuQuGbMP6INCvbRt1dhJ5den6om7fwdP3Y/+eS9iUz5690Hk2v3o3fuRG/KRq8oREyq+9VrM1z4O9q89Wc/Zdu/dGVXPwpbzM05L8k7yM9ODolQ9qv/rM/0f77m/fBb+9prQCPsRsyMO25N6rQDXReQ8i50wcxw+bRBrf+bJeVcrRC3Lv76kJYse5aGHeUgjB6hlkSrpJPYEKVLaJ4FaMte65Ld1Xrf/CgxakRGwjPyWkf+tKWAC5u604ywM+942CFqw5uBQB6fEFF632x9bACL+FOwgaCdFL3Nf9bd6EcJoIAwVrrGkM06zX6x6kwykS17knNNaufV8rQOx64EOnNCpi/SIiW7ckcCTt+W2N+TAm/dfaogRz7fBxW642IdmOuFoMxhpRGPtcLINjLcQk42n7XmcsvDlMqeTDlf+oMdFh+tsvuWPOL3JTw7hTlYWH7rsWy4DlqWxB4A9LtZO3dF4NqH89lTt1d7doKn70b/uR24/wHawdi9m927UlmLMyj3/nr88mmmOKTXvP55/Lzr/Xnhensf7mnNVmnf1JZdXmj+UmqKs6/4fq4y7rm33fab+6zOi86whdk8S9lPiswLdFpHTLHbC9DFo3iDSzb564PVDHhPAoyUJtYqQZh56mAvUP5Oq6aRqOlJNI5U+ClSTuDblvKcZLQGrMHiVClyBASt/CAjAXuF/BLQBnAHcloBaMA1/KqO32x+7X/zLnzNJzaS0dKI7J9uRz4uDRIxg11m6xOQNerS/MVyrtIErHmedjhPlTyVH/vDMg/zJku6zEM8d34Ei8QA33scL9NMZJn85YLnNf7Xl/eVUDbHUAThdxHzreX/JSuWrpbKwxVKXvUbm9RBLOEEnl5yJJbp0xkEyQr/sdK6N1wt0snn6st2h6dJ1WBC3T3otAu3MXw/jx2ivz1Rf/bwbPHUvZv9e1JZ89I582Oxdz55/0cv/Y5uuaPWC7RSan5A4nvL2CkOfe1mawy3J5pYXiBuqQFuDsKmi83PmI1Ofu4yv/7Yu/JfVZ6PXdfEH0rCfUu/VPwQwZQSY1GIC7nl8k4tcln9zpZwk0i6GjwqgZh5U/0yqZQAZAYD2UaiWzDMu4lqmNQevksGrVNAKCljB+oO1SOYVfBtnsQXI9r5McyAuxmGtl3224I2PA1Cci1GgBZDNJNF0spv4gX7Ne42IBOFvb+kqWzzp3PPRaCbHRjLvIxx1Gi4yufkZAMVBkgOPs2k7TIDAG99AI/VGEk/I9+btsH5P296suApWAnY6PVZr3ThVLks/nH+1utyM+VyPsHhjdsJFOrntBI5c0IUrunRHlx7g0F0yRT/vcGJbPzaIanRu59t38CJ3SZ8V0qj4VDVmgPb2VC1x9++gqbuxe3KBw/+2L7rLLJP3alWPGFcOamcEpP2uq7ipLePXfRPUfedXfRXVVUhbqmBnPexolHY0XrQ3Z3wo0vCvux+9/FfghFrUuFX2UMIxGbZLeK9AtyXEmkasCcQcRSbVoic5l3ddyx5EcBTfygj4Ah4XgccF6JYAlTSomgaVPwk10vi6OedmKS1ha9LgNRS8ioKwHVBBMgL8Ocivae5O6/93+/+pNuNYU+ZyZSWg20xYJlAdwstXmb7nZ68hiBWfB4o3PcTT9OVvDlPpDr/qHcg5j9Uq681RNhCHQZHfzrDD0QiDPHVDEm9K4oOkvpTEW0aDN7zxvv7pdLbkcDzt8Hvc7njC7mTc/mzK4XzOXrTtDE/dMejXrujGneJ7IgG+SwVeeoqX6aIhx9fhFroRzcw6rm3ztf+8OGCdsPhxSYsaUH97ovpy+++gyfs+PffdalQSNpRfHSv/c63yz/ndqBmDF32NRaXCrgayvR601+HVUU921gs7Gw87Wss+l7LCv2jGTspFL9+NmFdO+KWSsGpXtpBwAoK3JN5r2AKYk4gxhhgj0LhKpJ/N/cu55F7YotI7nnKSWOsLoV0CtAqwBahmAJV0pJqOaB9FD9OEWp/P9T60Ra2KQzao4FUUvEoFrCLsD1ZRwCryaZy5gxNd8KcE3SKzgP+142UbXxb54GxAJv1vS17u7ryGZLzkMki04yqZYxx3srtem6yX2JLTPlf9TiMVlmJuIAKRJNdzpPLp0YQrPmyNofelpH4U4Sd78KWkPpgSkS/i+6MrX8j1glxPeO6GLnE/BzuMGzd8bpLvRQk8ocATXHnyNx3AnrN4ivEmysLs7Zhx7ppl0xV7TBi4Qdg0C1TihtXfH6q/2Lzv1aAY0EF7+1spSaCcJFRIFdFSb+Seb2oknxm/Hnr1oaSr+Otue/Ov9tbF+vqKvLK49+VmkdXaLzkqL7flEjaUX+wpJ54ovD5RfrbEbtp6dgYCtyTeq8B1ATqOIcYYxRiGhhVCvc+n/2YV/h22hAlIFj3+ItUpI7WKcCQqy8WgahqifRJrpAkfZ19q/9MVzbkJ2kKhm1TQOoY+eJ0KXEOBa8i3aVZGwK3u44YwFp9G2QAWDjfxeKhMhTD64ozWoqmZF4B8Lr0OEe46E8vsmwG3lkT9g2/O5y1O0hHnmW/mF5u+kAwHYn/poW//d3PJaQiSBGDcCX+KCKDIQBkNARThj275wPcC+VESH0rsgwSeiO+B+PhcPHXjhq7dKYEXusF7n7/qSO4y0RlbNOuQGGlpkbKk+2HCpvHapv06cJ207xFqvJhQS9xVi53+m1mkkrirnCxUShXR0iS0DKlqFqmeylf557d2rsA4l2uUtKYb36sX06n9bFjv05bmhyOlN/tKb36pfzhVS76iJfNVkoVKH69oCXN+QyfPzkHAhsh7lXRdgPQRmQUMwydfBU/S9v/NLPw7jKP05lIpSfyoWPrkG6n95X+pAO4NQFqSRC1Z+DiLq5c8EjJ6FLqJwjapkHUUuEYFrVNYi9aQb+PMHVzyxCj/bwgF94Tx19uSA/4dx6lEyXRXR280IN8R/EjBrgu5wr4ZYrW/Mfr5hX1Sa0eOuF72OC+02JI3ARQMJW/857vteLuBUBSAcScCkNQPkYGIDEJkAEUEISKIIoPwrUxEIF5Sf9l5edzPoQReFN+TuvG8PR1PnLmeL9KvFuzRGZs6Z4sW6M8ibczS1rUSR1g9IoPKE89FKXNcrPXPolLckkJAl2r4sOKbU5UMsXKGhJYlUS0UP/pG6FYShhUSrbxzrZzTR1lHmhmHD1N+qSftqX7cV/t0rJlx9TCT//Cz5GEOqZ5D0DKFj/JEqgnjkQs3CWfAf03ktQJc5pD9IEUfRYwhqF3M1/m09R9W6f3wFYU3FwpJosfFhF4loV0CH+ZAjSyokor7M6rJUpVPQs1Mrnn+mkcrJ2SHwgdj1zH6AavYDv5YAI5/ZFH/bfQp6/3CJvinCdwCYAsga36tltVGEsQ/pCj+ZtcFrLnc9DPb3hptF7MvaxzJEVdxP3Om3EK474eIQEQE/Vx025nzBPwAhPGVcfAH/SAKhFAgGIFgCoQgMkhGgP8fAvC1EHghvhe69kQ8N3jqfslhb7SYgn0XxGWhE2fBjGNUON0gbUvz5ahLp9C87tKh/8ZtBuhkbCmFDcl51tLiVpXfXyon81SyhDqVUrMOZN6LnvYjsz5k3o3MO5BlO7JsRVbN6GkjNG1ARvXQsB7pV0PdSqBdSehVkQa10Lia1HozFrsuTDghfZeFXhzAnkX2AxRjlHLsR48LhQ9fL/zt/O1BxJpi4plikvhxMTCsInRLgWYu1MiEuD2QClVTCOUPfI00rnX5b0bZUPgWCttCIZtU0AYKxBJEBa4j36aZO62AxD7gdu7zD+632/92NBq08y8yy+Mk4jRAvLr+5QG23fgDjLpEvbnPDF41U9TrRI4yD5sd13vp8NoPkQHETdB4ux1xHYzwTg+k8GYP/gM6DKNAOAXCZAs/IDJYRkMAJQ2gJP6U2BfhWzu80JUbPGcL1lnL9RYSDhuds9EFCx6xBFP0oBC2fsYvrfcLjnXHVs08qza+ywyhX3SqFtwu71mjGLut8olLe/tLu0xg2YcsR5DlGLIeRRYjyHIYWeAbRqmnXZRFNzLthma9EBMzgCwGkeUgZTFAmXYho3ZkXE/ofxp+viOOOyD8VoQeHOA0hez6EX0U2ffCx/lC1WcTf7Mr5SJXFV4eK34SPyoCRnVAt5x8lA/VM6BqKvYBOBX450Y9lWdZfu6Q3xm+BcI2bgnAniBo/ZaAaSxBLZgA1IyXbCIRkY2QxKO4JOwgxanVH6+u0iH58fKXL7nnLhhltKRYtr+zvqphX7c4oCm2oIc1VW5OHPoiiQ8U+m5NMk63AoA4CJF4j8sgDsGIQxn0MIKCEZgGMowCoX8WEYgl6PbOFOwMvNC5CzxwOui33W22gXtOiOuMzphwnymccvYM9H6ScWiU9fNp4YpN67Vp7ZXzBGFUfqkRUKng3agQ/0sz6Vw+fvppm8RmElpPIdspZDeFrGQ0WA0hq0FkNYAshpHNFGU7TdnOIJsZ/GA1SVmOUSadyKQVmdaIzDNGXvwUxe1JAlcl7kuQOY5se/FZSZtOUjNPpBTZe9e1Ti5iRf7FkdIn8aNCYFxH6lWQjwuBRiZUTcOpgEoaqfyer57GNy4+s89oidwkIjap0C0sQcEbMjewgXwbp++0AYAJkCXAeAYd4tk37AAgbAVEyUwHZ/09JD9xD4Kl+x6iWafaT8aN6W7HP9xuGulgzJkYY2xV2RyNusBLTyT2IHk+c10OxHUIIsMRiJTt9wiKjKCADHQQRpHhsiVDnwyh8ArGVoI5kFmA0AtducNDFth2nvtmIF1wRafOFNeJOmOQO3TBlCvdL1g369S44Ew/ZYLeL9b/fu7QJzGv5ct5FquGDzyIP1B/uan0bNRhDDjMI4cFvBwXkMMccpil7KYxGXZTyGEWOcwj+3nKbp6ym0X2s5TNLGU9QZl1YQJMqgWWOcOvDiRRO6KADeCxBBkjlIwAaNVKPMoTPQhsvuve8iB8RS5hn5Yk0SwkjWtJo2pSq5jU+AzUMiHuz6STyv/w1VP5eoWXFkkt8ZvS8C0Yto2NIHgDhcqWf+MUJgAbgYwAPHUrO5rRBEAzAO3XJ9Wd8STx6fo8XrDvIV52bvhs+SPLk1PKvql3kg46wWknfjdz9ps5uY/jRSR0Fx95c/qY6CYQCQOQKJCShCBpGEWEYw7IcBkNsgf8HCYzkRCKCKGkgXjJCKD4XujcFe7SJUtOK5WmYJONuC7owgmdMsl1R96Yu7lfgs7nC6Minta7UWav0KqJb1p3ZV4nVAqufxgzrvD8WC6g16x403EG0BcRg4MYHEq2EJOD6EuQvgSZ+Ef8u+MS5biIHBcph3mZKUwgsw5k3IDMvvNsC0de/5ZEbgkDNqDHInLoR3a9iDkGzerFj3NFd71+3PXsfBC+LPfsl2qS5FEBaVhDmNaTOiV/CFDNgKrpgPZBoPrx6knB9ZO3LS/WheFbMEIWjIZtUrKFMAEyB4BPIzVj9GEDDkMx+m2kpLA7VyD4IBa+5O15STZdOkqdUt+5LVb5Xze4C7tZcJpNjDIWSs3PZ9wR1xMJ3BHfXbjjsd5uCX66wGMP6twbXfmhG38kCESiYCQJpqSh+Io+IowiQzHueOMH/28FURJ/PFdx7QEPnNAm/XKYsddsAfedqUtXdO6ETlgkx2Gvi20clqmTyzMsuVF9NujaJ7LrEJtW8UxrhRrPBtXDh5SeH98PbGEPCRkLiMVBzGWKtUI5rVDMFcRaRaxV/JW1jFgrlIwDxFhCjEXkuEDZziKbCfS0A+qXi02KfjuUTSQeE+FbooBN6LGA7HqhXR9kjiHjGrFWrvA/zl/uevfdD1u+H7enniJ6XEgY1hDmTaRuKamZg2ui2AjSgUqKWOHtpVaeQPfjUPzMafg2ithC4X/Qp8I3UUDT5B8LwOovOxPZiMv9uNrc9HttYv45JBO4+37kvs9wJf3VS5/FtmeHFc6CViaYZsNp5mkbY7bShtz3kgXvLtS12w3HZavVCs47wlUm3GSAbRbYcwYHLuC3Bzz1gOdeiOuLrjEllDiQkgQiiT8l/Z8R4PuF3NC5K9ikwxXW6g9T/rgzOnahuC7olIV+s4gFu/EKR/NXjboF14ZfBWrPJlnNh/Y9fJMK3tNakda7eeXgXtqzLcP0WccJgrmIGEvQaRlfzSrjADmtUk6rMvSXZZQsU8xlbBZMbATIbg5ZTSCzTqSVf2OQuubRyHl2KAnbEvpvAPc5ZNuFCaCPQP3vYp3sq/9j//me7+C9sOW7sT/VkwWPC6TGdeTTZqleOXiUi1tjapmIlg5U0wj515ean2/M89a9G+cidmDkNgrfRJHbKGKTithEgU2Td9oBaAewDVsA3vv4ZAAJW0lpRV8mIXkhvg4mTrx3+10/JEU2V75aKWXyW5yIMRc0wxIOOg3lGotW/BAPX3ZF3bDRBZs/x95oskJzdGqJgTgMtMxAKwy0JlsbTLjJgpvOcNsJ7bmgY3fE9UbXPojvA/nef0JPrie5zUAcB7DgtFhiRCyw0akbdeGKjp3QgbNk2q4izcouc87wK9+gTPj4/aZF3gxrWGr49cKqTqqXviPv23o/ZITRcuo4CxiYAOS8jJxWEEtmB7d7n7ks+3oLvUyLmEt/nITVODLtQI8yzx+9GAke+RW7Lw1Z5/utky7T0LoD2A1Ah2GgXSrQyzr/vyyS7voO3wvl3Iv7pfrx6lG+1KwBWrZJDSrJR/m3FoAbk6pppMLbK430a5vvXLu87thtgKHfpiK2UMQWJiCoeepOBwnaSewGbj0wPhpGgubTzcnpCCiJIC/8rpZcsjKCPpemb7WEn9c4iXvYcIYNxllrFXbrbUxw5EUJvKlrV3TFhkdOohk2p8oMzdHRAp1adEQcOlpjYgJWGWiVhVZYaJlFLeNPtMwCa0yw4wz3XeCRKzx0AXtOxJojXHBE8w6iMdZyqRnJcUJnrtS5K/XbGew5SaYdP76wsys+NPsh1C0V6iTt675tZwyJzKuvTCtvTHJ/y3s1KIZ3s4bFdnMYU+YiYi5RrCXktIzliMXB25/JoZjLWH+YHMRaovDvi4i+gOxmcKhq2orUPh0phzXEcy5j9iRBqzc+q4A1Aazbod0gdBiEmvl87Q+b//fT5Hv+Y3dDl+7FH9DeX2jmSswaoE0nYVwjfVwA1DNI9UxMgEoaqfDuSiPt2qz8yjy5MXabjNpBkTuYg0gZDcEtsigIO2FZl7ERo4+3f/lAnkSQAG4CRVtuTSXsL319zcWRP0sdBe3OYNIVTrJ4Pa59OabSbR8k9EN8D+rKBXGdwRZTMuU+UmQMZxjYCBbomIBV0zPo+wAAIABJREFUBtxkoR1ntMOGW05og4VWmYjDopaY1BITLTLhoiNadICLDmjeAc3ZUXMOaIbO66BvfntKrjmjM1ckI4DcZommWBEx3k9LLy1qhTrFYp3kE5XQ786DQod2iVHpuWkBV8GzWvvlwNNOse04cphGjjPIcQ4v+zkcBdHncEREn0f0BQqvRcpxAUNPn0e208hygjIfRLrVUP3Dkbx3UcKWIOanOGid77UCGKPQqh3ZDQLbXqCZy1eJHvyPfc69wMl7IUv344+U3p5p5oifYgJI80apVjEhkyCAq3JpQOmDQDWZZ1hyrfu6MXFdErONorAnoCK38ENw8+SddpJol23/elkVqAnCLjG3azgWiCPAWcB6h0tuQ1lyUfreD7/rBmfpoCucZouHWX2fTS/mfeBVAJL4oWs3iueCjp3IRYfDdvpmsx1YZYAVOlxyRBhcOuI4wlU6WmeiTSdqy5naZKN1Z2wNS3S0yKQWGdQ8Hc3TqXk6NeeIZhzIIbvtcovTFga54YzOXdC5C3XkRGwwryc8GWGxpmUCyyaxdpFEK/nsb9d8l74beq/UuPTEqOhK2ada//2wQa1EtxYYNZMmHaR5L7AYABaDwGoYWI5AixFoPQqtxqD1KLIehdZj0GoUWo1AyyFk2guN25BGEUF7c6Aa+CV+Wxy+xQ/dknguQYcBaNUmu7OpXfo4ly/nX3eP9fVe0PTd0OX78UdyiaeaOWKzBtKqg7RqJ3RKCbUsUgMPSeCKkGqyROkfrsEXgc777rip4+gdFL1LRWELuCVg6k4nIDsgbAI4/sFJACArON1Hx8/BdSB33qOsKKR682dlivfhN0dhhzOacpeOsRe/2y+3M4gzP0SG4urNlRu6cIHbDmCGMVZkIpnHYoIuPNCJK/zlDHed4BYLbjDx3l9nwhU6WGJADpNaYaEVJ7QgQ3+Oji1mjo6m6WDEXtprP52pL+h3BTsu6JwNT9noyIlYoa+2upvF5hl/F1q3inUKycefTu67lTCq9uy7xSalpwZFN8q+1bovBw2+izRL4eOv4FE5ePwdaFWQj38ArR/gcSV49APersc/4J9VCR9XwscV8NF3qFEClDPFii/3niTUR+9IwtZvwnaA2zyw7YVWndgBmNSLtHKu5bzK7zlX3A2Yuhuy8iD++P7zk8c5EvM60rKVtO0k9L5JNbLBw89A/bY/nEoqvr3Uyb9+mr/uXjkevQOjdzD0UdsoBluAjIB28L9jwCRoAeLy3mQgfkb8DlhsYhf0Nib/KNn57ndV5yYZcCMnXX42swe/2kh++yFpKC423HiiK1d0xIKrjpJJp+lyU7BMp07dKJ4HrudceaIrL+rSkzr3QKeu8MgF/WKDLWewQgcLjmCWDqfpaJKBJuloHC9i2IEcpIs66SOpJuJBNtrH1KIzNjpyJpcYlZn2tpnjRpUi63aJbhHU+nR037PaKn/OtltkXHqqX8yn+dfTwjpMKm8efYHqBaR6EVAtItW/AI0SqP4FqhUDjS9IvRhpFEN1/ADVi5FaMf5FrQiqFpC0XFI5VST/bNskqS9qhwhZ54duQ9dZaNUFrbuRwxDQq+DrZPPuuZbdda645z9xL2xVLv73vRdnmllCk1rCsp206ZIYVhGauVA9C6jjCQmsQgrvrjSzuHY/uJZpjTG7ROQOit6hondQ5BYKaZ260wHINgCaAJ61aoSwS8pr6Ysi+aHns561pUEtZxdFGZGHX+nCVlcw4XHW696Z/1R8FASlYYgIwXWbG0904Qq2sNqcdjr+6rCFWwx06YGu3KkrD+rGB/G9Kb43deMlI8MD/9OFBzp1QweucMuJXHCUjtsTQ/Zg0JEccASDDKLPgdfqVPdMVzzkhH674yzs3AUdOIsXnOITnOzKflvUC61aCN1C+PD1upx/h8GbXod2sfGXU4NivrJ/o3xgl1nh3sMCQj2fVMsnVW5XAaGST6gWkGoFhFoBqV4AVPNJ1QKomg/VCpBqAVLJg0rZUuUsQumTQD5u/WnWWOQ2EbR6E7gOnaegRQdp2wftB4FWyY1e2v7fnjX/da7822/8ftiaXNyh3EuueuqN8Q+JVRtp1y01qZE+KoBqmTgjw+MRaUD5H+HDNJ5ZGdfwTc3zTemt+ERvYyEKbsFhKJ5GacaTbnjgp+bn9PJKBHEestHh8rW9uGx5oT/b4+K7s7DT9WrIq6voqeggCInDgSQIif1w+M9zg78YcM0BTNlzvptKF9jwwAXxPNG1B3Xlga690I03deND3XhhDq490Y0X5kPgTfF9qGtvTMaRG1hhSUcdpH32cIgp6XSYz7NpeK4tGWOgU3fEZaNzZ7jnzJ/zcYqOtai8tm4Rm9aTukVQJXZGNWpGI7qF0S0xKDkz/MJXDmikRU4/Suh7mC9UyyNVc0mVXFIlD+DPXFI1j1TJJWg50tuvNPwjUMmBtGygnE0qZxGKaRL5dzzFWI5N8Wz4Dhm4eu27Cpjj0KId2A+Sdv3kwwK++vNxxdCR/7Kr/vYbvR++Jhe3/yCRp5Jypf9dYtVF2PfhbOBxEaGaSapnYz9MSwW0ZJHKJ65BCVf3Tcvz5auoXRSzQ0VtUdgJt/4vCpKhj+8mKOovEl8l8tZ9+0qZVdsr6TXFu2Vegno3QZ9PV6HF9X4QEIZCSQgU+SGRD3XlAY+c4IY9WnQkRhizX82IJRY686SuvNG1N8b32ov6/xEg+wULF98L3Zb+r9zRoQuYZxD9DqCPwW9jlYc+nsq2kE4x0Jk7umCjMza5zZxodLf7UGNZJ7XrFBtWk3oFUqWwflrUkpxvjWuXRL/4zLCYr+grG5byrnmSc6GeC1RzMfS3uKvmETL0CVoOoZwtVc4mlHNI2TOGHqOfScgnCeVfc1WeLdC/L0bskn5LPE8OScf3JQLHYdKyQ/ooX6AY0kqLW/mLXfW379CDiE25mF/yiVdK//D0v0psOoBdF2HXQWp9kahlAXVZUQgTkCKVf3OuV8CzyJv3b5yP2sEE4HBoCwW1yGpBLbK9Xw9gCykt604ir+O2+lhNFWGNNzf5xa/3y1jXjV69eVYX2yGkrMoGRX6U2A/3Dk9dsPhwHIkRm5NmxmyZBVhjIa43de0j2/U+GH2+D97yfC+MODYCGSV8L9xuxF/dIc8NHbmSc47SXjvQS98pd6wI0dmpdCAWGOjCHV24UKdsYpP9zxtHZsWvpw0ix37SoBLo594oBHUpxWzc96ljt17pF58bF13Le9c+iFx74NOq82FNPZtQyyWxHWBnQD4sQRpfoHoRwLqE7YCg5ZCYhtu9nyGRTxPLJ4vk3nJVn82wG9YjdkmfRZ7LPGE3AKy7gMMIMG0S6Rfw73rXKsZt/de15m/fwQeRm/ejdxRe8eTe8vRKJNadpE0PYdst1f0mVc/BbuB2TouWKlV4x9PMvGDVX9hlN8Vsg+gtGIsJgEGt03eaAYmDH9nh9zYgqe16TpxFrjQ61ranNwqEZZmBJ9/Yg9mWh8uBUl4grm4SoUjkj8WH6wp26GiVDqccJf2snmSDy35XsOqELjzRlYwAvi9GH6vNn4UVSWYZMrOQKdKlG7pwhbsscpoh7aFLuhhtL/T6X5px25ykyyx04QbPndER+3rZzy0+1rL60qpdbNtN6pcD3aRtxaBexZhteb9mRvVvg8Jzk4JLOfeqe2HLCsFjmi+nHuaJtb4CnQqo/QPqVEHtavD4Bw51NMuA+hdStRCo5AFaLknLAcqfScUMQj5NKp8iefCGqxY34d35M3KH8Jq7cp4hbXqgbS+gD5OGVWL9z8f/9WyUTzi4515316dPLnLzXtSOfOKl/Dv+41y+RQtp1wNtu6TGVVLNPKCWScoIgLQUoPT+5mEq1/L7xdPk+oRNnA3E7VAxtxaAJ91uMwCE2oGwqTtC9Ct8vZ71rf9bzcVZ+Sub6Xy7rQk/3GonwyARjKQh+CzYlQfcd4JrDDDnSAw4TuZa7LWzwZqbeJmO3zJy7I0usQqhG28s+rfqjyMiT+pGpktXXtSVJ3YSXDd06grW6cS4A9HreN3u8iNYa+i1gWjQjdh0wjMpZ2x0wO6rYTtnNVs3S217JBZtwKAM0KIGNOI4SjG7SkHtNl+39QrOTHJO77nX3I9YVwydevRq7Ml3YNCADFvwu1+MOvFbp4w6oUE7NGxFek1Qtw5pVULNMqhWiIMfpc+EYjohnyKVe3uhEjsWMHwcsiH2WeKzJkmbbmQ/AO0HCd2vfM1X8/IhIwovT+/7tNz16X4QuX4velfu5YXCO6FG1o1prdS2D9r1EWaNhGY+npJTyyJp6aQy7g+LaB/OjUt5ZqntzxcvYnZR7A4OQ4NaJu/gOqjsXo5GArYTgtbu0N/TbgdtvqUTLV/WV0peW6/0e4gvAhAZicgQCoQDgT9WkmNXuM5A847kCJ3z1W6+gkGuuaETd3TuRuw5i9cY0i0W/O2GLjwonifF80Rcd+rSHcPN86BuF9ed4rrhRPeADTgM6bAD0eOwkG/bk2A29t5IOupBbrugc9wXI/Y8Et+yXRp+W7RKHQelJvXAuPiGFtSpHLepGLOjEtJpUbSum3+mn7R1z6vlQfiGUuiU5qtRozqIuys9yKQXmfRB4wFo0o+MepFxD2XYiQza0JNm9KSJ0qmjHlUgtWLsihVSpfJvLpRjhkOnLgM41wHLEsYYadsLHYegTY9Uu+BKKbRDOW5d/jX3vn/nXa/2e2HL96P35BJOFd4JVVNvDL6J7PuwuVh1gEdFhFomqZ4FaOl4WlQlVSr/+ky/4IpZtelVNRK3DeN2MAeBzRN3mgDOwhoBaICogxC0dYb+GnQ+7worm2rPG6jva3CSXIYiEEOBUESGIUkYlG1buM2Ci0wwRt+tZ46X2BHb3ujCG126UTw24rmiS0/ytws8ciEPnMgDZ/TblTp2Qyeu6NQFnbnBUxd47AJ/s+GBE9xlwg0GmHWQDtgTPfTm5zor6fTJJBPpmAv86YYJOHM9XPDz/CfJtvHGplvqMACMqoBO0qZy8ADt2S+l2J+0oFaLL5vauWeaCePKocNyEZuKIeOPXo8b1QHTTmTai4z7KZMBZDyATPsp0z7Mh3E3Muqk9NspvTbqSTOlXUc9LIdKuaRiGiH35lw5ZjB87tpnkee/QtJHoX0/ZAyDp80ig/xLOZ86pYQDxbc3iqGDd90a/w5alIv9JRd/rPBWoPSJr/9VatdJ2vQB6y6oU0aof8Z+mJYh4yBVqvSWp5l+5tx8ZZPVkLAtjdtGsdt/CLjNAFADQG2EsLEt/Fcf86orrPD7u8YmDxEvHME4hKJw1E+Gw2uMMjxwxpXOKSav36W/4CnY80U8HySUtRLxQIMHHiq5csMJ2qU7uHAjjpyIfRa5xwR7THKXDnYcwRaD3KCTqw6Q4wDm7MkxB2mfnaSbWROpvZVhP5fxlBh1QXue8MyNPPYozLEPaeTYtwOHAalVF2FUJlIM6dB4tkJ7cawcs6vgW2NTeaidc6IS2q4avfQgfE0hcEDr/bRhDTDrQKY9yLSHMpNxYDKATPqhcT+SvQePMuxChh2UXgvSbUSPq5FqEVTKIOXfXijHDEQvCbzneL4cwnEI2Pfjv2taLzBI3/uvV5NS4oXCO75K7Mxf7Kr/BszKxe49ePZb/hVP+ZNQK19k2UrY9BJWXdDgB/Ewh9TIwmdmaBlAOU1K+yh4mHRuVnZhltL0ev3mDwFNk3caSbIZwHoS1pOohZTWtL04HHXfa/Cv+Gwm4EYhGINQJMKtxDAoDMB7/LcLXHVEswwwTe/JMObORvM3g252gwX7AaIjH+mpF7jyQ4IA3BET+mJWcPAjm7W6cqN4bojrgv3qKRsdO6NDJ/TTiVi0J0btJX22om5WS5zuTqbNUrYVMcJGe57ozJ2/HxLzIcKp6cK6U8ocI80bpIbZR4p+jbRnW7Tnx7TYzQc+3xjN19qZe7SABuWYjQdhnPu+bXpJS4ZV0KyVMuuizHooU6xClGk/ZSIbjzDtQYZdlHE3hQloQzqNSKsGq5ByJin/7kIppi96ReQzx/NYIO0HoUM/6TBAGn4XaCSMKoSNKb6+VnjH10xc/xfr21/+03Ixe3K4HHRBSxI+zBaY1Ers+oBNNzRrhA/zsQqpZZIqOBsg1VKlim9OjUuuGRUrgQ0zz3Zh7C4KaJq400CCehLWkQDflUWCkvaso4WYmjQj/kkUgs8oFEnBcCgNwe804uEMFqw5wnkGnGVcjfokhtk5eobqs2MN2DF2nqFhcSGpST4N5S4bo95X2wGiA2/iwg8KAqBI9hIGkTcl9sZvyRN7USJZC/PCGR5jDiRTduSgo6jLuSNeZyfDmpNvQ4w4oz13cOLeWs2Mqep06JTa90scB0mjMr5yWJdm9IRSwoHSsyPFqDnViDrHVrFe6oaCV61SzM79kMW/XaqM0rcMKqFJEzJuwa+/M+tEZr3IrI8y7aNMeykTmQQZdyGDDixEui1IqxapfUHKWUDhHZcW0xu9JvKeuXSbJe0HoOMwadcj1S24UA7rUIxeVngjVHgvePzu57+ZpX/5TcrF/HwQf/jg+ZnKJ6FqKt/oh9i2l7TuAhYt6FGRVDUDc6CSTqikkaqphNLbS63PF+zWG4ukypc7kvhdFCgjAG//WgI2EKBOCivnBiqKHG6OYxBIQDASwVDcuSVCIW7VupHrDDjHkMy6j9d4+YREavs3yAcMyYfOygXPyPsN3XVrv8uqkHfMUbZ8Zkr3THgZ8D3fbXPcV/DTjzj1gHxfnDyLff83AOGDLYPnhk5cwAqDGLIXdTv1Jer9zLReKbAhRlzgtof4ICjxk2dA15FtF8EYJSybpU8yDuS9ajWerau8PFaI278f3GOWOmFRfa35fFgpeEA5Zvd+8Ox/WCVGWb+1iyTG1ci4kTJspIwaoXELMumAZt3IrJsy6aaMuyjjTmTUTum3ylxxLaVaSKpkQsX3XKXo7ug1idfUpes0sB8EzFHCrFFkkHsi59uoEP9T/q2IlizSfLd/16nsL59h+ZhdTEDCsdIHvtLHa4MyiXUnYdNFWraBJ+Wk2mcp9sNpUnx6MlWqkiRUTz43+35t9KH+7eJF3A4Mapq8U0eAOhLWkrCeAA2E9Mdk9eFutOztR9G42EmGQGkQEgdArjuxziDmnX+PBb+McdB1fq7sU3PPv+Oef4984KBC2KJc1Pa9yE35iDX5kCWlwMG/XWrlvTrl6fnqtgnu/v7FGR6bI/6CHR/i2BN3wUQyn3GDKxno3AVsOROjDpJe9sh7491Mm/ViO2LUCWx6THZ5PiuvYHYI7QekjsOkQemNasyISmivUvxPlVfHCvHbj+IHTHKXdfP31EKbNJ6tK0Wvy4dM3mUVGmYfaxUJHxVJdL9B/R/IoAYa1AH9etKwgTRqBKatyKQdGbdTRq2UfgulV4c0y6FqAVDOgorvL2nRPbHrUvdJLnsK4Ft9BgmTKsmT5PV7fp0Kz0/k3wsf5hHq74/uuXz9t2ffvYgNLEEJpwpvrpU/iR4XCixapDY9wKoLGlYDjRxCPYNUTZOqppG0VEI9jVR8e2b49ZpZvetS0P5sB+B+QC0BqwlYQ8BaKewSXTV1RwFxJAKRUBoOJcFQGowkQZDrRWy4CBZ8q4o8bfxiDBJHn2ScGXyRGJSRusVi9Q/78mET8mFTCvH78gknCs9/yYeNPXp7qBC/pRS5pBDBkfcbVHare0h/5R3o/z3HfW/MX7TpSRy54FyB54ENa9uZGGNK+9jTKWY/s2zXimzJYVcpJyA52S164JdDD8kchRYtEq3kwwceVZrP15SfHyg9P5CLmtVJ7GU08XU+zij6N9Ke7cmFLSoH9moE1xkW80zrpGZNpHEdMKwh9arIJz9I/Sqo9wPoVwCdMkLrq1SrjHj0Var2RaJWKFHJI5SyCaUsQunjtXJUV9ya1HXswnkC0IegXa/E6JtILaZPPnxGPvFSKU2sVQIffjp/4Pr9L6+ev4M58vEHcs9P5F5dKn8QPcwWmtZIbLuBdQc0b4SPCgnVdKl6OtYflRRCJZ1Uen+tk33h0HCj/6zo3YYkSEYAqCZAtRTWkKDu98bsfAiUBkJphJTvj3VfEoAufSUbbiu9AYHRIRZvevRyLp+UQv0KpFeN9GuRfg3Sq4BauTdyYVMK/mNycXtyYXNqcZz7kRzFqHn1hHVa7KJK9IxyxJRqxLhq2OijoBYdtw8R8cFtX10vpr1Ea07kTxdynU2Os4gBp6Vsy5+f7VZL7Ihh9k6vb3xRnlPnNX0I0AeAXvGVZsKsckC7cvyWSuKRQsK2SnS/ed6SdcWpgv8PzYRFWvzeg+CJB+wyi88/TWqhaR0yrgEmDcCsBZi3QbMWaNoMjRpJk0Zo2AAN65BeHXxSC3VqgU4N0KqCmt+gWgFQSrqmRXXErUrcxi5YY4A+RNp2E0/yz9Qje+5FrMi/vnpURGiVQY3Uqwfedf/1avsrYEYu7pdcwvGDlxf4lpoUvkmNxKqTtOqAFi1QuxSoZRHqmYRyipiWStDSCNUUscrHU+PvNxafh4IbF0KbRu/USmENgWolsE6Kag435hejrw98rw78gCAQK8+5z82Gf3GOp01sjkn2kX6x1OA7MqxFRg3IoAHp10P9JmjYDPUqiUfJZ8pB/fI+9f91KlTya6KF92vEj6q9nFV/s6jxblntzZL66zm1F9NyIX1ywQPKIQPaYU3W/s8L0rx+D3hJRphgmE0OOS3lWf7Mtl8rspUOuRYkO8f17zj2EawxYF7D1007vudWrfmco/R8XynxSDlm9vHLLuc2gfbrAVpgi9qLPcXoddWgQRXPsqflQpMa0qiGMKmDxnXApB6YNELzZmjWgkxbkHELNG6Fxq3IoAno1QGdalKrknz4lVTOkyimihTeXiiHN8evSF1HLlijwHGAsGqVGmXvyQd0PIjdU/iHr/sd6lQgzQwhLazvP87V//GZVIjZexB/JPfiXPH9jeJHgU6JwLKVsG6Hlm1IvwI+zCHVMghaigifGkvFB5gU33GfFF46t9zoRBc+axy+UyMG1WJQK4Z1Ith1c1la5jLRaLM+yIa8UOmhz9qwf1BcsMXHUcN8vlEZaVQDzZpkb9ZtBqZtyKSeeFIq0Eo/UXm5pBI/9vj1hEHqgmXZuVW11LQGmNYD40b0/y79GlKvUqpXJtLJ4z58t6YQOX0/bFIzctDG71Vznrd4yIXoZy4X2vzMpW98sT/t9YrL/ODcdeU0Cuw7Jbr5XNWoMfWwPlrCLu3lAe35ump0m3MjzyxnScH7m+abbdUX2+oRIzT7d8bu8SZROU8Tvz191/I0fdwif8Os+LdZ+bXpd6FxhVS/QmJQSRhUAb1qUr8G6lSARyWkRgFBy5YqpIoUP/LlX58qhzTGL4tcR7jMUUDvJy0bCI2XYwph4/LPT9UyJXq16EkV0vgs0Xy5+C9m2b+8R+Witx7EH8i9OJN/w1P6JHxUIDCtl1h3Qss2aFqLtIugarpUJVWCz02mEKqpBO2TQD2da1J1Y/hPW2BmxZ0GEayVwGoJaJGIq6Zqjw8TiNNgTrvt/oB7TaGPdXiSadaRfqnUuBpaNOOhAfNmaN6G787Uzrt++HGf9nxa++OCeelv22aJWTMwaYFmbci0DZq1I9MOaNKGSzEGrZR+M9JrQXpN2G50a4BuueRR+rFKAud+xJx85Ky2T2FJcoBowH2p0H43l7FaYFmVQX/Wu0zvlTJHSL2SyydJv+Q8atSfrSg+/6X+alM9tsu18dyhZPGh1zv9oDTryI+BL8I/fXKvKqJPtjmvDbmPN7Dqs2zKkuyT4hxCgl3M3X0fe77Rivph9HFcP21VJ3PfoIBrUCTQyhdp5gk1ciWq2RK1zxLlZIFS4rFSYG3M3I3rMJc5DBx6xE8r+KqR3XIRK3KJF9olwLAJ6dchjWyJxpvt/zqV/dtr6G7oilzcnvzzE/lXl7SPIloa36RaYtFKmDcTFk3oyVeg8VmqnkYo4zhVqppGqKUSCu/On5TwXFoutd0T7/TzyY4bokNI1M72bu8kAmEw+dvrYj7k9Stfs2eNennX+t+AeRMwa0dmLdC8HZrUE9oFNw8//FRPnNHP3rJuFVt2QZNOaNoBzTuheRcw7wDm+CuONMzakHErMmyREdCMP580oSeNSKea0iqHj7O4is+W5WI4D0Jndb2zZ2piRlOtf+UxVoqYnzLiPLovnEeBWRVfL/v0vme9ZswULX5VO35Myy/T61NuXNbb7OKAgS6/g41I0XGI5NiXOHYHJ67wxA2dsNGRK7HpvttkufPDhtfpdNLqUvfBtiLF5VOiY0SsOyMgTNs1XsMr/VFUk9GnlSeZx4+yrjXSBEofecpvThX8qiPHz1yGLhyHSNtOsWH+gWrkwIOYXVrSjW4lNGpFhg3oURHxMOn4LvbDfX8HL8rH7z54diCXeK70ka+QxNcqFWH028DTFmhYibQLSdUUidL7a7UUsWqqVC1dqpIkeJR1+bT2Wjem8M5PAbF7QxxIiOofWTsToZJd351BH7+IANN/Zg1KxQY/wNMOaNYOTduQeSvULRVrZ1zQXszof962bhI//X96+g6vprZ9Xf+UN96795x7lCpFDCUBpKng3vsovTcVsaGAgh0LYqP3jkjvhN577y2EdNJD+lopK2uuN2bc546xRgZCGEN+36/P75sZB9fH8YAhPGAMBIyYboyAgFFIr7w2CK4OElcHwdUB4NWLX+kGXt3ElS7CsxO4twFyC4An4A3gcqXJ+duZXfqO5cNVp/TlBzE3l3JuCUpDOt4GZHUNBo7qQsYwjxKJ09Mx13sdrgllfyUmf/4WNzP1QCHKNMjvm5QJuCIOKOPNeqY4yGk8i8UlkUAcCQThOCMM2wkTj0au1/if9kf1ffaZLf9rtNhzotpvofnmcmfMeGN044+b75/9df9Jwq1H78kP6zzerNq/PLFLanswyojozIAdAAAgAElEQVSclgRPYf+mol4/9qwfzli9OHUu0Xt3mv2pG7jXY655ass73efj+v95b9U2g2mVwbZ+LbLLVl38ipJKUb8OfUCf6XqP6WoH8KjFSfmGSzmI7TvR5VyDU67xcp7RPvvMp05+48fYOY7aoNJj+9vr9Nlh+kh5a25EWFJKQCHL+xfm04b9MQiuDZiXKl0YpVxL+kS//Hbtz1ZtwAAeMAH8x8ymHzX9OQFujIPrQ/hv01+jwhHUuwd4dgD3TrhxdG+DXk9pI9zM1ndpAJdrcIdyE6kAtc04sHq6RcrYyHoUf1oWJKwJzXp4I2NWEDaBeZaLfbPXL/nHpL9NmJt9iChSjerbkISKxAIkDj7aOEKbAIV8qgRCEYeLY3FBgpF3W7AWvtzpP9N0Y7r5z5muW2sjQez1RBX7PiZ5iEvv4ZI7uCgeCBJMrFj9XpRsIZg9ErLSFl2SE/H4VaZ/fNrdzuWIaVnwJHazF3XKmLRL372YJaHUYX69BAzobsKrFScVaC9nrv53WOO/khZs0mnWmWyrlwIbeIkiYpeLXPll8O81BvSbrnUDr0ZAKcedvqMXMk6cv8qd8g1OBfpL3xG3AonPh4FzSiPGYhzvjXZie7Mn00NBD79fLxH4tZi8Ok3XB/Cr/eaM32r0KEOcMpev5Oz4txv8B8CNMQC9ftx0Ywz/9zQIGDE7/gBxtR/49sFE79mOe7QBj3aC0gYorYDcDMhNwK0RuNYD50o4Ijrl652+qqwyjy+k7Fg/OXTJXM17GiMoucWpiXmXmx1GVfzxS3r1zcDj13eOdtKMqoc4kgh0iYQ+Ab6itwkkkUAS4Kegq+KBPMEkvq2g3V7o8p9o+/dMf+DhSqxW9MSoeGxSJsEFIlSfJcDFFJoIkASoP9DAIw1wFmPmasRgzCj9drhmMeR0JGjqZ1xSZWvEhCx0CrvVpbV92G+Twbicq/ZoxPz6gF8/4dNN+HYQpGKd83v6/0TUn78zY5mya5PBsszkWr+V2H1RwyAoR692Gm7049e7gW8r8KgDpAK9Y7bM6tk2KR+9lGdwLsKccmTer4bOyVTyxb5Gw9Y4d2ow8lGuf5nYpxXz7jRd6zVd7YdV17fN6FGOOj2b88rZDGjDrvdB6/tP4DfGwR+T4OYUCBg2W58KrvYRPl3gSifw7ASeHYR7K6A04W6N5qcBuNaYSOVGh1zE/ova8bPC/i3P+vmh1ZNt2zTahSeH5NTR1tdR4tKb8z/C3/XOBbZK/dO+9Q6n61QpJvQeJK//Lee7C+V8aAJU0Zgd3yRO5K1Fjrf8sTgSoeCnYuoUXJ30t8W1iThyG2ox9ffgJ+bq70IFDhzC70IAVInwZEIWhwujTbwIwIs2nUTr14OFE4kP8wrDRqVhs9itFoVVcp/NSy653HClFfeFHSDh3UX4dgFylYn0RXQhpulC4qDlw42LmSyrTI7VK75dttL+m86hUO/VbPDvwa734Ve7gMdP2A45fddYpe9e/sC9nGcgFZpI+TrHh33nFoa70LXhs6XR2ynfrhWe+rSYvLtMXl2mq1Tg24v7dGCUCtTlzYb7u1n/JtS3Ew8YNrv/KPhzAvw1RfgPg+uDUNTg2wu8OnCvduDVTni0AnIjTm7AXatNzhVGxyK9ww/U/hti/wVx/KS4+Ipj8+zAOnXHKm3fJp1ukXJokX5EiS/cLkgQlQTWZ8Vk9B9Fvn5LZ7wxapPhKRD2HwGBIRno7sAHgR6Ny+O1J3dm2v84WL1n1Lww8wQSzdkpERpdlwz0cJQBaDL8J3KXQO4S2ruE5i5QmwkZSnhMBKRx8OhfGINzIwE7CtsJ08zFvv7yMpwqjJw3/tUgsX4waP9eTKkyebUDn76/49unG1AacOc8je2DkX+ENpy/t2Dz7Ng6k2OZybHOkjp809n9MDhX6Pza9QF94Fo34d0CKHWwd3LKFv0redYtVwWPa0pMpNTxczvtNcKRlg9Zede/0b3qMW/YIEPf9+7CfbtxSo3RM1/qcL/1Rv2Zb5vJf4Dwh4UXYnBjzBRgFvRcpcKdl3cn8GoDV1rg/8ylynS5zHgpX2f/DYXuAHkZasf3ItuXJ+a+c9smbcc6g26ZzriQsnf+0a7988PI2BROaYSoIrQq9/Xdj28Fgs8Y8sqEfQfYcwKeg5qVBIYHkM6O3sU1Cbg0XrAaOdERgiq/4PonkCGpv03oEoHujtnN7wLtHUJ7B4r0IS0DUjQI5W1CmQjkCUCeQJwlELJ4II0jJHGQpCSKAfxoyF7dCtXPR/7ISUoY5EfPG/+oFdo/Hrv8TUWpxb07gXcv8IW1DS74rrTgl/K1pLcH/wgst7gzbZWyY53BsshkWb4R2n/RXjQHwZUmw7Uu07Ue4NsJg8C9Gr/0TWX/fN/m6aJLPnK5ACc9nzq3VZ+33NZ448UQpRj1qMO8m/ArLVA16NsBPBtxcqnB9n7/9dyday34tV7o/v5jBDzlGDYFjOJ+fbhfL+HbDXw6gVcr8Kg3kcqMjvk6h+/oRXhfC+LwWWX/XnTx5YlF6qb1k3Xrpxu2GTTbTLpVGs0i/cTyydH5hxtWTw8d7/YmhCXOfr1zVHEv7WHIEb3IhDwEhs84VogbUs1SjkcQA30yTCCaBFwRz1+JXBqMwQw5wPCUMDwk4E/vwVSDmo2ugYQMXJ6An8WDs7/NTUjjCGkcPCWVJRLSBEIST0DTR+OCaIIfDbiROC0c7IRiy2GV2UEPBthRs7obtQKHp5Mu+Si5BvPqJHz6YBfk3QN8egjvDsK53ED6LPxnaPX5uB6L+8s2GQyL50yLF1zbD3LoeXmGy+U671b9tV78Wg/h3Uq41wOXUoPjJ5HV/SnSm93LhUan9MlzY62ND9OzvXOF7mUYpdrk+RNQGoDnL3DlF3CvNJG/iS8m1N+olfu14depkD7vP0oEjMLMc5UKBZ6URpNzudrzp5FciznmIfbftPZftQ6fFfYfRDaZdOv0XZu0bavUXbsXJzYvmDaZLMu04/NpNKu0Y8sntH/d37J8vOv4dMPO/3NScvPz+4XPE+81VD8wqJ9CTzc+Br+1G1DD9ADo7hFoEoHcxhWJyEn8bGeoSd8ADM+BMR0Y0qD2Rn8fllkzDQkoE/H/2BqeVMMD0ThCFAfE5leh+YhUGAuZd7x4wIrHj+Pw/VjjZox8OpTd9Vfd26sPfs2Fjmtu1IsvpU27FugpNSbvLgiATx/w7iV8YCUAbrXGy99VtveH/+tmhWXSnHXqgcUzhkUG0+qN2OEL6vDN6Fig82gw+LVjsBR3Eh6NBKUGv5ynsc2kX0wacM3hkNInzwnk6rB3reQClVupyb0KeNQCj3rg2Ui418KNh3Pmqlt6v+9PzK8TLlKuDUEd4bUhmPf9+qDXk6AuUEP6JriUI3L4rHD8ILTKpFk937dM3bV5TrN9xbJ+xbV9cWr5nH0h3Wz61GPLpzSLJ/vn729YPt69lLZ98a+ipMf9OV8OsnP2U55kS08/4+p4mEngZ80n/60dg66dRGhvA0WcSZC41HxdI3yJGz4CLAsYcwD2iTBmEOh9QpVAKGGGgWldHIuLYuB1H/x4QhgDRLFAkAD48YCfiHPvGk5uyzcjT6dvcYb+zez7g9bpf9Dke9jqzei5we+/edQaffdDbtCA4s9fMqdnM66FOnI17tNF+PYRvv2Edy/w7gde3cD9F+aQr3V6e/hff+WeTxi2fLBmlUa3eEa3yOTavpfbf9E55BpJpajXL8PVbvxqN7jSDNzrCedSg/0nqfWDebt7VK/0vnNChSbgZadbvta1CCNXAEoNBIBcAygVgPRVcSH619Wv++RK7EoT7teFXx8g4GqhD/j04L49BLkBd6kA9l/OLr/auvhwzDJt/ULKmm0G7eIrju0Lrt0LnlUG+0IazeLpkcXTI+tUmvXTA4uHO1YPdv71aN0iZc85dcvuZknCo95v3w9rq7jF+XvFuR/14mTIm0NuQ8U2eofQ/0c/jNwhVAm4MBrZi5tv8MO1yUCfDIxvAfYVGLIA+gTWWEU8IY8jZLCu4vwoIIgFp7dx7l3tcZxwNYi/cEs4H8oe/+tk6C/m0B/C6WDlSqR2JVq7GGrciMDWIrDVUGw1CJsPUg+Hfsh6GNYp+Heb2iltnFygo1Sa/LrNKajf7P79wKsH7oOdinWXc6T/DC4/H9pombRgnXZgkU63eM60eiV0yNHafzM65OkoNXqfZuP1LuDbQbj/BJQ63KkQsX/DI6UvWt96f45+KvF5PehSiLoV424VOKUGd68DLuW4S4HB6aP4f0LLrxULnEswci3wbMJhuu8F3l2EVztwbwKutcClHFz8JHZMXbSKabV7vm/znG7znGWVfmKZdmz19NgqlWaTfmzz5MDy4faF++sXkjcsH25bPtpySt12ujPsFFR8O6U/O2ensoz1s5bX3sTqbXmPieJgl/JbMg8797tAdw82jop4IIgCnCjm4J/c2XCguUPokoEO3oYAgwNNgvQvRRxxFkdIYoAwBnBj9Qexkvkw9tBfwqlgzWYUshWh247W70Rg+9Gm3QjTTphpMwiHDO1gfDXItByEL4WYFgIN04HoYHDT59DYus0/O1FSxig5X+laafDrgZ7nRyV8+s2VoBdc6QAulYZL39UOj8b/7/WcCwmjFx6sQrpcGu1CJsv2/Zl9js4h13CpQOv90+jbarzahXs1A0o9cC4zOH5TWj3doCTXnVvdO/Z7P++Sh7oVmyiVuHsNDALnEtzpm9bxLdcissKrSOZShLtVArca4PmLIP8E5Abg/hOQ64BLNXApxa3fcp1fbFrE9Fo82rRK2bNKObBOObR+cmD1ZN/iwfb55HXLpDXr5GX7B0sO9xYckiZI8R3RGUPJz3pff5grKaF1NIupXbKB7jNqD3O4M9UkioOkUm0S0N79zaqDlN6zOEIQSbDCcHrkZqOX7igeqBMgSEgSDBTEzL+Db4slzmIJEeRyGfeiOf23pCPB+sVIfDsKiqJ2Q8FuGNgOBVuhYDMUrIfhK8GmxRB8KRRbDMHmQ01zIYbpQMNUkHYwmN4SFfrsx58tiMvrOcpXAbnceK0T9+0Dvv2E798AEF4dwL0et/+uJmUx/o/v+/8O/GWROGfz5MAilWaRdmL1ku+Yo3XI0V36oXc1JyLfVsyvHXaJ7nXgciF66YPgfHDBuaG5Vf/vu875OucCI6UCp1ThlCpAKsIu5Wjs37CsY2s8ilUuMDgIl3LcvRbCQKmD1nerxZ2rcFIJZpt1ej6x3zZx4ML9Devk9YsP1+wfLNokjlnF9FtFdNhHd7nEdfsk9aV82cqpPPmQv97cfdrScjw9JpoZV8yPa+ZHkfkx9dyEcnzweHYwBRZMWSx+FksoEqHdZbGQQsEPJ7jhBDPMuBu63eyHncTiZ3Gwo0eSCO0dArkDmaYKM89OGo3zI03HUchi7MQ3snEuHl8MAevh+GoovhICVsLAUhi+GIbPh8JnLgSfDwPz4aaZEONUsH4ySDcWqBsJ1FCDzjqDXz2J/6uS7VXAtH+27FZh9G3DfKnAmwpP+f0GYDW+0gU8m8DlAvTSJ9F//fHj/1379q+YgfPJSxZPDi1TaRbPTuyyxI5fEPuvqEOu2r1G59lohENuMzSdWwXm9EPtkNx1rmlw2j+f5Zyrdy7A3CtwtwoTpRKQCo2OOSr7Vwzr+Fr3Yq1rMe5WTriUA3I1cKnCXatxMwCAVI6TijDbdzzbhxNWIdUWQXWXgiuuxjfnVB9kfFm9/2rh2aed9/n0H2Un9S3C0THlzKRqdVG9viBnHurEPAP7GN1dVa7PqkY6BRMDwq5fwyvDyeA0FpZQSQzx+xFFE8Io4jSC4EYSzAjDZuh++zWcFoGLYgnVXUJ7j4AM1ER40dBZDFQSiKNwbqRhO4re9Odaub9uMso0HQLmwsBsGJgJwyZDTJPBpskQbCIYmwjBJkKME8HG8VDDaDA6fAsduoUMBiLUQE1/kLIrsPpVYEBmc0Ct2u7hiEcR4v3TeK0PnuT4UYFXH4wGzw6Ylp2LdHYfpBfCa857pf33H6XnY8cs7m+Yy96hdSbT8bP84me1wzetU76aXIV6NBi9mk2Unyb3Gty5CCGlDp+r7Bq7XixwzjM452NuZSZyhYlcDpwLTPbZaofXDNuEekox4lJsci0DrhWEWzXhXAWcq4FbLeFSBS6X4c4QAK7jk1lKYG5C2ujH78dfcpnl5bzSUl5Dw1lb69nMjGJ3R7O0INvZ1LLpRi5dzz3WibkGudiokhlUEux4V7W9qh3t2+mqKNifvovzosw9TBwhigGiGEIUSwijidNIghMBGBG6ldCdFl9wGI5zY4E0ASjuAuUdII8nZLGExAyVIBKwovTLYft1f26UBqDDEaaJUON4kHE0GBsNwUaCjCO3jEOBhqFA/UCQbiBQN3hTRw1CB4J0g4HoYIhuJFxNDRV3hi+XxqQnRFwJe3M979jl1TIlh+tRY/DvN28HeoEPFY7EcLnbjDsVaOzeiy6E1Qbfa7zg++EfN6v+FTtqcX/TOpVmmUq3e8279EV1MVtl/1XtUqxxrdRRajE4YNWZyNWY87PRc/lNw1cLxc65epcCjFxmIpdDqbFzPmafrXZ6w7BNbHQvRiEA5bDeulYB50rgXEW41QCXCoJUjDvnGy++5ZLSlmIe9mV/Y+TnsRrrztqb5L0ditFBzey0jMsy8Lk6Md/IOzHwGQYhyyjmGqSnOhEXkYv1SolBJtTvrjDGf/46GaxlLSTjnFgghgQkAnbrMYQIalTh7YvsCHAcjiyE7rb4gcNIEwPqZ3B+FGQ5CiPhSCWIJPgRkOzFjFBNB9Eb/topvYYOhRpGQlX9wcreUFlPuKw7UtIbLe2NO+uLUfVHqvrDVAOhmoEY9XCccjiB2fXwsDNzuuZj5afPH16W33tUfyP4s3tCjX8u2yFlyrME9W/H/cxcIwhAL7jSjnv8xC9/ldmmb1v/lZfyaj4kqev8lTf/8+/Kf0QOWCRv2KbSLdNotm/4l7JV8G6tLwq3Eq1bhY5SjZFrjORqzOPt7LnsugGfIolz3m8AcLcy3LUYdy40OnzRXHp9YnO72b1E51piTkEVwAUCAIPAtZpwriAuFeOkAsPFd1y3jPWktKHqalltpbCpQULtlk+Pq9aWVXyOkXWM8Jg6MV8vExr5bFTMQ5Qyg1ZlVEhQmRAVcpXHu4yZ9lZ0dfK4v4K/+RjwouC4BAGIBjD/xABBNHEaRTDDwWGYeibksN0PRsBJGMGPhXmJFwV+009PI4jTSMANB6xIzWwYve7aUeU1enN0/9eI4mcRHx/EPL8dm5p45+vLDxlJz948fPbp8dPcZ4+rP2ZONzXuT4zNdbb1/6Q2VU5S2zktDUxqt2yIqm5qEn0u2PJJGSBnzLq+PfSq1V/tMF1pNXk0mcj1mEsV5l1lsE9dtQyvD7rb8Dnv5NWHnfgng7Y+7/8RkPfP8C7Le8s2aUc2qXT716cOH6T2n87sc+RuxVr3SsS9Wu9eafR4t3DuRVmvZ8EZKVfnVoS5lZrIZbhzocm5AHPI0Vx6fWxzu9W9RP8bALcKwrWScKkkXKsItyrgXAGcikyX83R277iUl1tJqYP1tbLWRnlft2J2Sr27hRzuafhsjMfUM7lnPLmUIeLL5Fqd2qSR66VC7fbS2trU2NrE8OHssPFwRTs3svzru/wgFXBigDAOZh5I6TU3lIJoghcJGGGm3WDlZPB+21VwEAHl9rwYghNJcKIgDKdRBC+C4EXgnDDAjEAXI9lNN4a+3sxIfvLkcU3a8/6XbybfZU3n/lhvqmPWltHKCw/qKg47m46Ge9jrc4qdZTl9R80+QBYmJPMjsvlx8d6WeqRfMtCp7GxXvc/bD0gfd7zbTX5P865EyBVQDu9aqncrltumLVjGdVyMqknK6C0s5VRU87/k0p+9XbgeWXXxRv6F4HrrxEmbJzs2aYeOL9mOH0QOnxQOOQrXAqV7qcazCvV4N3/uwfdWj0Klcz7qVmx0LcXcynBSAeZSiNnnqB1fHdrc6XAv1buUmsiVgFINXCuAGwQA5iJSGaTAX85F7d+x3V9t337SV1khaG+RTo4qdne0PC7CPlGfMlEJX88RSeaYczP0Wb5UqlEY1XKjXISOtnXRh/vkSxOKhYmt9vay11/f3r+tZT4neNGwERLGEPxoQgipqPALbiRghGPbgYqJ4L1WGAGAEQG40QQXWh9wIwheFMGNILhhODuUYEfoVyIP6/zLMqJeZPRlvJr5kr1enLfb8VPQ80vQ85PbVstqr6UtTcmPt3T7y2rmnlYhNJ0JMAnHyIMlynDK0okFxrUF5cyIZrhX09Eq+17KCHkxdSllGArTsk4cPrFJX7mkH6eeZXLXXCnpPef6vaYfRUfFZczSUnZuPi2viP4uZ+NuxrjjrTLrqHa7+4sXn+1dzDwmZYkcPkkdsmUu32TuJQrKy4lz0R8aKYVq1wIdpchELjG5lUKlPQQgW33pxf7FpC7PMoNbGe5eRVCqgVsFIFcRrpWwADiX41CD8EPrkMV2f7OT9Gyork42PKg6OkBPeYhUopVLDWdiTCUxiIUITyThSsR6nUmPYDotppZh9C3xcPN8a2l/U2H/r9KZ+vKd/OwfBu5z4jQGCGIJYRzBhxsbaH1elLkChxk3AuVjwbvNfvDWA1YE4ETCfMWB10jARAQBCCc4YWayacRBnX/t64QP7ya+ZS/XlzNbq1hddZyRdtFoJ3+8l3e4hjB2UNq6hrmLCJnomQhVSvVKISbnG8WnRjYdZRwix3uG3RX90rR6bEjR26POzJ71ypxx+cLzKFX4/DT4tuFeXbAR8qgzOeXIHW/3fM7fKS5lNtTxWpq5g1Rxe+tpQwOnuPLo9Y8tv9ttdlGtTo+XLj4/dMzi2GcJnD5ILn+WkNNHz918UeNepCUX6sgl0P1dSiAAroUmu2zVpRc7tsk9HhVG13LMvRq4VwNy5d+PWyVwgbFidPqudXzPcX+9ez9ztLZWNkA929+Ti4R6AVd/vK/hnOikQr3yzKBDTIjaaEBxnQZDlCal2Mg5QA43VJvziqF29mA7f7xPWluYbzp9SvDjYHLnxxKCOLhL4JkdnBMB6GHG1VvykZC93wBwogA3kuBDbGD+4UaaIyACQsWK0K+G79fdaMqKryxYry48bKpg9P/kd9YyBzvZg/1HOxtnJ7saPs3APUTZxyo6l7vD29tk7QkkSgZHeMw7pZ2ID/bltF30YB053FVyWLqRIVntT+71+7/cv3PdqzS+naYr3eBKu/mYrw4n5an8Xs4n3Cuvq+d3d6pGBs82VzWbK+rtdd34sKq1RVbdIHyVsx7+dMT34YjjoznSS/rld6f27/mkxwPnrj0tdy9C3IoMriVG1zIo8SYVmFwKMAhAxqZdcp9nucGtEk5ncEiuJFwrcHIVcK2AlCMIwDc1BODV9pNX442N8qUFlVRsOKGpaHv64z2dhI9pVZhRjxtQTKfGMNSolRsUQoNCaBSzDKd0PWNPf7CuWxqVzY2IO+sKMe4juDvjxxKnv/NPLOBGEZwowI6CACwHng2F7jf7QYk9TP1R8G2cCIIXaU5E4YAdZgYgUrccvlt7ozUruq54vbbwqLGY1lJJ62w5GJ3eG15enVhfPzgQcI9RFl2xc0Jf5i1P8kbHWYNrnK09Pn1PtL8r2KexuIJTFPoQ3cA6QVh04+aaNrdizSt9nJTNudpi9GjBrzSZvH4Zr1eJA1/2FuYUjVTX1BRT25r4XW2SkX7F6iKyMK0Zo6oHujX9PdrOdmVDo7S8XhCVQvVJaLGPbr30YMLlduM5n5RKjxIduchILjW5wb7e5FxgIuUZ7D+pHJ+tXbzX51lhIFfiFPOKgmI2PcxCMFYwUr7B6avK4R2b8mLj2duplmbV7IySto+cHBno+9jJkU4i0GmUmEKqV5yhGo1OpzPoNJiEq5NwUDEHZR8gtA0NbQOhbej2VtTU5lwT9z4hiIdm5Zuf02jAhgdVBCsS0MINC4GywZD9Jj+wb/4mJwJA3/9fACIAOxywIwhmBLoUsl3j3/kxsrZgsSb/sLmMNtEvmp1jTG9tjq4vLGzv0GhCJk15eMxaYMxOsAaGGO0TrIH5/SUmT3QqVJ5weUKJzKjHZUKDgKPjsVDWMbK/iYwM80OSKr1ShwMKaH9WMW98HEv53FJbWM2ZoOo3JozrQ/TB1tbiX70ttGGqYmJEMzFwNjmiGaFqJke1EyPaYaqmp0tV/1NUVs37lHeYlD7hG/jpnE9ag3uxjlKEUUpxcinuXGByzjeScvX2n5QOz1Zsk6me5QZKBcw/MAKqoPuTK4FrKe5abCLlGx1zlI5ZbErGWmbW7M862cSoZm0JOdhE9ze1extaPhv+ASyG8oh7uszcXuPs0EVsHl+mVRiUEgOfiZ7sqGibqpMd3eG6crglG+PcJQRxMKXwza0nzwwAKxIwI8BhmHEuUNIffNB0Hd+H0m3o++YibM4/kYATDtjhBCucYISjiyGb1QE9nyJqC+brimiddcwpqmRlTrS+wdk75J5y1GcCnfRULxAoDvhHa9yVbcnqMmNRLFOJT7ViHiLmI3KJTi3XKWSIWm5QnhkEbIRFQw62NQPU0xdv2n5k/ZioqeGM9CkXh4yH8xhzzXQyj9HmNFtjrImO5oLiwc61uQnFwox6ZQ6ZGFTOT2mnx5RTo6rRAUVft7yxQVhbKyouF9xJrj3n+7zZo/h3AYAVmFRgci40kvL1dp8U9qlLNskDnmUQABgBtfBxq4Y9qGs57lxighyjz3LHLI7bs8VX7+d+Nkh6O6Tjw8q9HeSUq9vZ0Bxu6452tGymbpfNnaQtLjC2drlMOoevVZjOBHoRF1FKMAHLQN9FjrYUE+3vTOxEQhD7NwCwsfkNAJyBwX6YcTZQ1HvrsNkf7MFx1wxANIAwRMJXdgS0PjOMYIQji8Gb1fGE3c8AAAiVSURBVAHUz+HVPyYaimk9DZyRDt7eikLE1SklBo0Mk/IRKQ85E+m4HCmPLxXLFGKJQsJHZXydQqSX8FCJANEhmEFnOhOjMqH+9ETLpev31lVTw6KOVnbll59n86M4Z9Uk3FKfzGqOZzH2Ms5Z4a9TOQvd6v2ZhY7GnrqOlUn+3Jh6cli9sWxYnNYuTOvmp7Vzk/ruNllTk7SqWpzxouvc1RddlGIdpQS6vxu8d9TkXISR8nQXP8rtnszb3h/yLDO4VwByNU6pBe61ONkcBHAwLsOd8owOn6ROH09dU2ezPi8NDqrmZ5DxEWRiVMpkaDhM7eGWdnsV2dvSrG/yd4/F+4dnJ8daLlN3ykD5TJR+qGAeqrlHuv0V5co0b6oj08SCqnkIgLmuwvzDjIAj2AncZRpng047b9Kab4DdCAgAO4Lg/A0ABOO3+zPDAD1MOx+yXX1j4Et4xfeRxgoa9RdvaVTGO9YrREaVxChiI1KeTik2qCRGuVCnOcPUckwlNcj4OpVUr5Lo5EKDlKeXnqIijpZLQw42lKxDlEVD6dsaxj66t4EMUgUNJb3MqSHd8bKJu7pHrVn9VSCZ7VWtDiI7Y9zZttPFLvESdaSudrBtaWJENT6sHqMqxgc1YwPKwV5lf6equVFSWyd59ab33PVXVHKRjlyGuZXAGgArcJHJOVdn90Fu93jW5t6IR6nBvdy8pq4D5FqcXI27wc8rgDOzU64BAvCB75wy8Tl3k0pVTo6q56a04yOK2Wn54uzZ1MjZ8rRme1mzuXK2u6E+2NLQdhHmkf74ULV9xFje29/eEe0uq1an5DMjjJn2x/hJLMGLhX0nrKtR0PqMCMAIB8dhYAcCwGv/99EvfwgA0+zvHHiWC63/HwAAMwwch6LzodtVAf05oVW5o3XFe/3N3PUZJWNHwzlSS9hG7hEiYOqFLFTM1sn4eoVQr5QaZXwEUWA6lQmRY3KhXsozCjkaHkdBP1TStjRcmp7P1PNOEAFbzzpCDzbRwb7T2pLh0bpm5kAnd6S99FVGdtrnhu8Vk1Wl0uke5UIPrbeMN9rU8K1kdlw2M66en9DOTaJTo1pqt7y/U97ZImuok7x63XPu2psRciFKKTW6lcAb/5wLMNciE+kHagZgxvbesEep3r3CBAtALe5eA2vA7xRkBkDv8ElG+sh3fjTyo2S/p087QlXNTWnnptBBqmJqFBmjKmfGlPtbesa+cX9Ne7Ch2VtXMo4MR/vKhd3tud31qVn61Ih0alA+0ne00P0YMGPMaR1OWIATBRgRBCOSYITjR6H4VohxJpDXevOg8Rq+HQ6YkbDe/q/1ORAPwAqHAByFIvNhm+X+fdlhdYVTbXWsvib+9IDg9ETPPtCeHqNcmpa1r+bSUPahRsA0iNk6Cdcg4xkkXPRMoFeKDBIOKuKiR2zOyvHGxsERk6bmHuv4DEQqROVimI4Yewj72Li7iSzMSIY61oZbRn9VDLT9pNeWHTSULFXkNHXll883NtS8yyr8UDs7Ltla1u2soqtzms0VdHEanZ9AJ0YU7S2yN297z119M0nO17qXGd2KMZcSuIdwLTKSfmjt3p/ZPZq2vTfkUaJzLzdRqnGPOpPH7xQEJzLcrdR06YfO4aPM6SP/0t3OvIr9+iZpa4u4r1vS1i2paRV29MqovfLhXtnCpGp7EdldRg7W0c1F7fayen1Rvrx8urQkmB6T9XcJe5r5PU3r632PAT2WYEcT7ChoXBacfglmBHEShh+E4Bsh2HQQu+mv/QZ/fMcMAMtchOE2whwBzHBYq5nwSjRkNnq73L/zfWhz1XJHA6e7gTtFFTAPNNwjLZeuVYqNfCbCYshox6dMuop7hDJ2VYxdNfcIEbHMeLD0Ao52g0abP1xe2tnZ3RbC36Wrz4QGucggPkWFbD3nBJVL9Sd7atYhyqHp9tbVW0vI8pR6ZRodpyqGOgTU1pP2nwcdP5mzY4rpUcXanGZ9UUk/1GwsqjeXtAtTSmqv8sP7vnN+WbNu+RpPCIDJrRh3LTK5FBlI37X2H6T2j6ds7w1cKdG5l5k8q4FHDe5Zaw6FGpxSiZNLMadcveNHyaWPpw53OnziK26mDObUcLPKTwJfTHk8nbiWMvyxilHVIhoYVM6Oa2ZHFQsTyokJ1eKUbHHybGLibGhY0tsj6WgX/ao77Wpc3B9JwU+i4IWKrEjAiiRYUcRJOExB9FDTfhC+HoTNBtPr/9hr8Me3QgAzCr4Hmj7S3H1G4r8BYITju8Gq6cj9Cv/md9EN5et1RQed9SxqK2duiMc70h9vKk92lLS9s809+uzmyt4en03TnR6j/GOUva+FaWofZe9oD9bkuzuitQ0G7VB2tK3kHWulfD2XphWxEY0cE3HhUp1D0/KOEe4JerSh3VtSH64ih+ua/RXl5pJ6a8GwMo0sTCEL49qVGe3qrHp2VDM3pZqfPhsfko8NKHvbpX2dypzP/ed83y+TCzQeEACjazHmUmR0KTI4fdfA7d2jKZukAY8ilFKKedRAlu+VengoZu5HAbkMI+XqHT+IL388tU1otYlusLw/7/R0yfn52uX0ZacXa5T3234f9wI+rt8rOCjpEBc3nFR2iKI/LX5rPHxfvhWfPXf760LEy+GEdxMpn+Zyf7SxZlMAPRowowErmmBFEowogm6uAcdhpr1Q02oQNhO0V359ty4A3wo1v82cpv4XMGYkTFknEfhWiGQohFFz81fWvbrSjebq49FOwWinYGFYuj17tr+koK1rdpbkW6vi/W0Jh4ay9rRCBsrcUzJ2tUdrmv0FJX1Fc7SC7C7JD9cUrF3dyRbC2EZYe1rWPso+0LIP1Sc7WsaO7ngT4R7paVuazTnV7pJ2f1VztKmUnOqFXPR4B6XvGA429FvLyOYyMjuqHKHKRwfkfZ2SnnbxxIiqq03c0SLJyaH+f9WSWOJh63luAAAAAElFTkSuQmCC);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);

            // Reset panel to center every time the modal is opened (fixes off-screen lock after dragging outside window)
            const _centerPanel = () => {
                panel.style.top = '100px';
                panel.style.left = 'calc(50% - 25rem)';
            };
            const _visibilityObserver = new MutationObserver(() => {
                if (!modal.classList.contains('hidden')) _centerPanel();
            });
            _visibilityObserver.observe(modal, { attributes: true, attributeFilter: ['class'] });

            console.log('[Guild Modal] v3.1 - Transformed to draggable floating panel with territories');

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

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

        const onMouseDown = (e) => {
            if (e.target.closest('.guild-modal-resize') || e.target.closest('button')) return;
            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            const rect = panel.getBoundingClientRect();
            offsetX = rect.left;
            offsetY = rect.top;
            panel.style.userSelect = 'none';
            document.addEventListener('mousemove', onMouseMove, true);
            document.addEventListener('mouseup', onMouseUp, true);
            e.preventDefault();
            e.stopPropagation();
        };

        const onMouseMove = (e) => {
            if (!isDragging) return;
            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;
            panel.style.left = (offsetX + deltaX) + 'px';
            panel.style.top = (offsetY + deltaY) + 'px';
        };

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

        header.addEventListener('mousedown', onMouseDown, true);
        
        // Also make the header bar itself draggable
        const headerBar = panel.querySelector('.guild-modal-header');
        if (headerBar && headerBar !== header) {
            headerBar.addEventListener('mousedown', onMouseDown, true);
        }
    }

    function setupResizeHandling(panel, handle) {
        let isResizing = false;
        let startX = 0, startY = 0, startW = 0, startH = 0;

        handle.addEventListener('mousedown', (e) => {
            isResizing = true;
            startX = e.clientX;
            startY = e.clientY;
            const rect = panel.getBoundingClientRect();
            startW = rect.width;
            startH = rect.height;
            panel.style.userSelect = 'none';
            document.addEventListener('mousemove', onMouseMove, true);
            document.addEventListener('mouseup', onMouseUp, true);
            e.preventDefault();
            e.stopPropagation();
        });

        const onMouseMove = (e) => {
            if (!isResizing) return;
            const newW = Math.max(300, startW + (e.clientX - startX));
            const newH = Math.max(200, startH + (e.clientY - startY));
            panel.style.width = newW + 'px';
            panel.style.maxHeight = newH + 'px';
        };

        const onMouseUp = () => {
            isResizing = false;
            panel.style.userSelect = 'auto';
            document.removeEventListener('mousemove', onMouseMove, true);
            document.removeEventListener('mouseup', onMouseUp, true);
        };
    }

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

    // --- Menu Commands ---
    // Commented out to keep the Tampermonkey menu clean.
    // Uncomment any block below to re-expose it in the menu.
    // All underlying functionality remains intact and accessible via the in-page UI.
    
    /*
    GM_registerMenuCommand("Snapshot Interval: Hourly", () => {
        CONFIG.minSnapshotInterval = SNAPSHOT_INTERVALS.HOURLY;
        GM_setValue('min_snapshot_interval', CONFIG.minSnapshotInterval);
        updateSnapshotIntervalUI();
        alert(`Snapshot Interval set to: Hourly (1 hour)`);
    });

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

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

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

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

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

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

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

    GM_registerMenuCommand("Toggle Territory Overlay", () => {
        toggleTerritories();
    });

    GM_registerMenuCommand("Toggle Player Markers", () => {
        togglePlayers();
    });

    GM_registerMenuCommand("Territory Settings", () => {
        // Open the guild modal projects tab where settings live
        if (typeof window.toggleMyGuildModal === 'function') {
            const modal = document.getElementById('myGuildModal');
            if (modal && modal.classList.contains('hidden')) window.toggleMyGuildModal();
            if (typeof window.switchGuildTab === 'function') window.switchGuildTab('projects');
            setTimeout(() => {
                const collapsible = document.getElementById('territorySettingsCollapsible');
                if (collapsible) {
                    const content = collapsible.querySelector('.territory-settings-content');
                    const arrow = collapsible.querySelector('.toggle-arrow');
                    if (content && content.classList.contains('collapsed')) {
                        content.classList.remove('collapsed');
                        if (arrow) arrow.classList.remove('collapsed');
                    }
                }
            }, 200);
        }
    });
    */

    // --- Initialization ---

    function init() {
        transformGuildModal();
        hookTerritoryToMap();
        hookPlayersToMap();

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

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

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

    console.log('[Guild Modal] v3.4.0 - Loaded with territory map overlay, player markers, territory-aware XP tracker, and activity-aware territory coloring');

            })();
            _featureStatus.guildOverhaul = 'ok';
            console.log('[GeoPixelcons++] ✅ Guild Overhaul loaded');
        } catch (err) {
            _featureStatus.guildOverhaul = 'error';
            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%';

        // Auto-enable newly purchased colors in the page's active palette,
        // mirroring what the native single-color purchase does.
        const successCount = toPurchase.filter(hex => results.get(hex) === 'purchased').length;
        if (successCount > 0) {
            try {
                let colorsLen;
                try { colorsLen = Colors.length; } catch (_) { colorsLen = (_pw.Colors || []).length; }
                const s = document.createElement('script');
                s.textContent = `(function(){try{for(var i=${colorsLen};i<${colorsLen + successCount};i++){if(!activeColors.includes(i))activeColors.push(i);}localStorage.setItem('activeColors',JSON.stringify(activeColors));}catch(e){}})();`;
                (document.head || document.documentElement).appendChild(s);
                s.remove();
            } catch (e) {
                console.warn('[BulkPurchase] Could not auto-enable purchased colors:', e);
            }
        }

        if (typeof window.synchronize === 'function') window.synchronize();

        return results;
    }

    /**
     * Switch the open modal into a results view.
     * The colour rows are already updated in real-time; this just updates the
     * title/subtitle and hides the confirm button.
     */
    function showBulkResults(original, results) {
        const purchased = original.filter(h => results.get(h) === 'purchased').length;
        const owned     = original.filter(h => results.get(h) === 'owned').length;
        const failed    = original.filter(h => results.get(h) === 'failed').length;
        const skipped   = original.filter(h => results.get(h) === 'skipped').length;

        document.getElementById('gp-bulk-title').textContent = 'Purchase Complete';

        const parts = [];
        if (purchased > 0) parts.push(`${purchased} purchased`);
        if (owned     > 0) parts.push(`${owned} already owned`);
        if (skipped   > 0) parts.push(`${skipped} skipped — insufficient Pixels`);
        if (failed    > 0) parts.push(`${failed} failed`);
        document.getElementById('gp-bulk-subtitle').textContent = parts.join(' · ');

        document.getElementById('gp-bulk-confirm').style.display = 'none';
    }

    // ─── Profile card queue helpers ───────────────────────────────────────────────

    /** Remove a single hex value from the textarea and fire 'input' to refresh the queue. */
    function removeColorFromTextarea(hex) {
        const textarea = document.getElementById('gp-bulk-textarea');
        if (!textarea) return;
        const { valid } = parseColorInput(textarea.value);
        const remaining = valid.filter(c => c !== hex);
        textarea.value = remaining.length ? remaining.join(', ') : '';
        textarea.dispatchEvent(new Event('input'));
    }

    /**
     * Re-render the right-side queue list from the current textarea contents.
     * Unowned colors come first (with Buy buttons); owned are grayed at the bottom.
     */
    function refreshColorQueue() {
        const textarea  = document.getElementById('gp-bulk-textarea');
        const list      = document.getElementById('gp-bulk-queue-list');
        const emptyHint = document.getElementById('gp-bulk-empty-hint');
        const buyAllBtn = document.getElementById('gp-bulk-buy-all-btn');
        const infoEl    = document.getElementById('gp-bulk-parse-info');
        if (!textarea || !list) return;

        const { valid, invalid } = parseColorInput(textarea.value);
        const ownedSet = buildOwnedSet();
        const unowned  = valid.filter(c => !ownedSet.has(c));
        const owned    = valid.filter(c =>  ownedSet.has(c));

        // Parse-info label (below textarea)
        if (infoEl) {
            if (!textarea.value.trim()) {
                infoEl.textContent = '';
            } else {
                const parts = [`${unowned.length} to purchase`];
                if (owned.length   > 0) parts.push(`${owned.length} already owned`);
                if (invalid.length > 0) parts.push(`${invalid.length} unrecognised`);
                infoEl.textContent = parts.join(' · ');
            }
        }

        // Buy All button state
        if (buyAllBtn) {
            const n = unowned.length;
            buyAllBtn.textContent = `🛒 Buy All (${n})`;
            buyAllBtn.disabled       = n === 0;
            buyAllBtn.style.opacity  = n === 0 ? '0.5' : '1';
            buyAllBtn.style.cursor   = n === 0 ? 'not-allowed' : 'pointer';
        }

        // Rebuild list
        list.innerHTML = '';
        if (valid.length === 0) {
            if (emptyHint) emptyHint.style.display = 'block';
            return;
        }
        if (emptyHint) emptyHint.style.display = 'none';

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