LOL Skin Search (Enhanced Edition)

为LOL皮肤列表提供多平台搜索功能预览 - 重构版本 (支持多种皮肤项结构)

// ==UserScript==
// @name         LOL Skin Search (Enhanced Edition)
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  为LOL皮肤列表提供多平台搜索功能预览 - 重构版本 (支持多种皮肤项结构)
// @author       Your Name
// @match        https://lol.qq.com/act/a20250429sale/*
// @match        https://lol.qq.com/act/*
// @match        *://lol.qq.com/act/* 
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const DEBUG = true;

    /**
     * 日志系统
     */
    const Logger = {
        debug: function(message, ...args) {
            if (DEBUG) {
                console.log(`[LOL Skin Search] ${message}`, ...args);
            }
        },
        info: function(message, ...args) {
            console.info(`[LOL Skin Search] ${message}`, ...args);
        },
        error: function(message, ...args) {
            console.error(`[LOL Skin Search] ${message}`, ...args);
        }
    };

    Logger.info('脚本开始执行 (Enhanced Edition v1.1.0)');

    /**
     * 搜索平台配置 - 可扩展平台列表
     */
    const searchPlatforms = [
        {
            id: 'douyin',
            name: '抖音',
            icon: `<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M16.6 5.82s.51.5 0 0A4.51 4.51 0 0 1 15.98 4c-.32-.48-.52-.86-.52-.86-.23-.48-.39-.97-.46-1.47h.72L16.43 3h1.75l.48-1.33h.66c-.56 1.38-2.24 2.33-2.24 2.33v2.97c.46.49 1.1.8 1.81.8 1.38 0 2.5-1.12 2.5-2.5 0-1.2-.85-2.2-1.97-2.45v-.84c2.49.37 4.3 2.51 4.3 5.04 0 2.9-2.37 5.25-5.29 5.25a5.25 5.25 0 0 1-5.23-4.84H16v8.77h-4.98V8.09a5.27 5.27 0 0 1-5.79 1.98v.85a5.25 5.25 0 0 1-2.96 6.98l-.5-.85a4.5 4.5 0 0 0 2.66-4.12c0-2.45-1.97-4.44-4.4-4.5v-.87c2.92.09 5.32 2.33 5.47 5.2h2.75A5.24 5.24 0 0 1 16.6 5.82Z"/></svg>`,
            url: 'https://www.douyin.com/discover/search/',
            param: '$s',
            color: '#FE2C55'
        },
        {
            id: 'bilibili',
            name: '哔哩哔哩',
            icon: `<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M18.223 3.086a1.25 1.25 0 0 1 0 1.768L17.08 5.996h1.17A3.75 3.75 0 0 1 22 9.746v7.5a3.75 3.75 0 0 1-3.75 3.75h-12.5A3.75 3.75 0 0 1 2 17.246v-7.5a3.75 3.75 0 0 1 3.75-3.75h1.166L5.775 4.855a1.25 1.25 0 1 1 1.767-1.768l2.652 2.652c.079.079.145.165.198.256h3.213c.053-.09.119-.177.198-.256l2.652-2.652a1.25 1.25 0 0 1 1.768 0zM10.25 8.246h-4.5a1.75 1.75 0 0 0-1.75 1.75v7.5c0 .966.784 1.75 1.75 1.75h12.5a1.75 1.75 0 0 0 1.75-1.75v-7.5a1.75 1.75 0 0 0-1.75-1.75h-4.5a.75.75 0 0 0-.75.75v.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75v-.5a.75.75 0 0 0-.75-.75zM7 12.246a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75v-4.5zm7 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75v-4.5z"/></svg>`,
            url: 'https://search.bilibili.com/all?keyword=',
            param: '$s&order=pubdate',
            color: '#00A1D6'
        } ,
        {
            id: 'youtube',
            name: 'YouTube',
            icon: `<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M10 15l5.19-3L10 9v6m11.56-7.83c.13.47.22 1.1.28 1.9.07.8.1 1.49.1 2.09L22 12c0 2.19-.16 3.8-.44
            4.83-.25.9-.83 1.48-1.73 1.73-.47.13-1.33.22-2.65.28-1.3.07-2.49.1-3.59.1L12 19c-4.19 0-6.8-.16-7.83-.44-.9-.25-1.48-.83-1.73-1.73-.13-.47-.22-1.1-.28-1.9-.07-.8-.1-1.49-.1-2.09L2
            12c0-2.19.16-3.8.44-4.83.25-.9.83-1.48 1.73-1.73.47-.13 1.33.22 2.65-.28 1.3-.07 2.49-.1 3.59-.1L12 5c4.19 0 6.8.16 7.83.44.9.25 1.48.83 1.73 1.73"/></svg>`,
            url: 'https://www.youtube.com/results?search_query=', // Note: This seems to be a specific non-standard URL. Kept as is.
            param: 'League of Legends skin $s',
            color: '#FF0000'
        }
    ];

    /**
     * 用户设置
     */
    const Settings = {
        getEnabledPlatforms: function() {
            const savedPlatforms = GM_getValue('enabledPlatforms');
            if (!savedPlatforms) {
                return searchPlatforms.map(p => p.id);
            }
            return savedPlatforms;
        },
        saveEnabledPlatforms: function(platforms) {
            GM_setValue('enabledPlatforms', platforms);
        },
        getLastUsedPlatform: function() {
            return GM_getValue('lastUsedPlatform', searchPlatforms[0].id);
        },
        saveLastUsedPlatform: function(platformId) {
            GM_setValue('lastUsedPlatform', platformId);
        }
    };

    /**
     * UI生成器
     */
    const UI = {
        createSearchIcon: function() {
            const icon = document.createElement('div');
            icon.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
                    <path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
                </svg>
            `;
            icon.className = 'lol-search-icon';
            icon.title = '搜索此皮肤';
            return icon;
        },
        createPlatformSelector: function(skinName) { // skinName is not directly used here now but kept for API consistency
            const selector = document.createElement('div');
            selector.className = 'search-platform-selector';
            const enabledPlatforms = Settings.getEnabledPlatforms();
            const lastUsedPlatform = Settings.getLastUsedPlatform();

            searchPlatforms.forEach(platform => {
                if (enabledPlatforms.includes(platform.id)) {
                    const button = document.createElement('button');
                    button.className = 'platform-button';
                    button.dataset.platformId = platform.id;
                    button.innerHTML = `
                        <span class="platform-icon" style="color: ${platform.color}">${platform.icon}</span>
                        <span class="platform-name">${platform.name}</span>
                    `;
                    if (platform.id === lastUsedPlatform) {
                        button.classList.add('last-used');
                    }
                    selector.appendChild(button);
                }
            });

            const settingsButton = document.createElement('button');
            settingsButton.className = 'settings-button';
            settingsButton.innerHTML = `
                <span class="platform-icon">
                    <svg viewBox="0 0 24 24" width="16" height="16">
                        <path fill="currentColor" d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" />
                    </svg>
                </span>
                <span class="platform-name">设置</span>
            `;
            selector.appendChild(settingsButton);
            return selector;
        },
        createSettingsPanel: function() {
            const panel = document.createElement('div');
            panel.className = 'search-settings-panel';
            panel.style.display = 'none';
            const header = document.createElement('div');
            header.className = 'settings-header';
            header.innerHTML = '<h3>搜索平台设置</h3>';
            panel.appendChild(header);
            const content = document.createElement('div');
            content.className = 'settings-content';
            const enabledPlatforms = Settings.getEnabledPlatforms();
            searchPlatforms.forEach(platform => {
                const checkboxContainer = document.createElement('div');
                checkboxContainer.className = 'checkbox-container';
                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.id = `platform-${platform.id}`;
                checkbox.checked = enabledPlatforms.includes(platform.id);
                checkbox.dataset.platformId = platform.id;
                const label = document.createElement('label');
                label.htmlFor = `platform-${platform.id}`;
                label.innerHTML = `
                    <span class="platform-icon" style="color: ${platform.color}">${platform.icon}</span>
                    ${platform.name}
                `;
                checkboxContainer.appendChild(checkbox);
                checkboxContainer.appendChild(label);
                content.appendChild(checkboxContainer);
            });
            panel.appendChild(content);
            const footer = document.createElement('div');
            footer.className = 'settings-footer';
            const saveButton = document.createElement('button');
            saveButton.className = 'save-settings-button';
            saveButton.textContent = '保存设置';
            saveButton.addEventListener('click', function() {
                const enabled = Array.from(
                    panel.querySelectorAll('input[type="checkbox"]:checked')
                ).map(checkbox => checkbox.dataset.platformId);
                if (enabled.length === 0) {
                    alert('请至少启用一个搜索平台');
                    return;
                }
                Settings.saveEnabledPlatforms(enabled);
                panel.style.display = 'none';
                refreshPlatformSelectors();
                Logger.info('已保存平台设置:', enabled);
            });
            const cancelButton = document.createElement('button');
            cancelButton.className = 'cancel-settings-button';
            cancelButton.textContent = '取消';
            cancelButton.addEventListener('click', function() {
                panel.style.display = 'none';
            });
            footer.appendChild(saveButton);
            footer.appendChild(cancelButton);
            panel.appendChild(footer);
            return panel;
        }
    };

    /**
     * 搜索处理器
     */
    const SearchHandler = {
        configureSearchIcon: function(searchIcon, skinName, imgBox) {
            let selector = null;
            let isOpen = false;
            searchIcon.addEventListener('click', function(event) {
                event.stopPropagation();
                if (!selector) {
                    selector = UI.createPlatformSelector(skinName); // skinName passed for consistency, not used by current createPlatformSelector
                    imgBox.appendChild(selector); // Appending selector to imgBox
                    selector.querySelectorAll('.platform-button').forEach(button => {
                        button.addEventListener('click', function(e) {
                            e.stopPropagation();
                            const platformId = button.dataset.platformId;
                            const platform = searchPlatforms.find(p => p.id === platformId);
                            if (platform) {
                                const searchQuery = skinName.replace(/\s+/g, ' ').trim();
                                const searchUrl = platform.url +
                                    encodeURIComponent(platform.param.replace('$s', searchQuery));
                                Settings.saveLastUsedPlatform(platformId);
                                window.open(searchUrl, '_blank');
                                Logger.debug('执行搜索:', platform.name, searchQuery, searchUrl);
                                selector.style.display = 'none';
                                isOpen = false;
                            }
                        });
                    });
                    const settingsButton = selector.querySelector('.settings-button');
                    settingsButton.addEventListener('click', function(e) {
                        e.stopPropagation();
                        let settingsPanel = document.querySelector('.search-settings-panel');
                        if (!settingsPanel) {
                            settingsPanel = UI.createSettingsPanel();
                            document.body.appendChild(settingsPanel);
                        }
                        const rect = settingsButton.getBoundingClientRect();
                        const viewportWidth = window.innerWidth;
                        const viewportHeight = window.innerHeight;
                        const panelWidth = settingsPanel.offsetWidth || 300; // Use actual or assumed width
                        const panelHeight = settingsPanel.offsetHeight || 400; // Use actual or assumed height

                        let left = rect.right + 10;
                        if (left + panelWidth > viewportWidth && rect.left - panelWidth - 10 >=0) { // Check if space on left
                            left = rect.left - panelWidth - 10;
                        } else if (left + panelWidth > viewportWidth) { // Default to left if right overflows and left also would
                             left = Math.max(0, viewportWidth - panelWidth - 5);
                        }


                        let top = rect.top;
                        if (top + panelHeight > viewportHeight) {
                            top = Math.max(0, viewportHeight - panelHeight - 10);
                        }

                        settingsPanel.style.left = `${Math.max(0,left)}px`; // Ensure not off-screen left
                        settingsPanel.style.top = `${Math.max(0,top)}px`;   // Ensure not off-screen top
                        settingsPanel.style.display = 'block';
                        selector.style.display = 'none';
                        isOpen = false;
                    });
                }
                isOpen = !isOpen;
                selector.style.display = isOpen ? 'block' : 'none';
                Logger.debug('切换搜索选择器显示状态:', isOpen);
            });
            // Using a more robust way to close by checking if click is outside searchIcon and its generated selector
            document.addEventListener('click', function(e) {
                if (isOpen && selector && !searchIcon.contains(e.target) && !selector.contains(e.target)) {
                    selector.style.display = 'none';
                    isOpen = false;
                }
            });
        }
    };

    /**
     * 皮肤处理器 - MODIFIED SECTION
     */
    const SkinProcessor = {
        // Define configurations for different skin item structures
        skinItemConfigs: [
            { // Original structure (based on original script logic for 'li' items)
                itemSelector: 'li', // Selector for the main skin item container
                imgBoxSelector: '.img-box', // Selector for the image container within the item
                // Function to extract skin name from the item
                getSkinName: function(itemElement) {
                    const pElement = itemElement.querySelector('p');
                    // Ensure this 'p' is not confused with other 'p' elements like '.icon'
                    if (pElement && !pElement.classList.contains('icon') && pElement.textContent) {
                        return pElement.textContent.trim();
                    }
                    return null;
                }
            },
            { // New structure from user's HTML snippet for 'div.skin-box' items
                itemSelector: 'div.skin-box',
                imgBoxSelector: '.img-box', // This is consistent
                getSkinName: function(itemElement) {
                    // Skin name is in the alt attribute of an image inside .img-box
                    const imgElement = itemElement.querySelector('.img-box img.pop-skin-img[alt]');
                    return imgElement ? imgElement.getAttribute('alt').trim() : null;
                }
            }
            // Add more configurations here for future HTML structures:
            // {
            //     itemSelector: 'selector-for-another-skin-container',
            //     imgBoxSelector: 'selector-for-its-image-box',
            //     getSkinName: function(itemElement) { /* logic to get name */ return 'skin name'; }
            // }
        ],

        processSkinItems: function() {
            Logger.debug('开始处理皮肤项 (Configurable Strategy)');
            let processedCountThisRun = 0;

            this.skinItemConfigs.forEach(config => {
                const skinItems = document.querySelectorAll(config.itemSelector);

                skinItems.forEach(item => {
                    // Check if the specific imgBox for this item already has a search icon.
                    // This is the most reliable check to prevent reprocessing.
                    const imgBox = item.querySelector(config.imgBoxSelector);

                    if (imgBox && imgBox.querySelector('.lol-search-icon')) {
                        return; // Already processed
                    }

                    // If imgBox exists, try to get skin name
                    if (imgBox) {
                        const skinName = config.getSkinName(item);

                        if (skinName) {
                            Logger.debug('处理皮肤:', skinName, '- 来自项目:', item, '- 使用配置:', config.itemSelector);

                            const currentPosition = getComputedStyle(imgBox).position;
                            if (currentPosition !== 'relative' && currentPosition !== 'absolute') {
                                imgBox.style.position = 'relative';
                                Logger.debug('设置 imgBox position 为 relative:', imgBox);
                            }

                            const searchIcon = UI.createSearchIcon();
                            imgBox.appendChild(searchIcon);
                            SearchHandler.configureSearchIcon(searchIcon, skinName, imgBox);

                            processedCountThisRun++;
                        } else {
                            // Logger.debug('无法从此项目获取皮肤名称:', item, '使用配置:', config.itemSelector);
                        }
                    } else {
                        // Logger.debug('未找到 imgBox for item:', item, '使用配置:', config.itemSelector, 'imgBoxSelector:', config.imgBoxSelector);
                    }
                });
            });

            if (processedCountThisRun > 0) {
                Logger.info(`本次动态/初始化处理完成, 新增处理了 ${processedCountThisRun} 个皮肤项`);
            }
            return processedCountThisRun;
        }
    };
    // END OF MODIFIED SECTION


    /**
     * 刷新所有平台选择器以反映新设置
     */
    function refreshPlatformSelectors() {
        document.querySelectorAll('.search-platform-selector').forEach(selector => {
            selector.remove();
        });
        Logger.debug('已刷新平台选择器 (下次点击图标时将重建)');
    }

    // 添加样式 (GM_addStyle) - NO CHANGES HERE, kept as is from original
    GM_addStyle(`
        /* 搜索图标样式 */
        .lol-search-icon {
            position: absolute;
            top: 5px;
            right: 5px;
            cursor: pointer;
            z-index: 10; /* Ensure it's above the image */
            background-color: rgba(255, 255, 255, 0.8);
            border-radius: 50%;
            padding: 5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
            transition: transform 0.2s, background-color 0.2s;
            display: flex; /* For better centering of SVG if needed */
            align-items: center;
            justify-content: center;
        }

       .lol-search-icon:hover {
            transform: scale(1.1);
            background-color: rgba(255, 255, 255, 0.95);
        }

        /* 平台选择器样式 */
        .search-platform-selector {
            position: absolute;
            top: 35px; /* Adjusted based on icon size + padding */
            right: 5px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            padding: 8px;
            z-index: 100; /* Above search icon and other elements */
            min-width: 150px;
            display: none; /* Initially hidden */
        }

        /* 平台按钮样式 */
        .platform-button, .settings-button {
            display: flex;
            align-items: center;
            padding: 8px 12px;
            width: 100%;
            border: none;
            background-color: transparent;
            cursor: pointer;
            border-radius: 4px;
            margin-bottom: 2px; /* Small gap between buttons */
            transition: background-color 0.2s;
            text-align: left; /* Ensure text aligns left */
            box-sizing: border-box; /* Better width calculation */
        }

        .platform-button:hover, .settings-button:hover {
            background-color: #f0f0f0;
        }

        .platform-button.last-used {
            background-color: #e8f0fe; /* A slightly different highlight for last used */
            font-weight: bold;
        }

        .platform-icon {
            margin-right: 8px;
            display: flex;
            align-items: center;
        }

        .platform-name {
            font-size: 14px;
            color: #333;
        }

        /* 设置面板样式 */
        .search-settings-panel {
            position: fixed; /* Fixed position relative to viewport */
            width: 300px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
            z-index: 1000; /* Highest z-index */
            overflow: hidden;
            display: none; /* Initially hidden */
        }

        .settings-header {
            padding: 15px;
            border-bottom: 1px solid #eee;
        }

        .settings-header h3 {
            margin: 0;
            font-size: 16px;
            color: #333;
        }

        .settings-content {
            padding: 15px;
            max-height: 300px; /* Limit height and allow scrolling */
            overflow-y: auto;
        }

        .checkbox-container {
            display: flex;
            align-items: center;
            margin-bottom: 12px;
        }

        .checkbox-container input[type="checkbox"] {
            margin-right: 10px; /* More space */
            width: 16px; /* Custom size */
            height: 16px;
            cursor: pointer;
        }

        .checkbox-container label {
            display: flex;
            align-items: center;
            cursor: pointer;
            font-size: 14px;
            color: #333;
        }
         .checkbox-container label .platform-icon { /* Ensure icon in label also has margin */
            margin-right: 8px;
        }


        .settings-footer {
            padding: 15px;
            border-top: 1px solid #eee;
            display: flex;
            justify-content: flex-end; /* Align buttons to the right */
            background-color: #f9f9f9; /* Slight background for footer */
        }

        .settings-footer button {
            padding: 8px 15px; /* More padding for footer buttons */
            border-radius: 4px;
            border: none;
            cursor: pointer;
            font-size: 14px;
            margin-left: 10px;
            transition: background-color 0.2s, box-shadow 0.2s;
        }

        .save-settings-button {
            background-color: #1a73e8; /* Google blue */
            color: white;
        }

        .save-settings-button:hover {
            background-color: #0d66d0; /* Darker blue */
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }

        .cancel-settings-button {
            background-color: #f1f1f1;
            color: #333;
            border: 1px solid #ddd; /* Subtle border */
        }

        .cancel-settings-button:hover {
            background-color: #e4e4e4;
            border-color: #ccc;
        }

        /* Responsive adjustments */
        @media (max-width: 768px) {
            .platform-name {
                font-size: 12px; /* Smaller font on small screens */
            }
            .search-settings-panel {
                width: 250px; /* Narrower panel on small screens */
                max-width: 90vw;
            }
            .settings-content {
                max-height: 250px;
            }
        }
    `);

    /**
     * 初始化函数
     */
    function initialize() {
        Logger.info('初始化脚本');

        const initialProcessedCount = SkinProcessor.processSkinItems();
        // Logger.info(`初始化完成,初次处理了 ${initialProcessedCount} 个皮肤项`); // Covered by processSkinItems log

        const observer = new MutationObserver(mutations => {
            // Check if any added nodes could be skin items or contain them
            const relevantMutation = mutations.some(mutation => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // Check if the added node itself matches any itemSelector or might contain them
                            if (SkinProcessor.skinItemConfigs.some(config => node.matches && node.matches(config.itemSelector) || node.querySelector(config.itemSelector))) {
                                return true;
                            }
                        }
                    }
                }
                return false;
            });

            if (relevantMutation) {
                Logger.debug('检测到DOM变化,可能包含新的皮肤项,重新处理...');
                const newProcessedCount = SkinProcessor.processSkinItems();
                // if (newProcessedCount > 0) { // Log is now inside processSkinItems
                //     Logger.debug(`动态加载处理了 ${newProcessedCount} 个新皮肤项`);
                // }
            }
        });

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

        // Global click listener for closing settings panel
        document.addEventListener('click', function(e) {
            const settingsPanel = document.querySelector('.search-settings-panel');
            if (settingsPanel && settingsPanel.style.display !== 'none') {
                const isClickInsidePanel = settingsPanel.contains(e.target);
                // Check if click was on any settings button that might open the panel
                const isClickOnAOpenerButton = e.target.closest('.settings-button');

                if (!isClickInsidePanel && !isClickOnAOpenerButton) {
                    Logger.debug('Clicked outside settings panel and not on an opener, closing panel.');
                    settingsPanel.style.display = 'none';
                } else if (!isClickInsidePanel && isClickOnAOpenerButton) {
                     // If it was a settings button, SearchHandler.configureSearchIcon already handled it.
                     // This case means the click was on a settings button that *didn't* belong to an active selector,
                     // which is unlikely to happen if the panel is already open.
                }
            }
        });
    }

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

    // 提供一个全局接口以便调试
    window.LOLSkinSearch = {
        refreshPlatformSelectors,
        searchPlatforms,
        Settings,
        processSkins: () => SkinProcessor.processSkinItems(), // Ensure 'this' context if needed, or direct call
        getSkinConfigs: () => SkinProcessor.skinItemConfigs // For debugging configs
    };

})();