Greasy Fork is available in English.

GeoGuessr Liked Maps Widget

Creates a widget on the GeoGuessr start page featuring your liked maps

// ==UserScript==
// @name         GeoGuessr Liked Maps Widget
// @version      0.9.8
// @namespace    https://github.com/asmodeo
// @icon         https://parmageo.vercel.app/gg.ico
// @description  Creates a widget on the GeoGuessr start page featuring your liked maps
// @author       Parma
// @match        https://www.geoguessr.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      geoguessr.com
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';
    // ======================
    // Constants & Configuration
    // ======================
    const CONFIG = {
        STORAGE_KEY: 'geoguessr_liked_maps',
        SORT_STORAGE_KEY: 'geoguessr_sort_preference',
        PINNED_MAPS_STORAGE_KEY: 'geoguessr_pinned_maps',
        SELECTORS: {
            RIGHT_SIDEBAR: '.pro-user-start-page_right__1Hf3g',
            DAILY_CHALLENGE_WIDGET: '.pro-user-start-page_right__1Hf3g .widget_root__j5z2N'
        }
    };
    const ONE_DAY_MS = 24 * 60 * 60 * 1000; // 24 hours in ms
    // ======================
    // State Management
    // ======================
    let currentPathname = window.location.pathname;
    let widgetInitialized = false;
    let likedMaps = [];
    let filteredMaps = [];
    let searchTerm = '';
    let currentSort = GM_getValue(CONFIG.SORT_STORAGE_KEY, 'default');
    let pinnedMapIds = new Set();
    let tooltip = null;
    // Auto-sync state
    let lastSyncTimestamp = GM_getValue('geoguessr_last_sync_timestamp', 0);
    let autoSyncTimerId = null;
    let sidebarObserver = null;
    // Initialization flag
    let initialDataLoaded = false;
    // ======================
    // Utility Functions
    // ======================
    /**
     * Checks if the current page is the GeoGuessr homepage.
     * @returns {boolean}
     */
    function isHomePage() {
        return window.location.pathname === '/' || window.location.pathname === '';
    }
    /**
     * Trims special and whitespace characters from the start of a string.
     * @param {string} str
     * @returns {string}
     */
    function trimSpecialCharacters(str) {
        return str.replace(/^[\s\t\u0000-\u001F\u007F-\u009F\u2000-\u200F\u2028-\u202F]+/, '');
    }
    /**
     * Debounces a function call.
     * @param {Function} func
     * @param {number} delay
     * @returns {Function}
     */
    function debounce(func, delay) {
        let timeoutId;
        return (...args) => {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => func.apply(null, args), delay);
        };
    }
    // ======================
    // Auto-Sync Management
    // ======================
    /**
     * Clears any existing auto-sync timer.
     */
    function clearAutoSync() {
        if (autoSyncTimerId) {
            clearTimeout(autoSyncTimerId);
            autoSyncTimerId = null;
        }
    }
    /**
     * Schedules the next auto-sync for 24 hours from now.
     */
    function scheduleNextAutoSync() {
        if (!isHomePage() || document.hidden) return;
        clearAutoSync();
        autoSyncTimerId = setTimeout(() => {
            fetchLikedMaps((success) => {
                if (success) {
                    lastSyncTimestamp = Date.now();
                    GM_setValue('geoguessr_last_sync_timestamp', lastSyncTimestamp);
                }
                scheduleNextAutoSync();
            });
        }, ONE_DAY_MS);
    }
    /**
     * Initiates the auto-sync system, performing an immediate sync if needed.
     */
    function startAutoSync() {
        const now = Date.now();
        if (lastSyncTimestamp === 0 || (now - lastSyncTimestamp) >= ONE_DAY_MS) {
            const message = lastSyncTimestamp === 0 ?
                'GeoGuessr Liked Maps Widget: First run. Performing initial sync.' :
                'GeoGuessr Liked Maps Widget: More than 24 hours since last sync. Performing sync.';
            console.log(message);
            fetchLikedMaps((success) => {
                if (success) {
                    lastSyncTimestamp = Date.now();
                    GM_setValue('geoguessr_last_sync_timestamp', lastSyncTimestamp);
                }
                scheduleNextAutoSync();
            });
        } else {
            scheduleNextAutoSync();
        }
    }
    // ======================
    // Map Data Management
    // ======================
    /**
     * Sorts an array of map objects based on the current sort preference.
     * @param {Array} maps
     * @returns {Array}
     */
    function sortMaps(maps) {
        const sorted = [...maps];
        switch (currentSort) {
            case 'a-z':
                return sorted.sort((a, b) => {
                    const nameA = trimSpecialCharacters(a.name).toLowerCase();
                    const nameB = trimSpecialCharacters(b.name).toLowerCase();
                    return nameA.localeCompare(nameB);
                });
            case 'z-a':
                return sorted.sort((a, b) => {
                    const nameA = trimSpecialCharacters(a.name).toLowerCase();
                    const nameB = trimSpecialCharacters(b.name).toLowerCase();
                    return nameB.localeCompare(nameA);
                });
            case 'creator':
                return sorted.sort((a, b) => {
                    const creatorA = (a.inExplorerMode ? '_CLASSIC_MAP_' : (a.creator?.nick || 'Unknown')).toLowerCase();
                    const creatorB = (b.inExplorerMode ? '_CLASSIC_MAP_' : (b.creator?.nick || 'Unknown')).toLowerCase();
                    return creatorA.localeCompare(creatorB);
                });
            case 'updated':
                return sorted.sort((a, b) => {
                    const dateA = new Date(a.updatedAt || 0);
                    const dateB = new Date(b.updatedAt || 0);
                    return dateB - dateA;
                });
            default:
                return sorted;
        }
    }
    /**
     * Filters and sorts the liked maps based on the current search term and pin state.
     * Pinned maps are always shown first.
     */
    function filterAndSortMaps() {
        let filtered;
        if (searchTerm === '') {
            filtered = [...likedMaps];
        } else {
            filtered = likedMaps.filter(map =>
                trimSpecialCharacters(map.name).toLowerCase().includes(searchTerm)
            );
        }
        const pinnedMaps = filtered.filter(map => pinnedMapIds.has(map.id));
        const unpinnedMaps = filtered.filter(map => !pinnedMapIds.has(map.id));
        const sortedPinned = sortMaps(pinnedMaps);
        const sortedUnpinned = sortMaps(unpinnedMaps);
        filteredMaps = [...sortedPinned, ...sortedUnpinned];
    }
    /**
     * Fetches the user's liked maps from the GeoGuessr API.
     * @param {Function} callback - Called with a boolean indicating success.
     */
    function fetchLikedMaps(callback) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://www.geoguessr.com/api/v3/likes?count=0',
            headers: {
                'Accept': 'application/json',
                'Cache-Control': 'no-cache',
                'User-Agent': 'GeoGuessrLikedMapsWidget/0.9.8 (UserScript; https://greasyfork.org/)'
            },
            onload: function (response) {
                const ok = response.status >= 200 && response.status < 300;
                if (ok) {
                    try {
                        const data = JSON.parse(response.responseText);
                        // Cleanse pinned map IDs
                        const newLikedMapIds = new Set(data.map(map => map.id));
                        pinnedMapIds = new Set([...pinnedMapIds].filter(id => newLikedMapIds.has(id)));
                        likedMaps = data || [];
                        // Load and sanitize pinned map IDs from storage
                        const storedPinned = GM_getValue(CONFIG.PINNED_MAPS_STORAGE_KEY, '[]');
                        try {
                            const parsedPinned = JSON.parse(storedPinned);
                            // Use the cleansed set, but save it back to storage
                            GM_setValue(CONFIG.PINNED_MAPS_STORAGE_KEY, JSON.stringify([...pinnedMapIds]));
                        } catch (e) {
                            console.error('Error parsing stored pinned maps:', e);
                        }
                        filterAndSortMaps();
                        GM_setValue(CONFIG.STORAGE_KEY, JSON.stringify(likedMaps));
                        initialDataLoaded = true;
                        if (widgetInitialized) {
                            updateWidgetContent();
                        }
                        showSyncFeedback(true);
                        if (typeof callback === 'function') callback(true);
                    } catch (e) {
                        console.error('Error parsing JSON:', e);
                        showError('Error parsing liked maps data');
                        showSyncFeedback(false);
                        if (typeof callback === 'function') callback(false);
                    }
                } else {
                    console.error('Failed to fetch liked maps. Status:', response.status);
                    showError('Failed to fetch liked maps. Are you logged in?');
                    showSyncFeedback(false);
                    if (typeof callback === 'function') callback(false);
                }
            },
            onerror: function (error) {
                console.error('Error fetching liked maps:', error);
                showError('Error fetching liked maps. Check console for details.');
                showSyncFeedback(false);
                if (typeof callback === 'function') callback(false);
            }
        });
    }
    /**
     * Attempts to load liked maps from local storage. If unavailable or invalid, fetches from API.
     */
    function loadLikedMapsEarly() {
        const storedMaps = GM_getValue(CONFIG.STORAGE_KEY, null);
        if (storedMaps) {
            try {
                likedMaps = JSON.parse(storedMaps);
                const storedPinned = GM_getValue(CONFIG.PINNED_MAPS_STORAGE_KEY, '[]');
                try {
                    const parsedPinned = JSON.parse(storedPinned);
                    pinnedMapIds = new Set(parsedPinned);
                } catch (e) {
                    console.error('Error parsing stored pinned maps:', e);
                    pinnedMapIds = new Set();
                }
                filterAndSortMaps();
                initialDataLoaded = true;
            } catch (e) {
                console.error('Error parsing stored maps:', e);
                fetchLikedMaps();
            }
        } else {
            fetchLikedMaps();
        }
    }
    // ======================
    // UI & Tooltip Management
    // ======================
    /**
     * Removes the currently displayed tooltip, if any.
     */
    function removeTooltip() {
        if (tooltip) {
            tooltip.remove();
            tooltip = null;
        }
    }
    /**
     * Creates and displays a tooltip for a map.
     * @param {Object} map - The map object.
     * @param {HTMLElement} element - The element to position the tooltip relative to.
     */
    function showMapTooltip(map, element) {
        removeTooltip();
        const coordinateCount = map.coordinateCount || 'Unknown';
        const updatedAt = map.updatedAt ? new Date(map.updatedAt).toLocaleDateString() : 'Unknown';
        const description = map.description || 'No description available.';
        const tags = map.tags || [];
        tooltip = document.createElement('div');
        tooltip.className = 'map-tooltip';
        tooltip.innerHTML = `
            <div class="tooltip-header">
                <div class="tooltip-locations">${coordinateCount} locations</div>
                <div class="tooltip-updated">Updated: ${updatedAt}</div>
            </div>
            <div class="tooltip-description">${description}</div>
            ${tags.length > 0 ? `
                <div class="tooltip-tags">
                    ${tags.map(tag => `<span class="tooltip-tag">${tag}</span>`).join('')}
                </div>
            ` : ''}
        `;
        document.body.appendChild(tooltip);
        const rect = element.getBoundingClientRect();
        const tooltipRect = tooltip.getBoundingClientRect();
        const left = rect.left - tooltipRect.width;
        const top = rect.top + rect.height * 2;
        tooltip.style.left = `${left}px`;
        tooltip.style.top = `${top}px`;
        setTimeout(() => tooltip.classList.add('visible'), 10);
    }
    /**
     * Shows visual feedback on the sync button (success or error).
     * @param {boolean} success
     */
    function showSyncFeedback(success) {
        const syncButton = document.querySelector('.sync-button');
        if (!syncButton) return;
        const iconElement = syncButton.querySelector('.button_icon__qFeMJ');
        if (!iconElement) return;
        if (!syncButton.dataset.originalIcon) {
            syncButton.dataset.originalIcon = iconElement.innerHTML;
        }
        syncButton.classList.remove('syncing', 'sync-success', 'sync-error');
        if (success) {
            iconElement.innerHTML = `
                <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                    <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
                </svg>
            `;
            syncButton.classList.add('sync-success');
        } else {
            iconElement.innerHTML = `
                <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                    <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
                </svg>
            `;
            syncButton.classList.add('sync-error');
        }
        setTimeout(() => {
            syncButton.classList.remove('sync-success', 'sync-error');
            if (syncButton.dataset.originalIcon) {
                iconElement.innerHTML = syncButton.dataset.originalIcon;
            }
        }, 2000);
    }
    /**
     * Displays an error message in the widget content area.
     * @param {string} message
     */
    function showError(message) {
        updateWidgetContent(message);
    }
    // ======================
    // Widget Management
    // ======================
    /**
     * Removes the widget and its tooltip from the DOM.
     */
    function removeWidget() {
        const widget = document.getElementById('geoguessr-liked-maps-widget');
        if (widget) {
            widget.remove();
        }
        removeTooltip();
    }
    /**
     * Retrieves the right sidebar element.
     * @returns {HTMLElement|null}
     */
    function getRightSidebar() {
        return document.querySelector(CONFIG.SELECTORS.RIGHT_SIDEBAR);
    }
    /**
     * Creates the skeleton/HTML structure of the widget.
     * @returns {HTMLElement}
     */
    function createWidgetSkeleton() {
        const widget = document.createElement('div');
        widget.id = 'geoguessr-liked-maps-widget';
        widget.className = 'widget_root__j5z2N';
        const widgetBorder = document.createElement('div');
        widgetBorder.className = 'widget_widgetBorder__91no_';
        widgetBorder.style.setProperty('--slideInDirection', '1');
        const widgetOuter = document.createElement('div');
        widgetOuter.className = 'widget_widgetOuter__6pfjR';
        const widgetInner = document.createElement('div');
        widgetInner.className = 'widget_widgetInner__rjXwy';
        const header = document.createElement('div');
        header.className = 'widget_header__51RHy';
        header.innerHTML = `
        <div class="widget_title___3rHd">
            <label style="--fs:var(--font-size-16);--lh:var(--line-height-16)" class="label_label__9xkbh shared_boldWeight__U2puG label_italic__LM62Y shared_whiteVariant__BKF94">Liked Maps</label>
        </div>
        <div class="widget_rightSlot__B0ZxO">
            <div class="sort-container">
                <button class="sort-button button_button__aR6_e button_variantTertiary__wW1d2 button_sizeSmall__MB_qj" title="Sort Maps">
                    <span class="button_icon__qFeMJ">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"/>
                        </svg>
                    </span>
                </button>
                <div class="sort-dropdown">
                    <div class="sort-option">
                        <label class="sort-radio-label">
                            <input type="radio" name="sort" value="default" ${currentSort === 'default' ? 'checked' : ''}>
                            <span class="radio-custom"></span>
                            Default
                        </label>
                    </div>
                    <div class="sort-option">
                        <label class="sort-radio-label">
                            <input type="radio" name="sort" value="a-z" ${currentSort === 'a-z' ? 'checked' : ''}>
                            <span class="radio-custom"></span>
                            A-Z
                        </label>
                    </div>
                    <div class="sort-option">
                        <label class="sort-radio-label">
                            <input type="radio" name="sort" value="z-a" ${currentSort === 'z-a' ? 'checked' : ''}>
                            <span class="radio-custom"></span>
                            Z-A
                        </label>
                    </div>
                    <div class="sort-option">
                        <label class="sort-radio-label">
                            <input type="radio" name="sort" value="creator" ${currentSort === 'creator' ? 'checked' : ''}>
                            <span class="radio-custom"></span>
                            Creator
                        </label>
                    </div>
                    <div class="sort-option">
                        <label class="sort-radio-label">
                            <input type="radio" name="sort" value="updated" ${currentSort === 'updated' ? 'checked' : ''}>
                            <span class="radio-custom"></span>
                            Last Updated
                        </label>
                    </div>
                </div>
            </div>
            <div class="search-container">
                <input type="text" class="map-search-input" placeholder="Search maps..." value="${searchTerm}" />
                <button class="clear-search-button ${!searchTerm ? 'hidden' : ''}" title="Clear search">
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
                    </svg>
                </button>
            </div>
            <button class="sync-button button_button__aR6_e button_variantTertiary__wW1d2 button_sizeSmall__MB_qj" title="Sync Liked Maps">
                <span class="button_icon__qFeMJ">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26l1.45-1.45c-.42-.99-.69-2.06-.69-3.21 0-3.31 2.69-6 6-6zm6.76 1.74l-1.45 1.45c.42.99.69 2.06.69 3.21 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/>
                    </svg>
                </span>
            </button>
        </div>`;
        widgetInner.appendChild(header);
        const dividerWrapper = document.createElement('div');
        dividerWrapper.className = 'widget_dividerWrapper__uSf4D';
        dividerWrapper.innerHTML = '<hr class="styles_divider__SMppY styles_variantWhiteTransparentSoft__bJmMq">';
        widgetInner.appendChild(dividerWrapper);
        const contentArea = document.createElement('div');
        contentArea.className = 'liked-maps-content';
        contentArea.style.overflowY = 'auto';
        contentArea.innerHTML = `
        <div style="padding: 20px; text-align: center; color: #a0aec0;">
            <div class="loading-spinner" style="margin-bottom: 10px;">
                <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
                    <path d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26l1.45-1.45c-.42-.99-.69-2.06-.69-3.21 0-3.31 2.69-6 6-6zm6.76 1.74l-1.45 1.45c.42.99.69 2.06.69 3.21 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/>
                </svg>
            </div>
            <div style="font-size: 12px;">Loading liked maps...</div>
        </div>`;
        widgetInner.appendChild(contentArea);
        widgetOuter.appendChild(widgetInner);
        widgetBorder.appendChild(widgetOuter);
        widget.appendChild(widgetBorder);
        return widget;
    }
    /**
     * Updates the content of the widget, displaying maps, errors, or placeholders.
     * @param {string|null} errorMessage
     */
    function updateWidgetContent(errorMessage = null) {
        const widget = document.getElementById('geoguessr-liked-maps-widget');
        if (!widget) return;
        const contentArea = widget.querySelector('.liked-maps-content');
        if (!contentArea) return;
        const rightSidebar = getRightSidebar();
        if (rightSidebar) {
            const sidebarHeight = rightSidebar.offsetHeight;
            const calculatedHeight = sidebarHeight - 624;   // Safety margin for other widgets
            const minHeight = 2 * 37 + 6; // 2 items minimum + padding
            const maxHeight = 12 * 37 + 6; // 12 items maximum + padding
            contentArea.style.height = `${Math.min(Math.max(calculatedHeight, minHeight), maxHeight)}px`;
        }
        widget.classList.remove('widget_loading');
        let mapItemsHTML = '';
        if (errorMessage) {
            mapItemsHTML = `
                <div style="padding: 20px; text-align: center; color: #e53e3e;">
                    <div style="margin-bottom: 10px;">
                        <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v2z"/>
                        </svg>
                    </div>
                    <div style="font-size: 12px;">${errorMessage}</div>
                </div>`;
        } else if (!filteredMaps || filteredMaps.length === 0) {
            mapItemsHTML = `
                <div style="padding: 20px; text-align: center; color: #a0aec0;">
                    <div style="margin-bottom: 10px;">
                        <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l5-5-5-5v10z"/>
                        </svg>
                    </div>
                    <div style="font-size: 12px;">${likedMaps.length === 0 ? 'No liked maps found. Click sync to load your liked maps.' : 'No maps match your search.'}</div>
                </div>`;
        } else {
            filteredMaps.forEach((map) => {
                const originalIndex = likedMaps.findIndex(m => m.id === map.id);
                const creatorName = map.inExplorerMode ? '_CLASSIC_MAP_' : (map.creator?.nick || 'Unknown');
                const creatorUrl = map.inExplorerMode ? null : (map.creator && map.creator.url ? map.creator.url : null);
                const collaboratorCount = map.collaborators ? map.collaborators.length : 0;
                const creatorDisplay = collaboratorCount > 0 ?
                    `${creatorName} (+${collaboratorCount})` : creatorName;
                const backgroundClass = map.inExplorerMode ? 'map-avatar_classic' :
                    `map-avatar_${map.avatar?.background || 'day'}`;
                const isPinned = pinnedMapIds.has(map.id);
                const pinIconHtml = `
                    <div class="pin-icon ${isPinned ? 'pinned' : ''}"
                         data-map-id="${map.id}"
                         title="${isPinned ? 'Unpin map' : 'Pin map'}">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
                        </svg>
                    </div>`;
                mapItemsHTML += `
                    <div class="liked-map-item" data-map-index="${originalIndex}">
                        ${pinIconHtml}
                        <div class="map-thumbnail ${backgroundClass}"></div>
                        <div class="liked-map-name">${map.name}</div>
                        ${map.inExplorerMode ? '<div class="official-badge">Classic</div>' :
                        creatorUrl ? `<a href="${creatorUrl}" class="liked-map-creator" onclick="event.stopPropagation();">${creatorDisplay}</a>` :
                            `<div class="liked-map-creator">${creatorDisplay}</div>`
                    }
                        <div class="liked-map-info-icon" data-map-index="${originalIndex}">
                            <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
                                <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
                            </svg>
                        </div>
                    </div>`;
            });
        }
        // Update clear search button visibility
        const searchContainer = widget.querySelector('.search-container');
        if (searchContainer) {
            const clearButton = searchContainer.querySelector('.clear-search-button');
            if (clearButton) {
                if (searchTerm) {
                    clearButton.classList.remove('hidden');
                } else {
                    clearButton.classList.add('hidden');
                }
            }
        }
        contentArea.innerHTML = `<div style="padding: 6px 8px 12px 8px;">${mapItemsHTML}</div>`;
        // Attach event listeners after updating content
        attachMapItemListeners(contentArea);
    }
    /**
     * Mounts the widget into the right sidebar, positioning it before the daily challenge widget.
     * @param {HTMLElement} widget
     * @returns {boolean} True if successfully mounted.
     */
    function mountInSidebar(widget) {
        const rightSidebar = getRightSidebar();
        if (!rightSidebar) return false;
        const existing = rightSidebar.querySelector('#geoguessr-liked-maps-widget');
        if (existing) existing.remove();
        const dailyChallengeWidget = rightSidebar.querySelector('.daily-streak_root__njtkG')?.closest('.widget_root__j5z2N');
        if (dailyChallengeWidget) {
            rightSidebar.insertBefore(widget, dailyChallengeWidget);
        } else {
            const firstWidget = rightSidebar.querySelector('.widget_root__j5z2N');
            if (firstWidget) {
                rightSidebar.insertBefore(widget, firstWidget);
            } else {
                rightSidebar.appendChild(widget);
            }
        }
        return true;
    }
    /**
     * Creates and initializes the widget on the homepage.
     */
    function createWidget() {
        if (!isHomePage()) {
            removeWidget();
            return;
        }
        const rightSidebar = getRightSidebar();
        if (!rightSidebar) return;
        removeWidget();
        const widget = createWidgetSkeleton();
        if (mountInSidebar(widget)) {
            setupWidgetInteractions(widget);
            widgetInitialized = true;
            // Update immediately to show "Loading..." or cached data
            setTimeout(() => updateWidgetContent(), 50);
            return widget;
        }
        return null;
    }
    // ======================
    // Widget Interaction Handlers
    // ======================
    /**
     * Clears the search input and resets the map list.
     */
    function clearSearch() {
        searchTerm = '';
        filterAndSortMaps();
        updateWidgetContent();
        const searchInput = document.querySelector('.map-search-input');
        if (searchInput) {
            searchInput.value = '';
            searchInput.focus();
        }
    }
    /**
     * Sets up all event listeners for the widget (sort, sync, search, pin, info, map navigation).
     * @param {HTMLElement} widget
     */
    function setupWidgetInteractions(widget) {
        // Sort Button and Dropdown
        const sortButton = widget.querySelector('.sort-button');
        const sortDropdown = widget.querySelector('.sort-dropdown');
        if (sortButton && sortDropdown) {
            sortButton.addEventListener('click', function (e) {
                e.stopPropagation();
                const isVisible = sortDropdown.classList.contains('visible');
                document.querySelectorAll('.sort-dropdown.visible').forEach(dropdown => {
                    if (dropdown !== sortDropdown) {
                        dropdown.classList.remove('visible');
                    }
                });
                sortDropdown.classList.toggle('visible', !isVisible);
            });
            const sortRadios = sortDropdown.querySelectorAll('input[name="sort"]');
            sortRadios.forEach(radio => {
                radio.addEventListener('change', function () {
                    if (this.checked) {
                        currentSort = this.value;
                        GM_setValue(CONFIG.SORT_STORAGE_KEY, currentSort);
                        filterAndSortMaps();
                        updateWidgetContent();
                        sortDropdown.classList.remove('visible');
                    }
                });
            });
        }
        document.addEventListener('click', function (e) {
            if (sortDropdown && !sortDropdown.contains(e.target) && !sortButton.contains(e.target)) {
                sortDropdown.classList.remove('visible');
            }
        });
        // Sync Button
        const syncButton = widget.querySelector('.sync-button');
        if (syncButton) {
            syncButton.addEventListener('click', function () {
                this.classList.add('syncing');
                clearAutoSync();
                fetchLikedMaps((success) => {
                    scheduleNextAutoSync();
                });
            });
        }
        // Search Input
        const searchInput = widget.querySelector('.map-search-input');
        if (searchInput) {
            if (searchTerm) {
                setTimeout(() => searchInput.focus(), 100);
            }
            const debouncedSearch = debounce(() => {
                filterAndSortMaps();
                updateWidgetContent();
            }, 300);
            searchInput.addEventListener('input', function () {
                searchTerm = trimSpecialCharacters(this.value).toLowerCase();
                debouncedSearch();
            });
        }
        // Clear Search Button
        const clearButton = widget.querySelector('.clear-search-button');
        if (clearButton) {
            clearButton.addEventListener('click', clearSearch);
        }
    }
    /**
     * Attach event listeners to map items (called after content updates)
     * @param {HTMLElement} container
     */
    function attachMapItemListeners(container) {
        // Map Item Click Event Listeners
        const mapItems = container.querySelectorAll('.liked-map-item');
        mapItems.forEach(item => {
            item.addEventListener('click', function (e) {
                if (!e.target.closest('.liked-map-info-icon') && !e.target.closest('.pin-icon')) {
                    const mapIndex = this.dataset.mapIndex;
                    if (mapIndex !== undefined && likedMaps[mapIndex] && likedMaps[mapIndex].url) {
                        window.location.href = likedMaps[mapIndex].url;
                    }
                }
            });
        });
        // Info Icon Event Listeners
        const infoIcons = container.querySelectorAll('.liked-map-info-icon');
        infoIcons.forEach(icon => {
            icon.addEventListener('mouseover', function (e) {
                const mapIndex = this.dataset.mapIndex;
                if (mapIndex !== undefined && likedMaps[mapIndex]) {
                    showMapTooltip(likedMaps[mapIndex], this);
                }
            });
            icon.addEventListener('mouseout', function () {
                removeTooltip();
            });
        });
        // Pin Icon Event Listeners
        const pinIcons = container.querySelectorAll('.pin-icon');
        pinIcons.forEach(pinIcon => {
            pinIcon.addEventListener('click', function (e) {
                e.stopPropagation(); // Prevent the click from triggering the map link
                const mapId = this.getAttribute('data-map-id');
                if (pinnedMapIds.has(mapId)) {
                    // Unpin
                    pinnedMapIds.delete(mapId);
                    this.classList.remove('pinned');
                    this.setAttribute('title', 'Pin map');
                } else {
                    // Pin
                    pinnedMapIds.add(mapId);
                    this.classList.add('pinned');
                    this.setAttribute('title', 'Unpin map');
                }
                // Persist the updated pin state to storage
                GM_setValue(CONFIG.PINNED_MAPS_STORAGE_KEY, JSON.stringify([...pinnedMapIds]));
                // Re-sort and update the UI
                filterAndSortMaps();
                updateWidgetContent();
            });
        });
    }
    // ======================
    // Page & Observer Management
    // ======================
    /**
    * Ensures the widget is mounted into the DOM.
    */
    function ensureMounted() {
        const checkReady = setInterval(() => {
            const rightSidebar = getRightSidebar();
            if (rightSidebar && rightSidebar.offsetWidth > 0) {
                clearInterval(checkReady);
                if (widgetContainer) {
                    const targetElement = getTargetElement();
                    if (targetElement && targetElement.parentNode) {
                        targetElement.parentNode.insertBefore(widgetContainer, targetElement.nextSibling);
                    }
                }
            }
        }, 200);
    }
    /**
     * Checks if the page URL has changed and triggers re-initialization if necessary.
     */
    function checkPageChange() {
        if (window.location.pathname !== currentPathname) {
            currentPathname = window.location.pathname;
            if (!isHomePage()) {
                removeWidget();
                widgetInitialized = false;
                clearAutoSync();
            } else {
                widgetInitialized = false;
                initialDataLoaded = false;
                setTimeout(initializeWidget, 200);
            }
        }
    }
    /**
    * Initializes the widget on the homepage by waiting for the right sidebar.
    */
    function initializeWidget() {
        if (widgetInitialized || !isHomePage()) return;

        // Load initial data early to prevent a blank state
        if (!initialDataLoaded) {
            loadLikedMapsEarly();
        }

        // Use a robust check to ensure the sidebar is ready
        const checkReady = setInterval(() => {
            const rightSidebar = getRightSidebar();
            if (rightSidebar && rightSidebar.offsetWidth > 0) {
                clearInterval(checkReady);
                const widget = createWidget();
                if (widget) {
                    startAutoSync();
                    ensureMounted();
                    setTimeout(() => {
                        const widgetBorder = widget.querySelector('.widget_widgetBorder__91no_');
                        if (widgetBorder) {
                            widgetBorder.classList.add('widget_hasLoaded__uIeOz');
                        }
                    }, 50);
                    widgetInitialized = true;
                }
            }
        }, 100); // Check every 100ms

        // Add a fallback timer in case something unexpected happens
        setTimeout(() => {
            clearInterval(checkReady);
            if (!widgetInitialized) {
                console.log("GeoGuessr Liked Maps Widget: Fallback initialization triggered.");
                const widget = createWidget();
                if (widget) {
                    startAutoSync();
                    ensureMounted();
                    setTimeout(() => {
                        const widgetBorder = widget.querySelector('#geoguessr-liked-maps-widget .widget_widgetBorder__91no_');
                        if (widgetBorder) {
                            widgetBorder.classList.add('widget_hasLoaded__uIeOz');
                        }
                    }, 50);
                    widgetInitialized = true;
                }

                if (!widgetInitialized) {
                    console.warn("GeoGuessr Liked Maps Widget: Failed to initialize after 2 seconds.");
                    const sidebar = getRightSidebar();
                    if (sidebar) {
                        const errorMessage = document.createElement('p');
                        errorMessage.textContent = 'Liked Maps widget failed to load. Please refresh the page.';
                        errorMessage.style.color = 'red';
                        errorMessage.style.marginTop = '10px';
                        sidebar.prepend(errorMessage);
                    }
                }
            }
        }, 2000); // Wait up to 2 seconds before giving up on the interval check
    }

    /**
     * Handles window resize events to re-initialize the widget or update its layout.
     */
    const handleResize = debounce(() => {
        if (isHomePage() && !widgetInitialized) {
            setTimeout(initializeWidget, 100);
        }
        if (isHomePage() && widgetInitialized) {
            const contentArea = document.querySelector('#geoguessr-liked-maps-widget .liked-maps-content');
            const rightSidebar = getRightSidebar();
            if (contentArea && rightSidebar) {
                const sidebarHeight = rightSidebar.offsetHeight;
                const calculatedHeight = sidebarHeight - 624; // Safety margin for other widgets
                const minHeight = 2 * 37 + 6; // 2 items minimum + padding
                const maxHeight = 12 * 37 + 6; // 12 items maximum + padding
                contentArea.style.height = `${Math.min(Math.max(calculatedHeight, minHeight), maxHeight)}px`;
            }
        }
    }, 10);
    // ======================
    // Styling
    // ======================
    /**
     * Injects all necessary CSS styles into the document head.
     */
    function addCustomStyles() {
        const styles = `
        /* Widget root */
        .widget_root__j5z2N {
            position: relative;
        }
        /* Main widget border - This is the element that slides in */
        .widget_widgetBorder__91no_ {
            --slideInDirection: -1;
            -webkit-backdrop-filter: blur(.5rem);
            backdrop-filter: blur(.5rem);
            background: color-mix(in srgb, var(--ds-color-purple-100) 90%, transparent);
            border: .0625rem solid var(--ds-color-purple-80, var(--ds-color-purple-80));
            border-radius: 1rem;
            min-height: 1rem;
            opacity: 0;
            padding: .25rem;
            transform: translateX(calc(110% * min(var(--slideInDirection), var(--allElementsOnLeftSide))));
            transition: all .75s cubic-bezier(.44,0,0,1);
        }
        .widget_widgetBorder__91no_.widget_hasLoaded__uIeOz {
            opacity: 1;
            transform: translateX(0);
        }
        .widget_widgetBorder__91no_.widget_slideInRight__TJ0yO {
            --slideInDirection: 1;
        }
        /* Outer container */
        .widget_widgetOuter__6pfjR {
            background: linear-gradient(hsla(0,0%,100%,.039), hsla(0,0%,100%,.012) 2.5rem);
            border-radius: .75rem;
            box-shadow: inset 0 0 .25rem var(--ds-color-purple-70);
            height: 100%;
            -webkit-user-select: none;
            -moz-user-select: none;
            user-select: none;
            width: 100%;
        }
        /* Inner container */
        .widget_widgetInner__rjXwy {
            --padding: 1rem;
            display: flex;
            flex-direction: column;
            position: relative;
        }
        /* Header */
        .widget_header__51RHy {
            align-items: center;
            display: flex;
            flex-direction: row;
            justify-content: space-between;
            min-height: 1.5rem;
            padding: calc(var(--padding)*.75) var(--padding) calc(var(--padding)*.5);
        }
        .widget_header__51RHy .widget_title___3rHd {
            position: relative;
        }
        .widget_header__51RHy > .widget_rightSlot__B0ZxO > a,
        .widget_header__51RHy > .widget_rightSlot__B0ZxO > button {
            margin-top: -.1875rem;
        }
        .widget_rightSlot__B0ZxO {
            display: flex;
        }
        /* Divider */
        .widget_dividerWrapper__uSf4D {
            margin: 0 var(--padding);
            width: calc(100% - var(--padding)*2);
        }
        /* Simple widget border (fallback if needed) */
        .widget_simpleWidgetBorder__Xgyhn {
            -webkit-backdrop-filter: blur(.5rem);
            backdrop-filter: blur(.5rem);
            background: color-mix(in srgb, var(--ds-color-purple-100) 95%, transparent);
            border: .0625rem solid var(--ds-color-purple-80, var(--ds-color-purple-80));
            border-radius: 1rem;
            min-height: 1rem;
            padding: .25rem;
        }
        /* Loading spinner animation */
        .loading-spinner {
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        /* Control buttons (sort, sync) */
        .sort-button,
        .sync-button {
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            height: 28px !important;
            width: 28px !important;
            min-width: 28px !important;
            padding: 0 !important;
            border: none !important;
            box-sizing: border-box !important;
            margin-top: 0px !important;
        }
        .button_icon__qFeMJ {
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            width: 14px !important;
            height: 14px !important;
            margin: 0 !important;
        }
        .button_icon__qFeMJ svg {
            width: 100%;
            height: 100%;
            display: block;
        }
        /* Sync button feedback animations */
        .sync-button.sync-success {
            animation: pulseSuccess 0.5s ease-in-out;
            background: #48bb78 !important;
            color: white !important;
            border-color: #48bb78 !important;
        }
        .sync-button.sync-error {
            animation: pulseError 0.5s ease-in-out;
            background: #f56565 !important;
            color: white !important;
            border-color: #f56565 !important;
        }
        @keyframes pulseSuccess {
            0% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7);
            }
            50% {
                transform: scale(1.05);
            }
            70% {
                box-shadow: 0 0 0 10px rgba(72, 187, 120, 0);
            }
            100% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(72, 187, 120, 0);
            }
        }
        @keyframes pulseError {
            0% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(245, 101, 101, 0.7);
            }
            50% {
                transform: scale(1.05);
            }
            70% {
                box-shadow: 0 0 0 10px rgba(245, 101, 101, 0);
            }
            100% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(245, 101, 101, 0);
            }
        }
        .sync-button.sync-success .button_icon__qFeMJ,
        .sync-button.sync-error .button_icon__qFeMJ {
            color: white !important;
        }
        /* Syncing animation */
        .sync-button.syncing {
            animation: spin 1s linear infinite;
        }
        /* Search container */
        .search-container {
            position: relative;
            display: flex;
            align-items: center;
            height: 28px;
            width: 160px !important;
        }
        .map-search-input {
            background: rgba(255, 255, 255, 0.1);
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 4px;
            color: #e2e8f0;
            padding: 6px 8px;
            font-size: 12px;
            font-family: 'ggfont', sans-serif;
            display: flex !important;
            transition: all 0.15s ease;
            height: 28px;
            width: 100% !important;
            box-sizing: border-box !important;
        }
        .map-search-input:focus {
            outline: none;
            border-color: rgba(255, 255, 255, 0.4);
            background: rgba(255, 255, 255, 0.15);
        }
        .map-search-input::placeholder {
            color: #a0aec0;
        }
        .clear-search-button {
            position: absolute;
            right: 6px;
            background: none;
            border: none;
            color: #a0aec0;
            cursor: pointer;
            padding: 2px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 16px;
            height: 16px;
        }
        .clear-search-button:hover {
            color: #e2e8f0;
            background: rgba(255, 255, 255, 0.1);
        }
        .clear-search-button.hidden {
            display: none;
        }
        /* Sort dropdown */
        .sort-container {
            position: relative;
            display: flex;
            align-items: center;
        }
        .sort-dropdown {
            position: absolute;
            top: 100%;
            left: 0;
            margin-top: 4px;
            background: #2d3748;
            border: 1px solid #4a5568;
            border-radius: 6px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
            z-index: 1001;
            min-width: 130px;
            opacity: 0;
            visibility: hidden;
            transform: translateY(-8px);
            transition: all 0.2s ease;
            pointer-events: none;
        }
        .sort-dropdown.visible {
            opacity: 1;
            visibility: visible;
            transform: translateY(0);
            pointer-events: auto;
        }
        .sort-option {
            padding: 0;
        }
        .sort-radio-label {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 12px;
            font-size: 12px;
            color: #e2e8f0;
            cursor: pointer;
            transition: background 0.15s ease;
            user-select: none;
        }
        .sort-radio-label:hover {
            background: rgba(255, 255, 255, 0.08);
        }
        .sort-radio-label:first-child {
            border-radius: 6px 6px 0 0;
        }
        .sort-radio-label:last-child {
            border-radius: 0 0 6px 6px;
        }
        .sort-radio-label input[type="radio"] {
            display: none;
        }
        .radio-custom {
            width: 12px;
            height: 12px;
            border: 2px solid #a0aec0;
            border-radius: 50%;
            position: relative;
            flex-shrink: 0;
            transition: all 0.15s ease;
        }
        .sort-radio-label input[type="radio"]:checked + .radio-custom {
            border-color: #63b3ed;
            background: #63b3ed;
        }
        .sort-radio-label input[type="radio"]:checked + .radio-custom::after {
            content: '';
            width: 4px;
            height: 4px;
            background: white;
            border-radius: 50%;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
        /* Map items */
        .liked-map-item {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 6px 10px;
            border-radius: 8px;
            margin-bottom: 3px;
            cursor: pointer;
            transition: all 0.15s ease;
            background: rgba(255, 255, 255, 0.03);
            border: 1px solid rgba(255, 255, 255, 0.1);
            position: relative;
        }
        .liked-map-item:hover {
            background: rgba(255, 255, 255, 0.08) !important;
            border-color: rgba(255, 255, 255, 0.2);
            transform: translateY(-1px);
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
        }
        /* Pin Icon Styles */
        /* Padding around the icon to make it easier to click */
        /* Margin to offset the padding so it doesn't affect layout */
        .pin-icon {
            width: 16px;
            height: 16px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
            transition: all 0.15s ease;
            opacity: 1;
            padding: 6px;
            margin: -6px;
        }
        .pin-icon:hover {
            transform: scale(1.1);
            cursor: pointer;
        }
        .pin-icon:active {
            transform: scale(1.2);
        }
        .pin-icon svg {
            width: 100%;
            height: 100%;
            display: block;
            fill: transparent;
            stroke: #a0aec0;
            stroke-width: 1.5;
            transition: all 0.15s ease;
        }
        .pin-icon:hover svg {
            stroke: #e2e8f0;
            filter: drop-shadow(0 0 3px rgba(226, 232, 240, 0.5));
        }
        .pin-icon.pinned svg {
            fill: #f8bf02;
            stroke: none;
            filter: drop-shadow(0 0 4px rgba(248, 191, 2, 0.6));
            animation: gentle-shimmer 3s ease-in-out infinite;
        }
        .pin-icon.pinned:hover svg {
            fill: #ffd700;
            filter: drop-shadow(0 0 6px rgba(255, 215, 0, 0.8));
            animation: none;
        }
        @keyframes gentle-shimmer {
            0%, 100% { filter: drop-shadow(0 0 4px rgba(248, 191, 2, 0.6)); }
            50% { filter: drop-shadow(0 0 6px rgba(255, 215, 0, 0.4)) drop-shadow(0 0 2px rgba(255, 255, 255, 0.3)); }
        }
        /* Map Avatar Styles */
        .map-avatar_day { background: #d4eaed; }
        .map-avatar_morning { background: linear-gradient(180deg, #c2db9c, #6eafe0); }
        .map-avatar_evening { background: linear-gradient(180deg, #a25e92, #01354b); }
        .map-avatar_night { background: #01354b; }
        .map-avatar_darknight { background: linear-gradient(180deg, #3c1d35, #01354b); }
        .map-avatar_sunrise { background: linear-gradient(180deg, #f8ab12, #e7861f); }
        .map-avatar_sunset { background: linear-gradient(180deg, #b34692, #ec6079); }
        .map-avatar_classic { background: #c52626; }
        .map-thumbnail {
            width: 16px;
            height: 16px;
            border-radius: 50%;
            border: 2px solid white;
            flex-shrink: 0;
        }
        /* Map Name & Creator */
        .liked-map-name {
            font-size: 12px;
            font-weight: 600;
            color: #e2e8f0;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            flex: 1;
            min-width: 0;
        }
        .liked-map-creator {
            font-size: 11px;
            color: var(--ds-color-white-40);
            font-style: italic;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            width: auto;
            min-width: unset;
            text-align: right;
            margin-right: 4px;
            text-decoration: none;
            transition: color 0.15s ease;
        }
        .liked-map-creator:hover {
            color: #e2e8f0 !important;
            transform: scale(1.05);
        }
        /* Special badge for classic maps */
        .official-badge {
            background: linear-gradient(135deg, #9900ffff, #6b03a7ff);
            color: #ffffffff !important;
            font-size: 10px;
            font-weight: 700;
            text-transform: uppercase;
            padding: 3px 6px;
            border-radius: 4px;
            letter-spacing: 0.5px;
            box-shadow: 0 2px 4px rgba(174, 0, 255, 0.3);
            margin-left: auto;
            white-space: nowrap;
        }
        /* Info icon */
        .liked-map-info-icon {
            width: 14px;
            height: 14px;
            color: #a0aec0;
            cursor: help;
            opacity: 0.7;
            transition: all 0.15s ease;
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .liked-map-info-icon:hover {
            opacity: 1;
            color: #e2e8f0;
            transform: scale(1.1);
        }
        /* Maps container and Scrollbar */
        .liked-maps-content::-webkit-scrollbar {
            width: 8px;
        }
        .liked-maps-content::-webkit-scrollbar-track {
            background: transparent;
        }
        .liked-maps-content::-webkit-scrollbar-thumb {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 4px;
            border: 2px solid transparent;
            background-clip: padding-box;
        }
        .liked-maps-content::-webkit-scrollbar-thumb:hover {
            background: rgba(255, 255, 255, 0.2);
            border: 2px solid transparent;
            background-clip: padding-box;
        }
        .liked-maps-content::-webkit-scrollbar-button {
            display: none;
        }
        .liked-maps-content {
            overflow-y: auto !important;
            scrollbar-width: thin;
            scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
        }
        /* Ensure sidebar doesn't overflow */
        .pro-user-start-page_right__1Hf3g {
            box-sizing: border-box !important;
        }
        /* Tooltip */
        .map-tooltip {
            position: fixed;
            background: #2d3748;
            border: 1px solid #4a5568;
            border-radius: 6px;
            padding: 12px;
            font-size: 12px;
            color: #e2e8f0;
            z-index: 1000;
            max-width: 300px;
            min-width: 250px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.2s ease;
        }
        .map-tooltip.visible {
            opacity: 1;
        }
        .tooltip-header {
            display: flex;
            justify-content: space-between;
            margin-bottom: 8px;
            font-weight: 600;
        }
        .tooltip-locations {
            color: #63b3ed;
        }
        .tooltip-updated {
            color: #a0aec0;
            font-size: 11px;
        }
        .tooltip-description {
            margin-bottom: 8px;
            line-height: 1.4;
        }
        .tooltip-tags {
            display: flex;
            flex-wrap: wrap;
            gap: 4px;
            margin-top: 8px;
        }
        .tooltip-tag {
            background: rgba(255, 255, 255, 0.1);
            padding: 2px 6px;
            border-radius: 4px;
            font-size: 10px;
        }
        /* Button colors */
        .button_variantTertiary__wW1d2 {
            background: rgba(255, 255, 255, 0.1) !important;
            color: #e2e8f0 !important;
        }
        .button_variantTertiary__wW1d2:hover {
            background: rgba(255, 255, 255, 0.15) !important;
        }
        /* Adjust spacing between header elements */
        .widget_header__51RHy > .widget_rightSlot__B0ZxO > .sort-container {
            margin-right: 8px !important;
        }
        .widget_header__51RHy > .widget_rightSlot__B0ZxO > .search-container {
            margin-right: 8px !important;
        }`;
        const styleSheet = document.createElement('style');
        styleSheet.textContent = styles;
        document.head.appendChild(styleSheet);
    }
    /**
    * Main initialization function.
    */
    function init() {
        addCustomStyles();
        loadLikedMapsEarly();
        let lastUrl = location.href;
        const urlObserver = new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                checkPageChange();
            }
        });
        urlObserver.observe(document, { subtree: true, childList: true });
        initializeWidget();
        window.addEventListener('resize', handleResize);
    }

    window.addEventListener('load', init);
})();