表情符号助手 Pro (Emoji Helper Pro)

终极表情助手

// ==UserScript==
// @name         表情符号助手 Pro (Emoji Helper Pro)
// @namespace    https://github.com/TechnologyStar/Emperor-Qin-Shi-Huang-Expression-Pack-Assistant
// @version      1.2.0
// @description  终极表情助手
// @author       TechnologyStar
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @connect        api.giphy.com
// @connect        tenor.googleapis.com
// @connect        media.tenor.com
// @connect        cdnjs.cloudflare.com
// @connect        cdn.jsdelivr.net
// @connect        unpkg.com

// ==/UserScript==

(function() {
    // 网站白名单检测 - 添加这部分代码
    const allowedSites = [
        'github.com',
        'linux.do',
        'reddit.com'
        // 在这里添加你想要启用的网站域名
    ];

    const currentHost = window.location.hostname;
    const isAllowed = allowedSites.some(site =>
                                        currentHost === site || currentHost.endsWith('.' + site)
                                       );

    if (!isAllowed) {
        console.log('表情助手:当前网站不在允许列表中');
        return; // 退出脚本执行
    }
    // 网站检测结束
    'use strict';

    // 防止在iframe中重复执行
    if (window.top !== window.self) return;

    // 防止重复加载
    if (window.EmojiHelperProLoaded) {
        console.warn('EmojiHelper Pro 已加载,跳过重复初始化');
        return;
    }
    window.EmojiHelperProLoaded = true;

    // 🚀 详细日志系统
    const Logger = {
        levels: {
            ERROR: 0,
            WARN: 1,
            INFO: 2,
            DEBUG: 3,
            TRACE: 4
        },

        categories: {
            STORAGE: { name: 'Storage', color: '#4CAF50', emoji: '💾' },
            CONFIG: { name: 'Config', color: '#2196F3', emoji: '⚙️' },
            CLOUD: { name: 'CloudData', color: '#FF9800', emoji: '☁️' },
            SEARCH: { name: 'Search', color: '#9C27B0', emoji: '🔍' },
            UI: { name: 'UI', color: '#00BCD4', emoji: '🎨' },
            EVENT: { name: 'Event', color: '#FF5722', emoji: '🎯' },
            CACHE: { name: 'Cache', color: '#795548', emoji: '🗂️' },
            UPDATE: { name: 'Update', color: '#607D8B', emoji: '🔄' },
            INIT: { name: 'Init', color: '#E91E63', emoji: '🚀' },
            ERROR: { name: 'Error', color: '#F44336', emoji: '❌' }
        },

        currentLevel: 3,
        history: [],
        maxHistorySize: 500,

        _log(level, category, message, data = null) {
            if (level > this.currentLevel) return;

            const timestamp = new Date().toISOString();
            const cat = this.categories[category] || this.categories.INFO;
            const levelNames = ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'];
            const levelName = levelNames[level];

            const logEntry = {
                timestamp,
                level: levelName,
                category: cat.name,
                message,
                data: data ? this._safeClone(data) : null
            };

            this.history.push(logEntry);
            if (this.history.length > this.maxHistorySize) {
                this.history.shift();
            }

            const consoleMessage = `%c${cat.emoji} [${cat.name}] %c${message}`;
            const styles = [
                `color: ${cat.color}; font-weight: bold;`,
                'color: inherit; font-weight: normal;'
            ];

            switch(level) {
                case this.levels.ERROR:
                    console.error(consoleMessage, ...styles, data);
                    break;
                case this.levels.WARN:
                    console.warn(consoleMessage, ...styles, data);
                    break;
                case this.levels.INFO:
                    console.info(consoleMessage, ...styles, data);
                    break;
                default:
                    console.log(consoleMessage, ...styles, data);
                    break;
            }
        },

        _safeClone(obj) {
            try {
                return JSON.parse(JSON.stringify(obj));
            } catch {
                return String(obj);
            }
        },

        error(category, message, data) { this._log(this.levels.ERROR, category, message, data); },
        warn(category, message, data) { this._log(this.levels.WARN, category, message, data); },
        info(category, message, data) { this._log(this.levels.INFO, category, message, data); },
        debug(category, message, data) { this._log(this.levels.DEBUG, category, message, data); },
        trace(category, message, data) { this._log(this.levels.TRACE, category, message, data); },

        setLevel(level) {
            this.currentLevel = typeof level === 'string' ? this.levels[level.toUpperCase()] : level;
            this.info('CONFIG', `日志级别设置为: ${Object.keys(this.levels)[this.currentLevel]}`);
        },

        getHistory(category = null, level = null) {
            let filtered = this.history;

            if (category) {
                filtered = filtered.filter(log => log.category === category);
            }

            if (level) {
                const levelValue = typeof level === 'string' ? this.levels[level.toUpperCase()] : level;
                filtered = filtered.filter(log => this.levels[log.level] === levelValue);
            }

            return filtered;
        },

        clearHistory() {
            const count = this.history.length;
            this.history = [];
            this.info('CONFIG', `清理了 ${count} 条日志记录`);
        },

        exportLogs() {
            try {
                const logs = JSON.stringify(this.history, null, 2);
                const blob = new Blob([logs], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `emoji-helper-logs-${new Date().toISOString().split('T')[0]}.json`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
                this.info('CONFIG', '日志已导出');
            } catch (error) {
                this.error('CONFIG', '日志导出失败', error);
            }
        }
    };

    // 🔥 极简存储管理器 - 三重保险
    const Storage = {
        prefix: 'EmojiHelper_',
        memCache: new Map(),

        get(key, defaultVal) {
            const fullKey = this.prefix + key;

            // 先检查内存缓存
            if (this.memCache.has(key)) {
                Logger.trace('STORAGE', `内存缓存读取 ${key}`, this.memCache.get(key));
                return this.memCache.get(key);
            }

            try {
                const val = GM_getValue(fullKey);
                if (val !== undefined) {
                    this.memCache.set(key, val);
                    Logger.trace('STORAGE', `GM读取 ${key}`, val);
                    return val;
                }
            } catch(e) {
                Logger.warn('STORAGE', `GM读取失败: ${key}`, e.message);
            }

            try {
                const val = localStorage.getItem(fullKey);
                if (val !== null) {
                    const parsed = JSON.parse(val);
                    this.memCache.set(key, parsed);
                    Logger.trace('STORAGE', `localStorage读取 ${key}`, parsed);
                    return parsed;
                }
            } catch(e) {
                Logger.warn('STORAGE', `localStorage读取失败: ${key}`, e.message);
            }

            Logger.debug('STORAGE', `使用默认值 ${key}`, defaultVal);
            return defaultVal;
        },

        set(key, value) {
            const fullKey = this.prefix + key;
            let saved = false;

            // 更新内存缓存
            this.memCache.set(key, value);

            try {
                GM_setValue(fullKey, value);
                if (GM_getValue(fullKey) === value) {
                    Logger.trace('STORAGE', `GM保存成功 ${key}`, value);
                    saved = true;
                }
            } catch(e) {
                Logger.warn('STORAGE', `GM保存失败: ${key}`, e.message);
            }

            try {
                localStorage.setItem(fullKey, JSON.stringify(value));
                if (!saved) {
                    Logger.trace('STORAGE', `localStorage保存 ${key}`, value);
                }
            } catch(e) {
                Logger.warn('STORAGE', `localStorage保存失败: ${key}`, e.message);
            }

            return true;
        },

        clearAll() {
            const keys = [];

            // 清理GM存储
            try {
                // 获取所有GM存储的键
                const gmKeys = [];
                for (let i = 0; i < 200; i++) {
                    const key = this.prefix + i;
                    if (GM_getValue(key) !== undefined) {
                        GM_setValue(key, undefined);
                        gmKeys.push(key);
                    }
                }
                keys.push(...gmKeys);
            } catch(e) {
                Logger.warn('STORAGE', 'GM清理失败', e.message);
            }

            // 清理localStorage
            try {
                for (let i = localStorage.length - 1; i >= 0; i--) {
                    const key = localStorage.key(i);
                    if (key && key.startsWith(this.prefix)) {
                        localStorage.removeItem(key);
                        keys.push(key);
                    }
                }
            } catch(e) {
                Logger.warn('STORAGE', 'localStorage清理失败', e.message);
            }

            // 清理内存缓存
            this.memCache.clear();

            Logger.info('STORAGE', `清理了 ${keys.length} 个存储项`);
            return keys.length;
        }
    };

    // 🎯 配置管理器
    const Config = {
        defaults: {
            lang: 'zh-CN',
            theme: 'light',
            autoInsert: true,
            gifSize: 'medium',
            searchEngine: 'giphy',
            dataVersion: '1.0',
            autoUpdate: true,
            lastUpdateCheck: 0,
            showFloatingButton: true,
            panelPosition: { x: 20, y: 86 },
            settingsPanelPosition: { x: 450, y: 86 },
            editorPosition: { x: 'center', y: 'center' },
            logLevel: 'WARN',
            customUpdateUrl: 'https://raw.githubusercontent.com/TechnologyStar/Emperor-Qin-Shi-Huang-Expression-Pack-Assistant/refs/heads/main/neo.json',
            enableDetailedLogs: true,
            cacheSize: 100
        },

        cache: {},

        init() {
            Object.keys(this.defaults).forEach(key => {
                this.cache[key] = Storage.get(key, this.defaults[key]);
            });

            Logger.setLevel(this.cache.logLevel);
            Logger.info('CONFIG', '配置初始化完成', this.cache);
        },

        get(key) {
            return this.cache[key];
        },

        set(key, value) {
            const oldValue = this.cache[key];
            if (oldValue === value) return false;

            this.cache[key] = value;
            Storage.set(key, value);

            Logger.debug('CONFIG', `配置更新 ${key}: ${oldValue} -> ${value}`);
            this.onConfigChange(key, value, oldValue);
            return true;
        },

        onConfigChange(key, newValue, oldValue) {
            try {
                switch(key) {
                    case 'theme':
                        applyTheme();
                        break;
                    case 'lang':
                        updateAllText();
                        break;
                    case 'gifSize':
                        refreshCurrentView();
                        break;
                    case 'searchEngine':
                        clearGifCache();
                        break;
                    case 'showFloatingButton':
                        updateFloatingButtonVisibility();
                        break;
                    case 'panelPosition':
                        updatePanelPosition();
                        break;
                    case 'settingsPanelPosition':
                        updateSettingsPanelPosition();
                        break;
                    case 'logLevel':
                        Logger.setLevel(newValue);
                        break;
                    case 'customUpdateUrl':
                        Logger.info('CONFIG', '更新源地址已修改', newValue);
                        break;
                }
            } catch (error) {
                Logger.error('CONFIG', '配置变更处理失败', { key, newValue, error });
            }
        },

        reset() {
            Logger.info('CONFIG', '重置所有配置');
            Object.keys(this.defaults).forEach(key => {
                this.set(key, this.defaults[key]);
            });
            showMessage('设置已重置');
        }
    };

    // 🗂️ 缓存管理器
    const CacheManager = {
        cache: new Map(),

        set(key, value, category = 'default') {
            const cacheKey = `${category}:${key}`;
            const cacheEntry = {
                value,
                timestamp: Date.now(),
                category
            };

            this.cache.set(cacheKey, cacheEntry);
            Logger.trace('CACHE', `缓存设置: ${cacheKey}`, value);

            this.checkCacheLimit();
        },

        get(key, category = 'default') {
            const cacheKey = `${category}:${key}`;
            const entry = this.cache.get(cacheKey);

            if (entry) {
                Logger.trace('CACHE', `缓存命中: ${cacheKey}`);
                return entry.value;
            }

            Logger.trace('CACHE', `缓存未命中: ${cacheKey}`);
            return null;
        },

        has(key, category = 'default') {
            const cacheKey = `${category}:${key}`;
            return this.cache.has(cacheKey);
        },

        delete(key, category = 'default') {
            const cacheKey = `${category}:${key}`;
            const deleted = this.cache.delete(cacheKey);
            if (deleted) {
                Logger.debug('CACHE', `缓存删除: ${cacheKey}`);
            }
            return deleted;
        },

        clear(category = null) {
            if (category) {
                const keysToDelete = [];
                for (const [key, entry] of this.cache.entries()) {
                    if (entry.category === category) {
                        keysToDelete.push(key);
                    }
                }
                keysToDelete.forEach(key => this.cache.delete(key));
                Logger.info('CACHE', `清理分类缓存: ${category}, 删除 ${keysToDelete.length} 项`);
            } else {
                const size = this.cache.size;
                this.cache.clear();
                Logger.info('CACHE', `清理所有缓存, 删除 ${size} 项`);
            }
        },

        checkCacheLimit() {
            const limit = Config.get('cacheSize');
            if (this.cache.size > limit) {
                const entries = Array.from(this.cache.entries());
                entries.sort((a, b) => a[1].timestamp - b[1].timestamp);

                const deleteCount = this.cache.size - limit + 10;
                for (let i = 0; i < deleteCount && i < entries.length; i++) {
                    this.cache.delete(entries[i][0]);
                }

                Logger.debug('CACHE', `缓存大小超限,删除了 ${deleteCount} 个最旧条目`);
            }
        },

        getStats() {
            const stats = {
                totalSize: this.cache.size,
                categories: {}
            };

            for (const [key, entry] of this.cache.entries()) {
                if (!stats.categories[entry.category]) {
                    stats.categories[entry.category] = 0;
                }
                stats.categories[entry.category]++;
            }

            return stats;
        }
    };

    // 🌐 多语言
    const i18n = {
        'zh-CN': {
            title: '表情助手',
            settings: '设置',
            search: '搜索表情、GIF...',
            searchBtn: '搜索',
            categories: {
                all: '全部',
                custom: '我的GIF',
                smileys: '表情符号',
                webGif: '网络GIF'
            },
            settingsPanel: {
                title: '设置面板',
                language: '界面语言',
                theme: '主题',
                autoInsert: '选择后自动关闭面板',
                gifSize: 'GIF显示尺寸',
                searchEngine: 'GIF搜索引擎',
                close: '关闭',
                reset: '重置设置',
                dataVersion: '数据版本',
                autoUpdate: '自动更新数据',
                updateNow: '立即更新',
                lastUpdate: '上次更新',
                showFloatingButton: '显示浮动按钮',
                logLevel: '日志级别',
                customUpdateUrl: '自定义更新源',
                enableDetailedLogs: '启用详细日志',
                cacheSize: '缓存大小限制',
                clearCache: '清理缓存',
                clearAllData: '清理所有数据',
                exportLogs: '导出日志',
                advanced: '高级设置'
            },
            textEditor: {
                title: '文字编辑器',
                addText: '添加文字',
                text: '文字内容',
                textPlaceholder: '输入你想添加的文字...',
                fontSize: '字体大小',
                fontFamily: '字体类型',
                textColor: '文字颜色',
                position: '文字位置',
                positions: {
                    top: '顶部',
                    center: '居中',
                    bottom: '底部'
                },
                generate: '复制图片',
                download: '下载',
                close: '关闭',
                dragHint: '可拖拽到任意位置使用',
                copyHint: '右键复制图片也可使用'
            },
            themes: {
                light: '浅色',
                dark: '深色'
            },
            sizes: {
                small: '小',
                medium: '中',
                large: '大'
            },
            logLevels: {
                ERROR: '错误',
                WARN: '警告',
                INFO: '信息',
                DEBUG: '调试',
                TRACE: '追踪'
            },
            messages: {
                copied: '已复制到剪贴板!',
                noResults: '没有找到相关内容',
                searching: '搜索中...',
                settingsSaved: '设置已保存!',
                settingsReset: '设置已重置!',
                searchHint: '输入关键词搜索GIF',
                apiError: '网络搜索失败,请稍后重试',
                updateSuccess: '数据更新成功!',
                updateFailed: '数据更新失败',
                updateChecking: '检查更新中...',
                noUpdate: '已是最新版本',
                imageGenerated: '图片生成成功!可拖拽或右键复制使用',
                imageError: '图片生成失败',
                cacheCleared: '缓存已清理',
                dataCleared: '所有数据已清理',
                logsExported: '日志已导出',
                invalidUpdateUrl: '更新源地址无效'
            }
        },
        'en': {
            title: 'Emoji Helper',
            settings: 'Settings',
            search: 'Search emoji, GIF...',
            searchBtn: 'Search',
            categories: {
                all: 'All',
                custom: 'My GIFs',
                smileys: 'Emojis',
                webGif: 'Web GIFs'
            },
            settingsPanel: {
                title: 'Settings Panel',
                language: 'Language',
                theme: 'Theme',
                autoInsert: 'Auto close after selection',
                gifSize: 'GIF display size',
                searchEngine: 'GIF search engine',
                close: 'Close',
                reset: 'Reset Settings',
                dataVersion: 'Data Version',
                autoUpdate: 'Auto Update Data',
                updateNow: 'Update Now',
                lastUpdate: 'Last Update',
                showFloatingButton: 'Show Floating Button',
                logLevel: 'Log Level',
                customUpdateUrl: 'Custom Update URL',
                enableDetailedLogs: 'Enable Detailed Logs',
                cacheSize: 'Cache Size Limit',
                clearCache: 'Clear Cache',
                clearAllData: 'Clear All Data',
                exportLogs: 'Export Logs',
                advanced: 'Advanced Settings'
            },
            textEditor: {
                title: 'Text Editor',
                addText: 'Add Text',
                text: 'Text Content',
                textPlaceholder: 'Enter text to add...',
                fontSize: 'Font Size',
                fontFamily: 'Font Family',
                textColor: 'Text Color',
                position: 'Text Position',
                positions: {
                    top: 'Top',
                    center: 'Center',
                    bottom: 'Bottom'
                },
                generate: 'Copy Image',
                download: 'Download',
                close: 'Close',
                dragHint: 'Draggable to any position',
                copyHint: 'Right-click copy image also works'
            },
            themes: {
                light: 'Light',
                dark: 'Dark'
            },
            sizes: {
                small: 'Small',
                medium: 'Medium',
                large: 'Large'
            },
            logLevels: {
                ERROR: 'Error',
                WARN: 'Warning',
                INFO: 'Info',
                DEBUG: 'Debug',
                TRACE: 'Trace'
            },
            messages: {
                copied: 'Copied to clipboard!',
                noResults: 'No results found',
                searching: 'Searching...',
                settingsSaved: 'Settings saved!',
                settingsReset: 'Settings reset!',
                searchHint: 'Enter keywords to search GIFs',
                apiError: 'Network search failed, please try again',
                updateSuccess: 'Data updated successfully!',
                updateFailed: 'Data update failed',
                updateChecking: 'Checking for updates...',
                noUpdate: 'Already up to date',
                imageGenerated: 'Image generated successfully! Draggable or right-click to copy',
                imageError: 'Image generation failed',
                cacheCleared: 'Cache cleared',
                dataCleared: 'All data cleared',
                logsExported: 'Logs exported',
                invalidUpdateUrl: 'Invalid update URL'
            }
        }
    };

    const t = () => i18n[Config.get('lang')] || i18n['zh-CN'];

    // 全局变量
    let emojiPanel = null;
    let settingsPanel = null;
    let textEditorPanel = null;
    let floatingButton = null;
    let webGifCache = new Map();
    let isSearching = false;
    let searchRequestId = 0;
    let currentEditingImage = null;

    // 拖拽相关变量
    let isDragging = false;
    let dragOffset = { x: 0, y: 0 };
    let currentDragElement = null;

    // 默认自定义GIF(本地备份)
    let customGifs = [
        {
            name: 'cat-wave',
            url: 'https://file.woodo.cn/upload/image/201910/25/c7eb21a4-7693-4836-b23a-5ab3c9e1813d.gif',
            alt: '招手猫',
            keywords: ['猫', '招手', 'cat', 'wave', 'hello', '你好', '嗨']
        },
        {
            name: 'hello-cat',
            url: 'https://c-ssl.duitang.com/uploads/item/202001/11/20200111042746_kmmjw.gif',
            alt: '你好猫',
            keywords: ['猫', '你好', 'cat', 'hello', 'hi', '问候', '打招呼']
        },
        {
            name: 'thumbs-up',
            url: 'https://media.giphy.com/media/111ebonMs90YLu/giphy.gif',
            alt: '点赞',
            keywords: ['点赞', '赞', '好', 'thumbs', 'up', 'good', 'nice', '棒']
        },
        {
            name: 'happy-dance',
            url: 'https://media.giphy.com/media/l0MYt5jPR6QX5pnqM/giphy.gif',
            alt: '开心舞蹈',
            keywords: ['开心', '舞蹈', '高兴', 'happy', 'dance', 'excited', 'party']
        }
    ];

    // 表情符号
    const defaultEmojis = ['😀', '😃', '😄', '😁', '😅', '😂', '🤣', '😊', '😇', '🙂', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '☹️', '😣', '😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡', '🤬', '🤯', '😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', '🤔', '🤭', '🤫', '🤥', '😶', '😐', '😑', '😬', '🙄', '😯', '😦', '😧', '😮', '😲', '🥱', '😴', '🤤', '😪', '😵', '🤐', '🥴', '🤢', '🤮', '🤧', '😷', '🤒', '🤕', '🤑', '🤠', '😈', '👿', '👹', '👺', '🤡', '💩', '👻', '💀', '☠️', '👽', '👾', '🤖', '🎃', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾'];

    // 🔄 云数据更新管理器
    const CloudDataManager = {
        getUpdateUrl() {
            return Config.get('customUpdateUrl');
        },

        validateUpdateUrl(url) {
            try {
                const urlObj = new URL(url);
                return ['https:', 'http:'].includes(urlObj.protocol);
            } catch {
                return false;
            }
        },

        async checkAndUpdate(forceUpdate = false) {
            try {
                Logger.info('UPDATE', '开始检查更新', { force: forceUpdate });

                if (!forceUpdate) {
                    if (!Config.get('autoUpdate')) {
                        Logger.info('UPDATE', '自动更新已禁用');
                        return false;
                    }

                    const lastCheck = Config.get('lastUpdateCheck');
                    const now = Date.now();
                    if (now - lastCheck < 24 * 60 * 60 * 1000) {
                        Logger.debug('UPDATE', '距离上次检查不足24小时', {
                            lastCheck: new Date(lastCheck).toLocaleString(),
                            nextCheck: new Date(lastCheck + 24 * 60 * 60 * 1000).toLocaleString()
                        });
                        return false;
                    }
                }

                const updateUrl = this.getUpdateUrl();
                if (!this.validateUpdateUrl(updateUrl)) {
                    Logger.error('UPDATE', '更新源地址无效', updateUrl);
                    if (forceUpdate) {
                        showMessage(t().messages.invalidUpdateUrl);
                    }
                    return false;
                }

                const cloudData = await this.fetchCloudData(updateUrl);
                if (!cloudData) {
                    Logger.warn('UPDATE', '获取云数据失败');
                    return false;
                }

                const cloudVersion = cloudData.version || '1.0';
                const localVersion = Config.get('dataVersion');

                Logger.info('UPDATE', '版本比较', {
                    local: localVersion,
                    cloud: cloudVersion,
                    updateUrl
                });

                if (forceUpdate || this.isNewerVersion(cloudVersion, localVersion)) {
                    await this.updateLocalData(cloudData);
                    Config.set('dataVersion', cloudVersion);
                    Config.set('lastUpdateCheck', Date.now());

                    showMessage(t().messages.updateSuccess);
                    Logger.info('UPDATE', '数据更新成功', {
                        oldVersion: localVersion,
                        newVersion: cloudVersion,
                        gifCount: cloudData.customGifs?.length || 0
                    });
                    return true;
                } else {
                    Config.set('lastUpdateCheck', Date.now());
                    if (forceUpdate) {
                        showMessage(t().messages.noUpdate);
                    }
                    Logger.info('UPDATE', '已是最新版本', { version: cloudVersion });
                    return false;
                }

            } catch (error) {
                Logger.error('UPDATE', '更新失败', error);
                if (forceUpdate) {
                    showMessage(t().messages.updateFailed);
                }
                return false;
            }
        },

        async fetchCloudData(url) {
            return new Promise((resolve, reject) => {
                Logger.debug('UPDATE', '开始获取云数据', url);

                const timeout = setTimeout(() => {
                    reject(new Error('请求超时'));
                }, 15000);

                GM_xmlhttpRequest({
                    method: 'GET',
                    url: url,
                    timeout: 15000,
                    onload: (response) => {
                        clearTimeout(timeout);
                        try {
                            Logger.debug('UPDATE', '云数据响应状态', response.status);

                            if (response.status === 200) {
                                const data = JSON.parse(response.responseText);
                                Logger.info('UPDATE', '获取云数据成功', {
                                    version: data.version,
                                    gifCount: data.customGifs?.length || 0,
                                    dataSize: response.responseText.length
                                });
                                resolve(data);
                            } else {
                                Logger.error('UPDATE', `HTTP错误: ${response.status}`);
                                reject(new Error(`HTTP ${response.status}`));
                            }
                        } catch (e) {
                            Logger.error('UPDATE', '解析响应数据失败', e);
                            reject(e);
                        }
                    },
                    onerror: (error) => {
                        clearTimeout(timeout);
                        Logger.error('UPDATE', '请求失败', error);
                        reject(error);
                    },
                    ontimeout: () => {
                        clearTimeout(timeout);
                        Logger.error('UPDATE', '请求超时');
                        reject(new Error('请求超时'));
                    }
                });
            });
        },

        isNewerVersion(cloudVersion, localVersion) {
            const parseVersion = (v) => {
                const cleaned = v.replace(/^v/, '');
                return cleaned.split('.').map(n => parseInt(n) || 0);
            };

            const cloud = parseVersion(cloudVersion);
            const local = parseVersion(localVersion);

            Logger.debug('UPDATE', '版本解析', {
                cloudParsed: cloud,
                localParsed: local
            });

            for (let i = 0; i < Math.max(cloud.length, local.length); i++) {
                const c = cloud[i] || 0;
                const l = local[i] || 0;
                if (c > l) {
                    Logger.debug('UPDATE', '发现新版本', { position: i, cloud: c, local: l });
                    return true;
                }
                if (c < l) {
                    Logger.debug('UPDATE', '云端版本较旧', { position: i, cloud: c, local: l });
                    return false;
                }
            }

            Logger.debug('UPDATE', '版本相同');
            return false;
        },

        async updateLocalData(cloudData) {
            Logger.info('UPDATE', '开始更新本地数据', cloudData);

            if (cloudData.customGifs && Array.isArray(cloudData.customGifs)) {
                const oldCount = customGifs.length;
                customGifs = cloudData.customGifs;
                Storage.set('customGifs', customGifs);

                CacheManager.clear('gif');
                CacheManager.clear('search');

                refreshCurrentView();

                Logger.info('UPDATE', '本地数据已更新', {
                    oldCount,
                    newCount: customGifs.length,
                    added: customGifs.length - oldCount
                });
            } else {
                Logger.warn('UPDATE', '云数据格式无效', cloudData);
            }
        },

        async manualUpdate() {
            showMessage(t().messages.updateChecking);
            Logger.info('UPDATE', '手动检查更新');
            return await this.checkAndUpdate(true);
        }
    };
    /* === EH 工具函数 BEGIN === */
    // 外部库地址
    const EH_GIF_JS_CANDIDATES = [
        'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.min.js',
        'https://cdn.jsdelivr.net/npm/[email protected]/dist/gif.min.js',
        'https://unpkg.com/[email protected]/dist/gif.min.js'
    ];
    const EH_GIF_WORKER_CANDIDATES = [
        'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.min.js',
        'https://cdn.jsdelivr.net/npm/[email protected]/dist/gif.worker.min.js',
        'https://unpkg.com/[email protected]/dist/gif.worker.min.js'
    ];
    const EH_GIFUCT_JS_CANDIDATES = [
        'https://cdn.jsdelivr.net/npm/[email protected]/dist/gifuct.min.js',
        'https://unpkg.com/[email protected]/dist/gifuct.min.js'
    ];

    // 简单日志别名
    const EH_LOG = { i: (...a)=>console.info('[EH]',...a), w:(...a)=>console.warn('[EH]',...a), e:(...a)=>console.error('[EH]',...a) };
    function eh_withTimeout(promise, ms = 3000) {
        return Promise.race([
            promise,
            new Promise(resolve => setTimeout(() => resolve('__EH_TIMEOUT__'), ms))
        ]);
    }
    // 动态载入脚本(一次)
    async function eh_loadScriptOnce(urlOrList){
        const urls = Array.isArray(urlOrList) ? urlOrList : [urlOrList];
        for (const url of urls) {
            if (window.__eh_loadedLibs && window.__eh_loadedLibs[url]) return;
            try {
                await new Promise((resolve, reject) => {
                    const s = document.createElement('script');
                    s.src = url;
                    s.crossOrigin = 'anonymous';
                    s.onload = () => {
                        window.__eh_loadedLibs = window.__eh_loadedLibs || {};
                        window.__eh_loadedLibs[url] = true;
                        resolve();
                    };
                    s.onerror = (err) => { EH_LOG.w('load lib fail', url, err); reject(err); };
                    document.head.appendChild(s);
                });
                return; // 某个候选加载成功,直接结束
            } catch (e) {
                // 该源失败,继续尝试下一个
            }
        }
        throw new Error('All CDN sources failed');
    }

    // 确保所需库已经加载
    async function eh_ensureLibs(){
        if (!window.GIF) await eh_loadScriptOnce(EH_GIF_JS_CANDIDATES);
        if (!window.gifuct) await eh_loadScriptOnce(EH_GIFUCT_JS_CANDIDATES);
        try {
            if (window.GIF) {
                for (const url of EH_GIF_WORKER_CANDIDATES) {
                    try { window.GIF.prototype.workerScript = url; break; } catch(e) {}
                }
            }
        } catch(e){ EH_LOG.w('set workerScript fail', e); }
    }

    // GM 跨域获取 ArrayBuffer(用于绕过 CORS)
    function eh_gmFetchArrayBuffer(url, timeout=20000){
        return new Promise((resolve, reject) => {
            try {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url,
                    responseType: 'arraybuffer',
                    timeout,
                    onload(res){ if (res.status >= 200 && res.status < 300) resolve(res.response); else reject(new Error('HTTP ' + res.status)); },
                    onerror(err){ reject(err); },
                    ontimeout(){ reject(new Error('timeout')); }
                });
            } catch(e) { reject(e); }
        });
    }

    // ArrayBuffer -> objectURL
    function eh_arrayBufferToObjectURL(ab, mime='image/gif'){
        const blob = new Blob([ab], { type: mime });
        return URL.createObjectURL(blob);
    }

    // 尝试用 GM 请求获取资源并返回 objectURL,失败回退原 url
    async function eh_loadImageObjectURL(url){
        try {
            const ab = await eh_gmFetchArrayBuffer(url);
            let mime = 'image/gif';
            if (/\.jpe?g($|\?)/i.test(url)) mime = 'image/jpeg';
            if (/\.png($|\?)/i.test(url)) mime = 'image/png';
            if (/\.webp($|\?)/i.test(url)) mime = 'image/webp';
            return eh_arrayBufferToObjectURL(ab, mime);
        } catch (err) {
            EH_LOG.w('GM fetch failed, fallback to direct URL', err);
            return url;
        }
    }

    // 用 gifuct-js 解析 GIF 帧
    function eh_parseGifFramesFromArrayBuffer(ab){
        const parsed = window.gifuct.parseGIF(ab);
        const frames = window.gifuct.decompressFrames(parsed, true);
        return frames;
    }

    // 将 gifuct-js 的帧合成为全帧 Canvas 列表(简单处理 disposalType==2)
    function eh_framesToCanvases(frames){
        const W = frames[0].dims.width;
        const H = frames[0].dims.height;
        const base = document.createElement('canvas'); base.width = W; base.height = H;
        const ctx = base.getContext('2d');
        ctx.clearRect(0,0,W,H);
        const out = [];
        frames.forEach(frame => {
            const { left, top, width: w, height: h } = frame.dims;
            try {
                const patch = new ImageData(new Uint8ClampedArray(frame.patch), w, h);
                ctx.putImageData(patch, left, top);
            } catch(e) {
                EH_LOG.w('putImageData failed', e);
            }
            const c = document.createElement('canvas'); c.width = W; c.height = H;
            c.getContext('2d').drawImage(base, 0, 0);
            out.push(c);
            if (frame.disposalType === 2) {
                ctx.clearRect(left, top, w, h);
            }
        });
        return out;
    }

    function eh_scaleFrameCanvas(canvas, scale, maxW = 480, maxH = 480){
        const w = Math.min(Math.round(canvas.width * scale), maxW);
        const h = Math.min(Math.round(canvas.height * scale), maxH);
        const c = document.createElement('canvas'); c.width = w; c.height = h;
        c.getContext('2d').drawImage(canvas, 0, 0, w, h);
        return c;
    }

    function eh_drawTextOnCanvas(canvas, text, opts = { fontSize: 36, fontFamily: 'Arial, sans-serif', color: '#fff', stroke: '#000', position: 'bottom' }){
        const ctx = canvas.getContext('2d');
        ctx.save();
        const scaleRef = Math.max(1, canvas.width / 400);
        const fs = Math.round(opts.fontSize * scaleRef);
        ctx.font = `bold ${fs}px ${opts.fontFamily}`;
        ctx.textAlign = 'center';
        ctx.fillStyle = opts.color || '#fff';
        ctx.strokeStyle = opts.stroke || '#000';
        ctx.lineWidth = Math.max(2, fs / 18);
        let x = Math.round(canvas.width / 2);
        let y;
        if (opts.position === 'top') y = fs + 10;
        else if (opts.position === 'center') y = Math.round(canvas.height / 2 + fs / 3);
        else y = canvas.height - 10;
        ctx.strokeText(text, x, y);
        ctx.fillText(text, x, y);
        ctx.restore();
    }

    // 使用 gif.js 编码 frames (canvas[]) -> Blob
    function eh_encodeWithGifJs(frames, delays, { quality = 12, repeat = 0 } = {}){
        return new Promise(async (resolve, reject) => {
            await eh_ensureLibs();
            try {
                const gif = new GIF({ workers: 2, quality, repeat });
                frames.forEach((c, i) => gif.addFrame(c, { delay: delays[i] || 100 }));
                gif.on('finished', blob => resolve(blob));
                gif.on('error', err => reject(err));
                gif.render();
            } catch (e) { reject(e); }
        });
    }

    // 迭代尝试不同缩放比以控制输出大小(maxBytes,默认 5MB)
    async function eh_encodeGifWithLimit(frames, delays, { maxBytes = 5*1024*1024, quality = 12, repeat = 0, maxWidth = 480, maxHeight = 480 } = {}){
        let scale = 1.0;
        let lastBlob = null;
        for (let i=0;i<6;i++){
            const scaled = frames.map(f => eh_scaleFrameCanvas(f, scale, maxWidth, maxHeight));
            const blob = await eh_encodeWithGifJs(scaled, delays, { quality, repeat });
            lastBlob = blob;
            EH_LOG.i('encode try', i, 'scale', scale, 'size', blob.size);
            if (blob.size <= maxBytes) return blob;
            scale *= 0.8;
        }
        return lastBlob;
    }

    // 复制 Blob 到剪贴板(优先 Clipboard API)
    async function eh_copyBlobToClipboard(blob, { allowDownload = true } = {}) {
        // 1) 原生写入对应 MIME(若支持 image/gif 就保持动图)
        try {
            if (navigator.clipboard && navigator.clipboard.write && window.ClipboardItem) {
                const item = new ClipboardItem({ [blob.type]: blob });
                await navigator.clipboard.write([item]);
                return true;
            }
        } catch (e) {
            EH_LOG.w('clipboard write failed', e);
        }

        // 2) ✂️ 删除原逻辑(写入 blob: 文本URL)

        // 3) 仍不行:允许则下载兜底,至少保证得到动图文件
        if (!allowDownload) return false;
        try {
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'emoji-' + Date.now() + (blob.type.includes('gif') ? '.gif' : '.png');
            document.body.appendChild(a); a.click(); a.remove();
            setTimeout(() => URL.revokeObjectURL(url), 3000);
            return true;
        } catch (e3) {
            EH_LOG.e('fallback download failed', e3);
            return false;
        }
    }
    /* 主流程:给 imageUrl(gif 或 静态)加文字并返回 Blob */
    async function addTextToImageOrGifAndExport(imageUrl, text, options = { fontSize: 36, fontFamily: 'Arial', color: '#fff', position: 'bottom' }){
        await eh_ensureLibs();
        const objectUrl = await eh_loadImageObjectURL(imageUrl);
        const isGif = /\.gif($|\?)/i.test(imageUrl) || (objectUrl && objectUrl.startsWith('blob:') && /\.gif($|\?)/i.test(imageUrl));
        if (isGif) {
            let ab;
            try { ab = await eh_gmFetchArrayBuffer(imageUrl); }
            catch(e) { const resp = await fetch(objectUrl); ab = await resp.arrayBuffer(); }
            const frames = eh_parseGifFramesFromArrayBuffer(ab);
            const canvases = eh_framesToCanvases(frames);
            const delays = frames.map(f => (f.delay || 10) * 10);
            canvases.forEach(c => eh_drawTextOnCanvas(c, text, options));
            const blob = await eh_encodeGifWithLimit(canvases, delays, { maxBytes: 5*1024*1024, quality: 12, maxWidth: 480, maxHeight: 480 });
            return blob;
        } else {
            const img = new Image();
            img.crossOrigin = 'anonymous';
            img.src = objectUrl || imageUrl;
            await new Promise((res, rej)=>{ img.onload = res; img.onerror = ()=>rej(new Error('image load fail')); });
            const maxW = 1024, maxH = 1024;
            let w = img.naturalWidth, h = img.naturalHeight;
            const r = Math.min(1, Math.min(maxW / w, maxH / h));
            w = Math.round(w * r); h = Math.round(h * r);
            const c = document.createElement('canvas'); c.width = w; c.height = h;
            const ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0, w, h);
            eh_drawTextOnCanvas(c, text, options);
            const blob = await new Promise(resolve => c.toBlob(resolve, 'image/webp', 0.85));
            if (blob && blob.size <= 5*1024*1024) return blob;
            return await new Promise(resolve => c.toBlob(resolve, 'image/png', 0.95));
        }
    }
    // 从 URL 拉取图像并转成 PNG Blob(走 GM_xmlhttpRequest,避免 CORS 污染)
    async function eh_fetchBlobFromUrl(imageUrl) {
        try {
            // 先尝试用 GM 直接拿原始二进制并保留 MIME(GIF 动图不会丢帧)
            const ab = await eh_gmFetchArrayBuffer(imageUrl);
            let mime = 'application/octet-stream';
            if (/\.gif($|\?)/i.test(imageUrl)) mime = 'image/gif';
            else if (/\.png($|\?)/i.test(imageUrl)) mime = 'image/png';
            else if (/\.jpe?g($|\?)/i.test(imageUrl)) mime = 'image/jpeg';
            else if (/\.webp($|\?)/i.test(imageUrl)) mime = 'image/webp';
            return new Blob([ab], { type: mime });
        } catch (e) {
            // 回退:静图转 PNG,GIF 仍尽量保持动画(多数浏览器直接 <img> 即可动)
            const objectUrl = await eh_loadImageObjectURL(imageUrl);
            const isGif = /\.gif($|\?)/i.test(imageUrl);
            if (isGif) {
                // 没有 GM 权限时,尽量把 blob: URL 的数据当作 GIF 交还
                const resp = await fetch(objectUrl);
                const buf = await resp.arrayBuffer();
                URL.revokeObjectURL(objectUrl);
                return new Blob([buf], { type: 'image/gif' });
            } else {
                const img = new Image();
                img.src = objectUrl;
                await img.decode();
                const c = document.createElement('canvas');
                c.width = img.naturalWidth;
                c.height = img.naturalHeight;
                c.getContext('2d').drawImage(img, 0, 0);
                const pngBlob = await new Promise(res => c.toBlob(res, 'image/png', 0.95));
                URL.revokeObjectURL(objectUrl);
                return pngBlob;
            }
        }
    }

    // 模拟“复制图像”——始终以 PNG 写入剪贴板;失败不下载
    async function copyImageLikeBrowser(imageUrl) {
        const blob = await eh_fetchBlobFromUrl(imageUrl);
        const isGif = /\.gif($|\?)/i.test(imageUrl) || (blob && blob.type === 'image/gif');
        await eh_copyBlobToClipboard(blob, { allowDownload: isGif });
    }

    /* === EH 工具函数 END === */

    // 🎨 文字编辑器
    const TextEditor = {
        fonts: [
            'Arial, sans-serif',
            'Helvetica, sans-serif',
            'Georgia, serif',
            'Times New Roman, serif',
            'Courier New, monospace',
            'Verdana, sans-serif',
            'Impact, sans-serif',
            'Comic Sans MS, cursive',
            'Trebuchet MS, sans-serif',
            'Arial Black, sans-serif',
            'Microsoft YaHei, sans-serif',
            'SimHei, sans-serif',
            'SimSun, serif',
            'KaiTi, serif'
        ],

        open(imageUrl) {
            currentEditingImage = imageUrl;
            Logger.info('UI', '打开文字编辑器', imageUrl);
            this.createEditor();
            this.showEditor();
        },

        createEditor() {
            if (textEditorPanel) {
                textEditorPanel.remove();
                Logger.debug('UI', '移除旧的编辑器面板');
            }

            const lang = t();
            const panel = document.createElement('div');
            panel.className = 'emoji-helper-text-editor';
            panel.id = 'emoji-helper-text-editor';

            panel.innerHTML = `
                <div class="emoji-helper-header draggable-header">
                    <div class="emoji-helper-title">${lang.textEditor.title}</div>
                    <button class="emoji-helper-btn close text-editor-close">×</button>
                </div>
                <div class="text-editor-content">
                    <div class="editor-preview">
                        <canvas id="text-editor-canvas"></canvas>
                        <div class="preview-overlay">
                            <div class="drag-hint">${lang.textEditor.dragHint}</div>
                            <div class="copy-hint">${lang.textEditor.copyHint}</div>
                        </div>
                    </div>
                    <div class="editor-controls">
                        <div class="control-group">
                            <label class="control-label">${lang.textEditor.text}</label>
                            <input type="text" id="text-input" placeholder="${lang.textEditor.textPlaceholder}" maxlength="50">
                        </div>
                        <div class="control-group">
                            <label class="control-label">${lang.textEditor.fontSize}</label>
                            <input type="range" id="font-size-slider" min="12" max="72" value="36">
                            <span id="font-size-value">36px</span>
                        </div>
                        <div class="control-group">
                            <label class="control-label">${lang.textEditor.fontFamily}</label>
                            <select id="font-family-select">
                                ${this.fonts.map(font => `<option value="${font}">${font.split(',')[0]}</option>`).join('')}
                            </select>
                        </div>
                        <div class="control-group">
                            <label class="control-label">${lang.textEditor.textColor}</label>
                            <input type="color" id="text-color-picker" value="#ffffff">
                        </div>
                        <div class="control-group">
                            <label class="control-label">${lang.textEditor.position}</label>
                            <select id="text-position-select">
                                <option value="top">${lang.textEditor.positions.top}</option>
                                <option value="center">${lang.textEditor.positions.center}</option>
                                <option value="bottom" selected>${lang.textEditor.positions.bottom}</option>
                            </select>
                        </div>
                    </div>
                </div>
                <div class="text-editor-actions">
                    <button class="editor-btn primary" id="download-btn" disabled>${lang.textEditor.download}</button>
                    <button class="editor-btn" id="close-editor-btn">${lang.textEditor.close}</button>
                </div>
            `;

            textEditorPanel = panel;
            document.body.appendChild(panel);

            this.bindEditorEvents();
            this.loadImage();
            this.makePanelDraggable(panel);

            Logger.debug('UI', '文字编辑器界面创建完成');
        },

        makePanelDraggable(panel) {
            const header = panel.querySelector('.draggable-header');
            if (!header) return;

            let isDragging = false;
            let startX = 0;
            let startY = 0;
            let initialX = 0;
            let initialY = 0;

            header.addEventListener('mousedown', (e) => {
                if (e.target.classList.contains('close')) return;

                isDragging = true;
                header.style.cursor = 'grabbing';

                const rect = panel.getBoundingClientRect();
                startX = e.clientX;
                startY = e.clientY;
                initialX = rect.left;
                initialY = rect.top;

                e.preventDefault();
                Logger.trace('UI', '开始拖拽文字编辑器');
            });

            const handleMouseMove = (e) => {
                if (!isDragging) return;

                const deltaX = e.clientX - startX;
                const deltaY = e.clientY - startY;

                const newX = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, initialX + deltaX));
                const newY = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, initialY + deltaY));

                panel.style.left = newX + 'px';
                panel.style.top = newY + 'px';
                panel.style.transform = 'none';
            };

            const handleMouseUp = () => {
                if (isDragging) {
                    isDragging = false;
                    header.style.cursor = 'grab';
                    Logger.trace('UI', '结束拖拽文字编辑器');
                }
            };

            document.addEventListener('mousemove', handleMouseMove);
            document.addEventListener('mouseup', handleMouseUp);

            header.style.cursor = 'grab';
        },

        async loadImage() {
            const canvas = document.getElementById('text-editor-canvas');
            if (!canvas) return;

            const ctx = canvas.getContext('2d');

            Logger.debug('UI', '开始加载图片到canvas', currentEditingImage);

            const img = new Image();
            img.crossOrigin = 'anonymous';

            img.onload = () => {
                const maxWidth = 400;
                const maxHeight = 300;
                let { width, height } = img;

                if (width > maxWidth || height > maxHeight) {
                    const ratio = Math.min(maxWidth / width, maxHeight / height);
                    width *= ratio;
                    height *= ratio;
                }

                canvas.width = width;
                canvas.height = height;

                ctx.drawImage(img, 0, 0, width, height);
                this.redrawText();
                this.enableAdvancedDrag();

                Logger.info('UI', '图片加载完成', { width, height });
            };

            img.onerror = () => {
                Logger.error('UI', '图片加载失败', currentEditingImage);
                showMessage(t().messages.imageError);
            };

            img.src = currentEditingImage;
        },

        enableAdvancedDrag() {
            const canvas = document.getElementById('text-editor-canvas');
            if (!canvas) return;

            canvas.draggable = true;
            canvas.style.cursor = 'grab';

            canvas.addEventListener('dragstart', (e) => {
                canvas.style.cursor = 'grabbing';
                canvas.toBlob((blob) => {
                    if (!blob) return;
                    const filename = 'emoji-text-' + Date.now() + (blob.type.includes('gif') ? '.gif' : '.png');
                    const objectURL = URL.createObjectURL(blob);
                    try {
                        e.dataTransfer.setData('DownloadURL', `${blob.type}:${filename}:${objectURL}`);
                        e.dataTransfer.setData('text/plain', filename);
                        e.dataTransfer.effectAllowed = 'copy';
                    } catch (err) {
                        console.warn('dragset failed', err);
                    }
                    const dragImg = new Image();
                    dragImg.onload = () => e.dataTransfer.setDragImage(dragImg, dragImg.width / 2, dragImg.height / 2);
                    dragImg.src = objectURL;
                    setTimeout(() => { URL.revokeObjectURL(objectURL); }, 3000);
                }, 'image/png', 0.95);
            });

            canvas.addEventListener('dragend', () => {
                canvas.style.cursor = 'grab';
            });

            canvas.addEventListener('dragstart', (e) => {
                // 禁用从预览canvas导出PNG,避免误把GIF变成静态第一帧
                e.preventDefault();
            });

            canvas.addEventListener('contextmenu', (e) => {
                e.preventDefault();
                canvas.toBlob(async (blob) => {
                    if (!blob) return;
                    try {
                        const ok = await eh_copyBlobToClipboard(blob);
                        if (ok) showMessage(t().messages.copied);
                    } catch (err) {
                        console.warn('copy failed', err);
                        const dataURL = canvas.toDataURL('image/png');
                        const ta = document.createElement('textarea'); ta.value = dataURL; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove();
                        showMessage(t().messages.copied);
                    }
                }, 'image/png', 0.95);
            });
        },


        fallbackCopyMethod(canvas) {
            try {
                const dataURL = canvas.toDataURL('image/png');
                const tempInput = document.createElement('textarea');
                tempInput.value = dataURL;
                document.body.appendChild(tempInput);
                tempInput.select();
                document.execCommand('copy');
                document.body.removeChild(tempInput);
                showMessage(t().messages.copied);
                Logger.info('UI', '图片复制成功(备用方法)');
            } catch (err) {
                Logger.error('UI', '备用复制方法失败', err);
                showMessage('复制失败,请使用拖拽功能');
            }
        },

        redrawText() {
            const canvas = document.getElementById('text-editor-canvas');
            if (!canvas) return;

            const ctx = canvas.getContext('2d');
            const img = new Image();
            img.crossOrigin = 'anonymous';

            img.onload = () => {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

                const textInput = document.getElementById('text-input');
                const text = textInput ? textInput.value : '';

                if (!text) {
                    const downloadBtn = document.getElementById('download-btn');
                    if (downloadBtn) downloadBtn.disabled = true;
                    return;
                }

                const fontSizeSlider = document.getElementById('font-size-slider');
                const fontFamilySelect = document.getElementById('font-family-select');
                const textColorPicker = document.getElementById('text-color-picker');
                const textPositionSelect = document.getElementById('text-position-select');

                const fontSize = fontSizeSlider ? fontSizeSlider.value : '36';
                const fontFamily = fontFamilySelect ? fontFamilySelect.value : 'Arial, sans-serif';
                const textColor = textColorPicker ? textColorPicker.value : '#ffffff';
                const position = textPositionSelect ? textPositionSelect.value : 'bottom';

                ctx.font = `bold ${fontSize}px ${fontFamily}`;
                ctx.fillStyle = textColor;
                ctx.strokeStyle = '#000000';
                ctx.lineWidth = Math.max(2, parseInt(fontSize) / 18);
                ctx.textAlign = 'center';

                const x = canvas.width / 2;
                let y;
                switch (position) {
                    case 'top':
                        y = parseInt(fontSize) + 10;
                        break;
                    case 'center':
                        y = canvas.height / 2 + parseInt(fontSize) / 3;
                        break;
                    case 'bottom':
                    default:
                        y = canvas.height - 10;
                        break;
                }

                ctx.strokeText(text, x, y);
                ctx.fillText(text, x, y);

                const downloadBtn = document.getElementById('download-btn');
                if (downloadBtn) downloadBtn.disabled = false;

                Logger.trace('UI', '文字重绘完成', { text, fontSize, fontFamily, textColor, position });
            };

            img.src = currentEditingImage;
        },

        bindEditorEvents() {
            const closeBtn = document.querySelector('.text-editor-close');
            const closeEditorBtn = document.getElementById('close-editor-btn');

            if (closeBtn) closeBtn.addEventListener('click', this.close.bind(this));
            if (closeEditorBtn) closeEditorBtn.addEventListener('click', this.close.bind(this));

            const textInput = document.getElementById('text-input');
            const fontSizeSlider = document.getElementById('font-size-slider');
            const fontFamilySelect = document.getElementById('font-family-select');
            const textColorPicker = document.getElementById('text-color-picker');
            const textPositionSelect = document.getElementById('text-position-select');
            const downloadBtn = document.getElementById('download-btn');

            // ★ 关键:输入时联动预览 & 控件变动时重绘
            if (textInput) textInput.addEventListener('input', this.redrawText.bind(this));
            if (fontSizeSlider) {
                fontSizeSlider.addEventListener('input', (e) => {
                    const fontSizeValue = document.getElementById('font-size-value');
                    if (fontSizeValue) fontSizeValue.textContent = e.target.value + 'px';
                    this.redrawText();
                });
            }
            if (fontFamilySelect) fontFamilySelect.addEventListener('change', this.redrawText.bind(this));
            if (textColorPicker) textColorPicker.addEventListener('change', this.redrawText.bind(this));
            if (textPositionSelect) textPositionSelect.addEventListener('change', this.redrawText.bind(this));

            // 下载键:点击时生成并下载(GIF 保持多帧)
            if (downloadBtn) {
                downloadBtn.addEventListener('click', async () => {
                    this.redrawText();
                    const text = document.getElementById('text-input')?.value || '';
                    if (!text) { showMessage('请输入文字'); return; }

                    const fontSize = parseInt(document.getElementById('font-size-slider')?.value || 36, 10);
                    const fontFamily = document.getElementById('font-family-select')?.value || 'Arial, sans-serif';
                    const textColor = document.getElementById('text-color-picker')?.value || '#ffffff';
                    const position = document.getElementById('text-position-select')?.value || 'bottom';

                    try {
                        const blob = await addTextToImageOrGifAndExport(currentEditingImage, text, {
                            fontSize, fontFamily, color: textColor, position
                        });

                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = 'emoji-text-' + Date.now() + (blob.type.includes('gif') ? '.gif' : '.png');
                        document.body.appendChild(a); a.click(); a.remove();
                        setTimeout(() => URL.revokeObjectURL(url), 3000);
                        showMessage(t().messages.imageGenerated);
                    } catch (err) {
                        Logger.error('UI', '生成失败', err);
                        showMessage(t().messages.imageError);
                    }
                });
            }

            Logger.debug('UI', '编辑器事件绑定完成');
        },

        downloadImage() {
            const blob = this.lastGeneratedBlob;
            if (blob) {
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'emoji-text-' + Date.now() + (blob.type.includes('gif') ? '.gif' : '.png');
                document.body.appendChild(a);
                a.click();
                a.remove();
                setTimeout(() => URL.revokeObjectURL(url), 3000);
                showMessage(t().messages.imageGenerated);
                Logger.info('UI', '图片下载完成', a.download);
                return;
            }
            const canvas = document.getElementById('text-editor-canvas');
            if (!canvas) return;
            const link = document.createElement('a');
            link.download = `emoji-with-text-${Date.now()}.png`;
            link.href = canvas.toDataURL('image/png');
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            showMessage(t().messages.imageGenerated);
            Logger.info('UI', '图片下载完成 (canvas fallback)');
        },


        showEditor() {
            if (textEditorPanel) {
                textEditorPanel.style.display = 'flex';
                Logger.debug('UI', '显示文字编辑器');
            }
        },

        close() {
            if (textEditorPanel) {
                textEditorPanel.style.display = 'none';
                Logger.debug('UI', '关闭文字编辑器');
            }
        }
    };

    // 🔍 网络GIF搜索 API
    const GifSearchAPI = {
        async searchGifs(query, limit = 12, timeoutMs = 3000) {
            const searchEngine = Config.get('searchEngine');
            const cacheKey = `${searchEngine}-${query}`;

            if (CacheManager.has(cacheKey, 'search')) {
                Logger.debug('SEARCH', '使用缓存结果', { query, engine: searchEngine });
                return CacheManager.get(cacheKey, 'search');
            }

            try {
                Logger.info('SEARCH', '开始搜索GIF', { query, engine: searchEngine, limit, timeoutMs });
                const results = await this.callAPI(query, limit, timeoutMs);
                CacheManager.set(cacheKey, results, 'search');
                Logger.info('SEARCH', '搜索成功', { query, engine: searchEngine, resultCount: results.length });
                return results;
            } catch (error) {
                Logger.error('SEARCH', '搜索失败', { query, engine: searchEngine, error });
                return [];
            }
        },
        async callAPI(query, limit, timeoutMs = 3000) {
            const searchEngine = Config.get('searchEngine');

            return new Promise((resolve, reject) => {
                const apiUrl = this.getApiUrl(searchEngine, query, limit);
                Logger.debug('SEARCH', '调用API', { url: apiUrl, timeoutMs });

                const kill = setTimeout(() => reject(new Error('API请求超时')), timeoutMs);

                GM_xmlhttpRequest({
                    method: 'GET',
                    url: apiUrl,
                    timeout: timeoutMs,
                    onload: (response) => {
                        clearTimeout(kill);
                        try {
                            Logger.debug('SEARCH', 'API响应状态', response.status);
                            const data = JSON.parse(response.responseText);
                            const gifs = this.parseResponse(searchEngine, data);
                            Logger.debug('SEARCH', 'API解析完成', { gifCount: gifs.length });
                            resolve(gifs);
                        } catch (e) {
                            Logger.error('SEARCH', '解析响应失败', e);
                            reject(e);
                        }
                    },
                    onerror: (error) => {
                        clearTimeout(kill);
                        Logger.error('SEARCH', 'API请求失败', error);
                        reject(error);
                    },
                    ontimeout: () => {
                        clearTimeout(kill);
                        Logger.error('SEARCH', 'API请求超时');
                        reject(new Error('请求超时'));
                    }
                });
            });
        },

        getApiUrl(searchEngine, query, limit) {
            const encodedQuery = encodeURIComponent(query);

            switch (searchEngine) {
                case 'giphy':
                    return `https://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=${encodedQuery}&limit=${limit}&rating=g&lang=zh`;
                case 'tenor':
                    return `https://tenor.googleapis.com/v2/search?key=AIzaSyAyimkuYQYF_FXVALexPuGQctUWRURdCl0&q=${encodedQuery}&limit=${limit}&media_filter=gif&contentfilter=high`;
                default:
                    return `https://api.giphy.com/v1/gifs/search?api_key=GlVGYHkr3WSBnllca54iNt0yFbjz7L65&q=${encodedQuery}&limit=${limit}&rating=g`;
            }
        },

        parseResponse(searchEngine, data) {
            try {
                switch (searchEngine) {
                    case 'giphy':
                        return (data.data || []).map(gif => ({
                            id: gif.id,
                            title: gif.title || 'GIF',
                            url: gif.images.fixed_height_small?.url || gif.images.original?.url,
                            previewUrl: gif.images.preview_gif?.url || gif.images.fixed_height_small?.url,
                            width: gif.images.fixed_height_small?.width || 200,
                            height: gif.images.fixed_height_small?.height || 200
                        }));
                    case 'tenor':
                        return (data.results || []).map(gif => ({
                            id: gif.id,
                            title: gif.content_description || 'GIF',
                            url: gif.media_formats?.gif?.url || gif.media_formats?.tinygif?.url,
                            previewUrl: gif.media_formats?.tinygif?.url || gif.media_formats?.gif?.url,
                            width: gif.media_formats?.gif?.dims?.[0] || 200,
                            height: gif.media_formats?.gif?.dims?.[1] || 200
                        }));
                    default:
                        return [];
                }
            } catch (e) {
                Logger.error('SEARCH', '解析失败', e);
                return [];
            }
        }
    };

    // 拖拽管理器
    const DragManager = {
        makeDraggable(element, handle) {
            const dragHandle = handle || element.querySelector('.draggable-header') || element.querySelector('.emoji-helper-header');
            if (!dragHandle) return;

            let isDragging = false;
            let startX = 0;
            let startY = 0;
            let initialX = 0;
            let initialY = 0;

            const handleMouseDown = (e) => {
                if (e.target.classList.contains('close') || e.target.classList.contains('emoji-helper-btn')) return;

                isDragging = true;
                dragHandle.style.cursor = 'grabbing';

                const rect = element.getBoundingClientRect();
                startX = e.clientX;
                startY = e.clientY;
                initialX = rect.left;
                initialY = rect.top;

                e.preventDefault();
                Logger.trace('UI', '开始拖拽面板', element.id);
            };

            const handleMouseMove = (e) => {
                if (!isDragging) return;

                const deltaX = e.clientX - startX;
                const deltaY = e.clientY - startY;

                let newX = initialX + deltaX;
                let newY = initialY + deltaY;

                newX = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, newX));
                newY = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, newY));

                element.style.left = newX + 'px';
                element.style.top = newY + 'px';
                element.style.right = 'auto';
                element.style.bottom = 'auto';

                if (element.id === 'emoji-helper-main-panel') {
                    Config.set('panelPosition', { x: newX, y: newY });
                } else if (element.id === 'emoji-helper-settings-panel') {
                    Config.set('settingsPanelPosition', { x: newX, y: newY });
                }
            };

            const handleMouseUp = () => {
                if (isDragging) {
                    isDragging = false;
                    dragHandle.style.cursor = 'grab';
                    Logger.trace('UI', '结束拖拽面板', element.id);
                }
            };

            dragHandle.addEventListener('mousedown', handleMouseDown);
            document.addEventListener('mousemove', handleMouseMove);
            document.addEventListener('mouseup', handleMouseUp);

            dragHandle.style.cursor = 'grab';
            dragHandle.style.userSelect = 'none';
        }
    };

    // 浮动按钮显示控制
    function updateFloatingButtonVisibility() {
        if (floatingButton) {
            const show = Config.get('showFloatingButton');
            floatingButton.style.display = show ? 'flex' : 'none';
            Logger.debug('UI', '更新浮动按钮显示状态', show);
        }
    }

    // 更新面板位置
    function updatePanelPosition() {
        if (emojiPanel) {
            const pos = Config.get('panelPosition');
            emojiPanel.style.left = pos.x + 'px';
            emojiPanel.style.top = pos.y + 'px';
            emojiPanel.style.right = 'auto';
            emojiPanel.style.bottom = 'auto';
            Logger.trace('UI', '更新主面板位置', pos);
        }
    }

    function updateSettingsPanelPosition() {
        if (settingsPanel) {
            const pos = Config.get('settingsPanelPosition');
            settingsPanel.style.left = pos.x + 'px';
            settingsPanel.style.top = pos.y + 'px';
            settingsPanel.style.right = 'auto';
            settingsPanel.style.bottom = 'auto';
            Logger.trace('UI', '更新设置面板位置', pos);
        }
    }

    // 继续添加其余代码...
    // (由于长度限制,我需要分几个部分来完成。这是第一部分的修复版本)

    // 🎨 样式(全面优化UI)
    GM_addStyle(`
        :root {
            --eh-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            --eh-border-radius: 12px;
            --eh-shadow: 0 8px 32px rgba(0,0,0,0.12);
            --eh-shadow-hover: 0 12px 48px rgba(0,0,0,0.18);
            --eh-transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .emoji-helper-light {
            --eh-bg-primary: #ffffff;
            --eh-bg-secondary: #f8fafc;
            --eh-bg-tertiary: #f1f5f9;
            --eh-text-primary: #1e293b;
            --eh-text-secondary: #64748b;
            --eh-border-color: #e2e8f0;
            --eh-accent-color: #3b82f6;
            --eh-accent-hover: #2563eb;
            --eh-hover-bg: #f1f5f9;
            --eh-success-color: #10b981;
            --eh-danger-color: #ef4444;
            --eh-warning-color: #f59e0b;
        }

                .emoji-helper-dark {
            --eh-bg-primary: #1e293b;
            --eh-bg-secondary: #334155;
            --eh-bg-tertiary: #475569;
            --eh-text-primary: #f8fafc;
            --eh-text-secondary: #cbd5e1;
            --eh-border-color: #475569;
            --eh-accent-color: #60a5fa;
            --eh-accent-hover: #3b82f6;
            --eh-hover-bg: #475569;
            --eh-success-color: #34d399;
            --eh-danger-color: #f87171;
            --eh-warning-color: #fbbf24;
        }

        /* 浮动按钮 */
        .emoji-helper-floating-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 56px;
            height: 56px;
            background: var(--eh-accent-color);
            border: none;
            border-radius: 50%;
            box-shadow: var(--eh-shadow);
            cursor: pointer;
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 24px;
            color: white;
            transition: var(--eh-transition);
            user-select: none;
        }

        .emoji-helper-floating-btn:hover {
            background: var(--eh-accent-hover);
            box-shadow: var(--eh-shadow-hover);
            transform: scale(1.1);
        }

        /* 主面板 */
        .emoji-helper-panel {
            position: fixed;
            top: 86px;
            left: 20px;
            width: 380px;
            max-height: 480px;
            background: var(--eh-bg-primary);
            border: 1px solid var(--eh-border-color);
            border-radius: var(--eh-border-radius);
            box-shadow: var(--eh-shadow);
            z-index: 10001;
            font-family: var(--eh-font);
            display: none;
            flex-direction: column;
            overflow: hidden;
            transition: var(--eh-transition);
        }

        .emoji-helper-panel.show {
            display: flex;
        }

        /* 面板头部 */
        .emoji-helper-header {
            background: var(--eh-bg-secondary);
            padding: 16px 20px;
            border-bottom: 1px solid var(--eh-border-color);
            display: flex;
            align-items: center;
            justify-content: space-between;
            cursor: grab;
            user-select: none;
        }

        .emoji-helper-header:active {
            cursor: grabbing;
        }

        .emoji-helper-title {
            font-size: 16px;
            font-weight: 600;
            color: var(--eh-text-primary);
            margin: 0;
        }

        .emoji-helper-btn {
            background: none;
            border: none;
            cursor: pointer;
            padding: 8px;
            border-radius: 8px;
            color: var(--eh-text-secondary);
            transition: var(--eh-transition);
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 4px;
        }

        .emoji-helper-btn:hover {
            background: var(--eh-hover-bg);
            color: var(--eh-text-primary);
        }

        .emoji-helper-btn.close {
            font-size: 20px;
            width: 32px;
            height: 32px;
            justify-content: center;
            padding: 0;
        }

        /* 搜索区域 */
        .emoji-helper-search-area {
            padding: 16px 20px;
            border-bottom: 1px solid var(--eh-border-color);
        }

        .emoji-helper-search-container {
            position: relative;
            display: flex;
            gap: 8px;
        }

        .emoji-helper-search-input {
            flex: 1;
            padding: 12px 16px;
            border: 1px solid var(--eh-border-color);
            border-radius: 8px;
            background: var(--eh-bg-primary);
            color: var(--eh-text-primary);
            font-size: 14px;
            transition: var(--eh-transition);
            outline: none;
        }

        .emoji-helper-search-input:focus {
            border-color: var(--eh-accent-color);
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
        }

        .emoji-helper-search-btn {
            padding: 12px 16px;
            background: var(--eh-accent-color);
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            transition: var(--eh-transition);
        }

        .emoji-helper-search-btn:hover {
            background: var(--eh-accent-hover);
        }

        .emoji-helper-search-btn:disabled {
            background: var(--eh-text-secondary);
            cursor: not-allowed;
        }

        /* 分类标签 */
        .emoji-helper-tabs {
            display: flex;
            padding: 0 20px;
            background: var(--eh-bg-secondary);
            border-bottom: 1px solid var(--eh-border-color);
            overflow-x: auto;
            min-height: 48px; /* 添加这行 */
            align-items: center; /* 添加这行 */
        }

        .emoji-helper-tab {
            padding: 12px 16px;
            background: none;
            border: none;
            color: var(--eh-text-secondary);
            cursor: pointer;
            font-size: 13px;
            font-weight: 500;
            white-space: nowrap;
            border-bottom: 2px solid transparent;
            transition: var(--eh-transition);
        }

        .emoji-helper-tab:hover {
            color: var(--eh-text-primary);
        }

        .emoji-helper-tab.active {
            color: var(--eh-accent-color);
            border-bottom-color: var(--eh-accent-color);
        }

        /* 内容区域 */
        .emoji-helper-content {
            flex: 1;
            overflow-y: auto;
            padding: 16px 20px;
            max-height: 320px;
        }

        .emoji-helper-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
            gap: 8px;
        }

        .emoji-helper-item {
            aspect-ratio: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            border: 1px solid transparent;
            border-radius: 8px;
            cursor: pointer;
            transition: var(--eh-transition);
            background: var(--eh-bg-tertiary);
            position: relative;
            overflow: hidden;
        }

        .emoji-helper-item:hover {
            border-color: var(--eh-accent-color);
            background: var(--eh-hover-bg);
            transform: scale(1.05);
        }

        .emoji-helper-item.emoji-item {
            font-size: 24px;
        }

        .emoji-helper-item.gif-item img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            border-radius: 6px;
        }

        .emoji-helper-item .item-actions {
            position: absolute;
            top: 4px;
            right: 4px;
            display: none;
            gap: 2px;
        }

        .emoji-helper-item:hover .item-actions {
            display: flex;
        }

        .item-action-btn {
            width: 20px;
            height: 20px;
            background: rgba(0,0,0,0.7);
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        /* 设置面板 */
        .emoji-helper-settings-panel {
            position: fixed;
            top: 86px;
            left: 450px;
            width: 360px;
            max-height: 600px;
            background: var(--eh-bg-primary);
            border: 1px solid var(--eh-border-color);
            border-radius: var(--eh-border-radius);
            box-shadow: var(--eh-shadow);
            z-index: 10002;
            font-family: var(--eh-font);
            display: none;
            flex-direction: column;
            overflow: hidden;
            max-height: calc(100vh - 40px);
            overflow-y: auto;

        }

        .emoji-helper-settings-panel.show {
            display: flex;
        }

        .settings-content {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
        }

        .setting-group {
            margin-bottom: 24px;
        }

        .setting-group:last-child {
            margin-bottom: 0;
        }

        .setting-label {
            display: block;
            font-size: 14px;
            font-weight: 500;
            color: var(--eh-text-primary);
            margin-bottom: 8px;
        }

        .setting-input {
            width: 100%;
            padding: 10px 12px;
            border: 1px solid var(--eh-border-color);
            border-radius: 6px;
            background: var(--eh-bg-primary);
            color: var(--eh-text-primary);
            font-size: 14px;
            transition: var(--eh-transition);
        }

        .setting-input:focus {
            outline: none;
            border-color: var(--eh-accent-color);
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
        }

        .setting-checkbox {
            display: flex;
            align-items: center;
            gap: 8px;
            cursor: pointer;
        }

        .setting-checkbox input[type="checkbox"] {
            margin: 0;
        }

        .settings-actions {
            padding: 16px 20px;
            border-top: 1px solid var(--eh-border-color);
            display: grid; /* 改为grid布局 */
            grid-template-columns: repeat(3, 1fr); /* 每行3个按钮 */
            gap: 8px;
            justify-items: stretch; /* 让按钮填满网格 */
        }




        .settings-btn {
            padding: 10px 12px; /* 减少左右padding */
            border: 1px solid var(--eh-border-color);
            border-radius: 6px;
            background: var(--eh-bg-primary);
            color: var(--eh-text-primary);
            cursor: pointer;
            font-size: 12px; /* 减小字体 */
            transition: var(--eh-transition);
            white-space: nowrap; /* 防止文字换行 */
            text-align: center;
            min-height: 36px; /* 统一按钮高度 */
        }


        .settings-btn.primary {
            background: var(--eh-accent-color);
            color: white;
            border-color: var(--eh-accent-color);
        }

        .settings-btn:hover {
            background: var(--eh-hover-bg);
        }

        .settings-btn.primary:hover {
            background: var(--eh-accent-hover);
        }

        .settings-btn.danger {
            background: var(--eh-danger-color);
            color: white;
            border-color: var(--eh-danger-color);
        }

        /* 文字编辑器 */
        .emoji-helper-text-editor {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 600px;
            max-height: 80vh;
            background: var(--eh-bg-primary);
            border: 1px solid var(--eh-border-color);
            border-radius: var(--eh-border-radius);
            box-shadow: var(--eh-shadow);
            z-index: 10003;
            font-family: var(--eh-font);
            display: none;
            flex-direction: column;
            overflow: hidden;
        }

        .text-editor-content {
            display: flex;
            flex: 1;
            overflow: hidden;
        }

        .editor-preview {
            flex: 1;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            background: var(--eh-bg-tertiary);
            position: relative;
        }

        .editor-preview canvas {
            max-width: 100%;
            max-height: 300px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
        }

        .preview-overlay {
            position: absolute;
            bottom: 10px;
            left: 10px;
            right: 10px;
            text-align: center;
            font-size: 12px;
            color: var(--eh-text-secondary);
        }

        .editor-controls {
            width: 250px;
            padding: 20px;
            border-left: 1px solid var(--eh-border-color);
            overflow-y: auto;
        }

        .control-group {
            margin-bottom: 16px;
        }

        .control-label {
            display: block;
            font-size: 13px;
            font-weight: 500;
            color: var(--eh-text-primary);
            margin-bottom: 6px;
        }

        .text-editor-actions {
            padding: 16px 20px;
            border-top: 1px solid var(--eh-border-color);
            display: flex;
            gap: 8px;
            justify-content: flex-end;
        }

        .editor-btn {
            padding: 10px 16px;
            border: 1px solid var(--eh-border-color);
            border-radius: 6px;
            background: var(--eh-bg-primary);
            color: var(--eh-text-primary);
            cursor: pointer;
            font-size: 14px;
            transition: var(--eh-transition);
        }

        .editor-btn.primary {
            background: var(--eh-accent-color);
            color: white;
            border-color: var(--eh-accent-color);
        }

        .editor-btn:hover:not(:disabled) {
            background: var(--eh-hover-bg);
        }

        .editor-btn.primary:hover:not(:disabled) {
            background: var(--eh-accent-hover);
        }

        .editor-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        /* 消息提示 */
        .emoji-helper-message {
            position: fixed;
            top: 20px;
            right: 20px;
            background: var(--eh-success-color);
            color: white;
            padding: 12px 16px;
            border-radius: 8px;
            box-shadow: var(--eh-shadow);
            z-index: 10004;
            font-family: var(--eh-font);
            font-size: 14px;
            transform: translateX(100%);
            transition: var(--eh-transition);
        }

        .emoji-helper-message.show {
            transform: translateX(0);
        }

        .emoji-helper-message.error {
            background: var(--eh-danger-color);
        }

        .emoji-helper-message.warning {
            background: var(--eh-warning-color);
        }

        /* 加载状态 */
        .emoji-helper-loading {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 40px;
            color: var(--eh-text-secondary);
            font-size: 14px;
        }

        .emoji-helper-loading::before {
            content: '';
            width: 16px;
            height: 16px;
            border: 2px solid var(--eh-border-color);
            border-top-color: var(--eh-accent-color);
            border-radius: 50%;
            margin-right: 8px;
            animation: eh-spin 1s linear infinite;
        }

        @keyframes eh-spin {
            to { transform: rotate(360deg); }
        }

        /* 空状态 */
        .emoji-helper-empty {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 40px 20px;
            color: var(--eh-text-secondary);
            text-align: center;
        }

        .emoji-helper-empty-icon {
            font-size: 48px;
            margin-bottom: 16px;
            opacity: 0.5;
        }

        /* 响应式 */
        @media (max-width: 768px) {
            .emoji-helper-panel {
                width: calc(100vw - 40px);
                left: 20px;
                right: 20px;
            }

            .emoji-helper-settings-panel {
                width: calc(100vw - 40px);
                left: 20px;
                right: 20px;
            }

            .emoji-helper-text-editor {
                width: calc(100vw - 40px);
                left: 20px;
                right: 20px;
                transform: translateY(-50%);
                top: 50%;
            }

            .text-editor-content {
                flex-direction: column;
            }

            .editor-controls {
                width: 100%;
                border-left: none;
                border-top: 1px solid var(--eh-border-color);
            }
        }

        /* 滚动条样式 */
        .emoji-helper-content::-webkit-scrollbar,
        .settings-content::-webkit-scrollbar,
        .editor-controls::-webkit-scrollbar {
            width: 6px;
        }

        .emoji-helper-content::-webkit-scrollbar-track,
        .settings-content::-webkit-scrollbar-track,
        .editor-controls::-webkit-scrollbar-track {
            background: var(--eh-bg-tertiary);
        }

        .emoji-helper-content::-webkit-scrollbar-thumb,
        .settings-content::-webkit-scrollbar-thumb,
        .editor-controls::-webkit-scrollbar-thumb {
            background: var(--eh-border-color);
            border-radius: 3px;
        }

        .emoji-helper-content::-webkit-scrollbar-thumb:hover,
        .settings-content::-webkit-scrollbar-thumb:hover,
        .editor-controls::-webkit-scrollbar-thumb:hover {
            background: var(--eh-text-secondary);
        }
    `);

    // 继续其余功能函数...

    // 🏃‍♀️ 初始化函数
    function initEmojiHelper() {
        Logger.info('INIT', '开始初始化表情助手');

        try {
            // 初始化配置
            Config.init();

            // 加载自定义GIF
            const savedGifs = Storage.get('customGifs', null);
            if (savedGifs && Array.isArray(savedGifs) && savedGifs.length > 0) {
                customGifs = savedGifs;
                Logger.info('INIT', `加载了 ${customGifs.length} 个自定义GIF`);
            } else {
                Storage.set('customGifs', customGifs);
                Logger.info('INIT', '使用默认GIF集');
            }

            // 创建浮动按钮
            createFloatingButton();

            // 应用主题
            applyTheme();

            // 检查更新(异步,不阻塞初始化)
            if (Config.get('autoUpdate')) {
                setTimeout(() => {
                    CloudDataManager.checkAndUpdate();
                }, 2000);
            }

            Logger.info('INIT', '表情助手初始化完成');

        } catch (error) {
            Logger.error('INIT', '初始化失败', error);
        }
    }

    // 🎨 应用主题
    function applyTheme() {
        const theme = Config.get('theme');
        document.documentElement.className = document.documentElement.className
            .replace(/emoji-helper-(light|dark)/g, '') + ` emoji-helper-${theme}`;
        Logger.debug('UI', '应用主题', theme);
    }

    // 🔄 刷新当前视图
    function refreshCurrentView() {
        if (emojiPanel && emojiPanel.style.display !== 'none') {
            const activeTab = emojiPanel.querySelector('.emoji-helper-tab.active');
            if (activeTab) {
                const category = activeTab.dataset.category;
                Logger.debug('UI', '刷新当前视图', category);
                showCategory(category);
            }
        }
    }

    // 🗑️ 清理GIF缓存
    function clearGifCache() {
        webGifCache.clear();
        CacheManager.clear('gif');
        CacheManager.clear('search');
        Logger.info('CACHE', '已清理GIF缓存');
    }

    // 🔄 更新所有文本
    function updateAllText() {
        Logger.debug('UI', '更新界面语言');
        if (emojiPanel) {
            createEmojiPanel();
        }
        if (settingsPanel) {
            createSettingsPanel();
        }
    }

    // 🎈 创建浮动按钮
    function createFloatingButton() {
        if (floatingButton) {
            floatingButton.remove();
        }

        floatingButton = document.createElement('button');
        floatingButton.className = 'emoji-helper-floating-btn';
        floatingButton.innerHTML = '😀';
        floatingButton.title = t().title;

        floatingButton.addEventListener('click', toggleEmojiPanel);

        document.body.appendChild(floatingButton);
        updateFloatingButtonVisibility();

        Logger.debug('UI', '浮动按钮创建完成');
    }

    // 🔄 切换表情面板
    function toggleEmojiPanel() {
        if (!emojiPanel) {
            createEmojiPanel();
        }

        const isVisible = emojiPanel.style.display !== 'none';

        if (isVisible) {
            hideEmojiPanel();
        } else {
            showEmojiPanel();
        }

        Logger.debug('UI', '切换表情面板', !isVisible);
    }

    // 👁️ 显示表情面板
    function showEmojiPanel() {
        if (!emojiPanel) {
            createEmojiPanel();
        }

        emojiPanel.classList.add('show');
        emojiPanel.style.display = 'flex';

        // 应用保存的位置
        updatePanelPosition();

        // 默认显示“我的GIF”
        showCategory('custom');

        // 聚焦搜索框
        const searchInput = emojiPanel.querySelector('.emoji-helper-search-input');
        if (searchInput) {
            setTimeout(() => searchInput.focus(), 100);
        }

        Logger.info('UI', '显示表情面板');
    }

    // 🙈 隐藏表情面板
    function hideEmojiPanel() {
        if (emojiPanel) {
            emojiPanel.classList.remove('show');
            emojiPanel.style.display = 'none';
            Logger.debug('UI', '隐藏表情面板');
        }
    }

    // 🏗️ 创建表情面板
    function createEmojiPanel() {
        if (emojiPanel) {
            emojiPanel.remove();
        }

        const lang = t();
        const panel = document.createElement('div');
        panel.className = 'emoji-helper-panel';
        panel.id = 'emoji-helper-main-panel';

        panel.innerHTML = `
            <div class="emoji-helper-header">
                <div class="emoji-helper-title">${lang.title}</div>
                <div style="display: flex; gap: 8px;">
                    <button class="emoji-helper-btn settings-btn" title="${lang.settings}">⚙️</button>
                    <button class="emoji-helper-btn close" title="关闭">×</button>
                </div>
            </div>
            <div class="emoji-helper-search-area">
                <div class="emoji-helper-search-container">
                    <input type="text" class="emoji-helper-search-input" placeholder="${lang.search}" maxlength="50">
                    <button class="emoji-helper-search-btn">${lang.searchBtn}</button>
                </div>
            </div>
            <div class="emoji-helper-tabs">
                <button class="emoji-helper-tab active" data-category="custom">${lang.categories.custom}</button>
                <button class="emoji-helper-tab" data-category="smileys">${lang.categories.smileys}</button>
                <button class="emoji-helper-tab" data-category="webGif">${lang.categories.webGif}</button>
            </div>
            <div class="emoji-helper-content">
                <div class="emoji-helper-grid"></div>
            </div>
        `;

        emojiPanel = panel;
        document.body.appendChild(panel);

        bindEmojiPanelEvents();
        DragManager.makeDraggable(panel);

        Logger.debug('UI', '表情面板创建完成');
    }

    // 🔗 绑定表情面板事件
    function bindEmojiPanelEvents() {
        if (!emojiPanel) return;

        const closeBtn = emojiPanel.querySelector('.close');
        const settingsBtn = emojiPanel.querySelector('.settings-btn');
        const searchInput = emojiPanel.querySelector('.emoji-helper-search-input');
        const searchBtn = emojiPanel.querySelector('.emoji-helper-search-btn');
        const tabs = emojiPanel.querySelectorAll('.emoji-helper-tab');

        if (closeBtn) {
            closeBtn.addEventListener('click', hideEmojiPanel);
        }

        if (settingsBtn) {
            settingsBtn.addEventListener('click', toggleSettingsPanel);
        }

        if (searchInput) {
            searchInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    performSearch();
                }
            });

            searchInput.addEventListener('input', debounce(() => {
                if (searchInput.value.trim()) {
                    performSearch();
                }
            }, 800));
        }

        if (searchBtn) {
            searchBtn.addEventListener('click', performSearch);
        }

        tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                const category = tab.dataset.category;
                if (category) {
                    setActiveTab(tab);
                    showCategory(category);
                }
            });
        });

        Logger.debug('UI', '表情面板事件绑定完成');
    }

    async function performSearch() {
        const searchInput = emojiPanel?.querySelector('.emoji-helper-search-input');
        if (!searchInput) return;

        const query = searchInput.value.trim();
        if (!query) {
            Logger.debug('SEARCH', '搜索词为空');
            return;
        }

        const reqId = ++searchRequestId; // 本次搜索的 ID
        if (isSearching) {
            Logger.debug('SEARCH', '搜索进行中,仍然记录最新reqId以丢弃旧结果');
        }

        Logger.info('SEARCH', '开始搜索', { query, reqId });
        setSearching(true);

        try {
            // 1) 本地结果:先立即渲染,保证UI不卡
            const emojiResults = searchEmojis(query);
            const customResults = searchCustomGifs(query);
            const localResults = [
                ...emojiResults.map(e => ({ type: 'emoji', data: e })),
                ...customResults.map(g => ({ type: 'gif', data: g })),
            ];
            requestAnimationFrame(() => displaySearchResults(localResults, query));

            // 2) 第三方:最多等3秒;失败/超时就放弃
            const webPromise = GifSearchAPI.searchGifs(query, 12, 3000)
            .catch(err => { Logger.warn('SEARCH', '第三方失败', err); return []; });
            const webResults = await eh_withTimeout(webPromise, 3000);

            // 3) 过期保护
            if (reqId !== searchRequestId) {
                Logger.warn('SEARCH', '丢弃过期搜索结果', { query, reqId, latest: searchRequestId });
                return;
            }

            // 4) 成功在3s内返回才合并
            if (webResults !== '__EH_TIMEOUT__' && Array.isArray(webResults)) {
                const merged = [
                    ...localResults,
                    ...webResults.map(g => ({ type: 'webGif', data: g })),
                ];
                displaySearchResults(merged, query);
                Logger.info('SEARCH', '搜索完成(含第三方)', {
                    query, emoji: emojiResults.length, custom: customResults.length, web: webResults.length, total: merged.length
                });
            } else {
                Logger.warn('SEARCH', '第三方搜索超时(3s),仅显示本地结果');
            }
        } catch (error) {
            Logger.error('SEARCH', '搜索失败', { query, error });
            showMessage(t().messages.apiError, 'error');
        } finally {
            setSearching(false);
        }
    }

    // 设置搜索状态
    function setSearching(searching) {
        isSearching = searching;
        const searchBtn = emojiPanel?.querySelector('.emoji-helper-search-btn');
        if (searchBtn) {
            searchBtn.disabled = searching;
            searchBtn.textContent = searching ? t().messages.searching : t().searchBtn;
        }
    }

    // 搜索表情符号
    // 修复表情符号搜索函数
    function searchEmojis(query) {
        const lowerQuery = query.toLowerCase();

        // 表情符号关键词映射
        const emojiKeywords = {
            // 笑脸和情感
            '😀': ['笑', '开心', '高兴', 'smile', 'happy', 'grin'],
            '😃': ['大笑', '开心', '高兴', 'smile', 'happy', 'joy'],
            '😄': ['哈哈', '大笑', '开心', 'laugh', 'happy', 'joy'],
            '😁': ['嘻嘻', '开心', '笑', 'grin', 'happy', 'smile'],
            '😅': ['苦笑', '尴尬', '汗', 'sweat', 'laugh', 'nervous'],
            '😂': ['笑哭', '大笑', '哈哈', 'joy', 'laugh', 'cry'],
            '🤣': ['笑得满地打滚', '大笑', '哈哈', 'rofl', 'laugh', 'roll'],
            '😊': ['微笑', '开心', '高兴', 'smile', 'happy', 'sweet'],
            '😇': ['天使', '纯洁', '善良', 'angel', 'innocent', 'halo'],
            '🙂': ['微笑', '开心', '友好', 'smile', 'happy', 'friendly'],
            '😉': ['眨眼', '调皮', '暗示', 'wink', 'playful', 'hint'],
            '😌': ['满足', '放松', '舒服', 'relieved', 'peaceful', 'calm'],
            '😍': ['爱心眼', '喜爱', '迷恋', 'love', 'heart', 'adore'],
            '🥰': ['可爱', '爱心', '甜蜜', 'cute', 'love', 'sweet'],
            '😘': ['飞吻', '亲吻', '爱', 'kiss', 'love', 'blow'],
            '😗': ['亲吻', '嘟嘴', '吻', 'kiss', 'pucker', 'lips'],
            '😙': ['亲吻', '吻', '嘟嘴', 'kiss', 'pucker', 'cute'],
            '😚': ['亲吻', '吻', '闭眼', 'kiss', 'closed', 'eyes'],
            '😋': ['好吃', '美味', '馋', 'yum', 'delicious', 'tasty'],
            '😛': ['吐舌', '调皮', '淘气', 'tongue', 'playful', 'silly'],
            '😝': ['吐舌', '调皮', '鬼脸', 'tongue', 'playful', 'wink'],
            '😜': ['调皮', '吐舌', '眨眼', 'wink', 'tongue', 'playful'],
            '🤪': ['疯狂', '搞怪', '调皮', 'crazy', 'wild', 'silly'],
            '🤨': ['怀疑', '质疑', '挑眉', 'skeptical', 'doubt', 'eyebrow'],
            '🧐': ['思考', '研究', '仔细', 'thinking', 'study', 'monocle'],
            '🤓': ['书呆子', '学霸', '眼镜', 'nerd', 'geek', 'glasses'],
            '😎': ['酷', '帅', '墨镜', 'cool', 'awesome', 'sunglasses'],
            '🤩': ['崇拜', '明星', '闪闪', 'star', 'worship', 'amazed'],
            '🥳': ['庆祝', '派对', '生日', 'party', 'celebrate', 'birthday'],
            '😏': ['得意', '坏笑', '阴险', 'smirk', 'sly', 'mischievous'],
            '😒': ['无聊', '无语', '翻白眼', 'bored', 'unamused', 'meh'],
            '😞': ['失望', '沮丧', '难过', 'disappointed', 'sad', 'down'],
            '😔': ['沮丧', '难过', '失落', 'pensive', 'sad', 'thoughtful'],
            '😟': ['担心', '忧虑', '不安', 'worried', 'anxious', 'concern'],
            '😕': ['困惑', '疑惑', '不解', 'confused', 'puzzled', 'uncertain'],
            '🙁': ['皱眉', '不高兴', '难过', 'frown', 'sad', 'unhappy'],
            '☹️': ['不高兴', '难过', '皱眉', 'frown', 'sad', 'unhappy'],
            '😣': ['痛苦', '难受', '挣扎', 'pain', 'struggle', 'persevere'],
            '😖': ['痛苦', '难受', '纠结', 'confounded', 'pain', 'struggle'],
            '😫': ['疲惫', '累', '痛苦', 'tired', 'weary', 'exhausted'],
            '😩': ['疲惫', '累', '无奈', 'weary', 'tired', 'helpless'],
            '🥺': ['可怜', '委屈', '乞求', 'pleading', 'pitiful', 'beg'],
            '😢': ['哭', '难过', '伤心', 'cry', 'sad', 'tears'],
            '😭': ['大哭', '伤心', '痛哭', 'cry', 'sob', 'wail'],
            '😤': ['生气', '愤怒', '怒气', 'angry', 'mad', 'huffing'],
            '😠': ['生气', '愤怒', '怒火', 'angry', 'mad', 'rage'],
            '😡': ['愤怒', '生气', '怒', 'angry', 'mad', 'furious'],
            '🤬': ['脏话', '愤怒', '生气', 'swearing', 'angry', 'cursing'],
            '🤯': ['震惊', '爆炸', '惊讶', 'shocked', 'mind-blown', 'exploding'],
            '😳': ['脸红', '害羞', '震惊', 'blushing', 'shy', 'flushed'],
            '🥵': ['热', '出汗', '发烧', 'hot', 'sweat', 'fever'],
            '🥶': ['冷', '寒冷', '冰', 'cold', 'freezing', 'ice'],
            '😱': ['恐惧', '害怕', '惊恐', 'fear', 'scared', 'screaming'],
            '😨': ['害怕', '恐惧', '惊吓', 'fearful', 'scared', 'anxious'],
            '😰': ['紧张', '出汗', '害怕', 'anxious', 'nervous', 'cold-sweat'],
            '😥': ['难过', '伤心', '失望', 'sad', 'disappointed', 'relieved'],
            '😓': ['出汗', '紧张', '累', 'sweat', 'nervous', 'tired'],
            '🤗': ['拥抱', '温暖', '友好', 'hug', 'warm', 'friendly'],
            '🤔': ['思考', '想', '考虑', 'thinking', 'consider', 'ponder'],
            '🤭': ['偷笑', '掩嘴', '害羞', 'giggle', 'shy', 'cover-mouth'],
            '🤫': ['安静', '嘘', '保密', 'quiet', 'shh', 'secret'],
            '🤥': ['撒谎', '长鼻子', '谎言', 'lie', 'pinocchio', 'liar'],
            '😶': ['无语', '沉默', '闭嘴', 'speechless', 'silent', 'no-mouth'],
            '😐': ['面无表情', '无感', '冷漠', 'neutral', 'expressionless', 'meh'],
            '😑': ['无语', '翻白眼', '无表情', 'expressionless', 'blank', 'meh'],
            '😬': ['尴尬', '龇牙', '紧张', 'grimace', 'awkward', 'nervous'],
            '🙄': ['翻白眼', '无语', '鄙视', 'eye-roll', 'whatever', 'annoyed'],
            '😯': ['惊讶', '震惊', '哇', 'surprised', 'shocked', 'wow'],
            '😦': ['担心', '不安', '惊讶', 'worried', 'frowning', 'concerned'],
            '😧': ['痛苦', '担心', '不安', 'anguished', 'worried', 'pain'],
            '😮': ['惊讶', '震惊', '张嘴', 'surprised', 'shocked', 'open-mouth'],
            '😲': ['震惊', '惊讶', '哇', 'astonished', 'shocked', 'amazed'],
            '🥱': ['打哈欠', '困', '无聊', 'yawn', 'sleepy', 'tired'],
            '😴': ['睡觉', '困', '休息', 'sleep', 'tired', 'zzz'],
            '🤤': ['流口水', '想要', '渴望', 'drool', 'desire', 'want'],
            '😪': ['困', '疲惫', '打瞌睡', 'sleepy', 'tired', 'drowsy'],
            '😵': ['晕', '头晕', '不省人事', 'dizzy', 'knocked-out', 'unconscious'],
            '🤐': ['闭嘴', '拉链', '保密', 'zip', 'silence', 'sealed'],
            '🥴': ['晕', '醉', '头晕', 'woozy', 'drunk', 'dizzy'],
            '🤢': ['恶心', '想吐', '不舒服', 'nausea', 'sick', 'vomit'],
            '🤮': ['呕吐', '恶心', '吐', 'vomit', 'puke', 'sick'],
            '🤧': ['打喷嚏', '感冒', '生病', 'sneeze', 'cold', 'sick'],
            '😷': ['口罩', '生病', '感冒', 'mask', 'sick', 'medical'],
            '🤒': ['发烧', '生病', '温度计', 'fever', 'sick', 'thermometer'],
            '🤕': ['受伤', '头痛', '绷带', 'injured', 'hurt', 'bandage'],
            '🤑': ['贪钱', '发财', '金钱', 'money', 'rich', 'greedy'],
            '🤠': ['牛仔', '帽子', '西部', 'cowboy', 'hat', 'western'],
            '😈': ['恶魔', '坏', '邪恶', 'devil', 'evil', 'mischievous'],
            '👿': ['愤怒', '恶魔', '生气', 'angry', 'devil', 'imp'],
            '👹': ['日本鬼', '恶魔', '怪物', 'ogre', 'demon', 'monster'],
            '👺': ['日本鬼', '恶魔', '怪物', 'goblin', 'demon', 'monster'],
            '🤡': ['小丑', '搞笑', '马戏团', 'clown', 'funny', 'circus'],
            '💩': ['便便', '大便', '屎', 'poop', 'shit', 'pile'],
            '👻': ['鬼', '幽灵', '鬼魂', 'ghost', 'spirit', 'boo'],
            '💀': ['骷髅', '死亡', '头骨', 'skull', 'death', 'bone'],
            '☠️': ['骷髅', '死亡', '危险', 'skull', 'death', 'poison'],
            '👽': ['外星人', '外星', '宇宙', 'alien', 'extraterrestrial', 'space'],
            '👾': ['游戏', '外星怪物', '电子游戏', 'alien-monster', 'game', 'pixel'],
            '🤖': ['机器人', '科技', '人工智能', 'robot', 'ai', 'technology'],
            '🎃': ['南瓜', '万圣节', '杰克灯', 'pumpkin', 'halloween', 'jack-o-lantern'],
            '😺': ['猫', '笑猫', '开心猫', 'cat', 'happy', 'smile'],
            '😸': ['猫', '大笑猫', '开心猫', 'cat', 'joy', 'grin'],
            '😹': ['猫', '笑哭猫', '流泪猫', 'cat', 'joy', 'tears'],
            '😻': ['猫', '爱心眼猫', '喜爱猫', 'cat', 'love', 'heart-eyes'],
            '😼': ['猫', '得意猫', '坏笑猫', 'cat', 'smirk', 'sly'],
            '😽': ['猫', '亲吻猫', '吻猫', 'cat', 'kiss', 'kissing'],
            '🙀': ['猫', '惊讶猫', '害怕猫', 'cat', 'surprised', 'weary'],
            '😿': ['猫', '哭猫', '伤心猫', 'cat', 'cry', 'sad'],
            '😾': ['猫', '生气猫', '愤怒猫', 'cat', 'angry', 'pouting']
        };

        // 精确匹配表情符号
        const matchedEmojis = [];

        for (const [emoji, keywords] of Object.entries(emojiKeywords)) {
            // 检查关键词是否匹配
            const isMatch = keywords.some(keyword =>
                                          keyword.includes(lowerQuery) ||
                                          lowerQuery.includes(keyword) ||
                                          keyword.startsWith(lowerQuery)
                                         );

            if (isMatch) {
                matchedEmojis.push(emoji);
            }
        }

        // 如果没有匹配的表情,返回部分默认表情
        if (matchedEmojis.length === 0) {
            return defaultEmojis.slice(0, 10);
        }

        Logger.debug('SEARCH', `表情符号搜索: "${query}" 匹配到 ${matchedEmojis.length} 个`, matchedEmojis);
        return matchedEmojis;
    }


    // 搜索自定义GIF
    function searchCustomGifs(query) {
        const lowerQuery = query.toLowerCase();
        return customGifs.filter(gif => {
            return gif.keywords.some(keyword =>
                                     keyword.toLowerCase().includes(lowerQuery)
                                    ) || gif.alt.toLowerCase().includes(lowerQuery);
        });
    }

    // 显示搜索结果
    function displaySearchResults(results, query) {
        const content = emojiPanel?.querySelector('.emoji-helper-content');
        const grid = content?.querySelector('.emoji-helper-grid');
        if (!grid) return;

        // 清除活动标签
        const tabs = emojiPanel.querySelectorAll('.emoji-helper-tab');
        tabs.forEach(tab => tab.classList.remove('active'));

        if (results.length === 0) {
            showEmptyState(t().messages.noResults);
            return;
        }

        grid.innerHTML = '';

        results.forEach(result => {
            const item = document.createElement('div');
            item.className = 'emoji-helper-item';

            if (result.type === 'emoji') {
                item.classList.add('emoji-item');
                item.textContent = result.data;
                item.addEventListener('click', () => insertEmoji(result.data));
            } else {
                item.classList.add('gif-item');
                const img = document.createElement('img');
                img.src = result.data.previewUrl || result.data.url;
                img.alt = result.data.alt || result.data.title;
                img.loading = 'lazy';

                img.onerror = () => {
                    item.style.display = 'none';
                };

                const actions = document.createElement('div');
                actions.className = 'item-actions';
                actions.innerHTML = `
                    <button class="item-action-btn" title="添加文字">T</button>
                `;

                actions.querySelector('.item-action-btn').addEventListener('click', (e) => {
                    e.stopPropagation();
                    TextEditor.open(result.data.url);
                });

                item.appendChild(img);
                item.appendChild(actions);
                item.addEventListener('click', () => insertGif(result.data));
            }

            grid.appendChild(item);
        });

        Logger.debug('UI', '搜索结果显示完成', { query, count: results.length });
    }

    // 设置活动标签
    function setActiveTab(activeTab) {
        const tabs = emojiPanel?.querySelectorAll('.emoji-helper-tab');
        tabs?.forEach(tab => tab.classList.remove('active'));
        activeTab.classList.add('active');
    }

    // 显示分类内容
    function showCategory(category) {
        const content = emojiPanel?.querySelector('.emoji-helper-content');
        const grid = content?.querySelector('.emoji-helper-grid');
        if (!grid) return;

        Logger.debug('UI', '显示分类', category);

        switch (category) {
            case 'smileys':
                displayEmojis();
                break;
            case 'custom':
                displayCustomGifs();
                break;
            case 'webGif':
                showEmptyState(t().messages.searchHint);
                break;
        }
    }

    // 显示表情符号
    function displayEmojis() {
        const grid = emojiPanel?.querySelector('.emoji-helper-grid');
        if (!grid) return;

        grid.innerHTML = '';

        defaultEmojis.forEach(emoji => {
            const item = document.createElement('div');
            item.className = 'emoji-helper-item emoji-item';
            item.textContent = emoji;
            item.addEventListener('click', () => insertEmoji(emoji));
            grid.appendChild(item);
        });

        Logger.debug('UI', '表情符号显示完成', defaultEmojis.length);
    }

    // 显示自定义GIF
    function displayCustomGifs() {
        const grid = emojiPanel?.querySelector('.emoji-helper-grid');
        if (!grid) return;

        grid.innerHTML = '';

        if (customGifs.length === 0) {
            showEmptyState('暂无自定义GIF');
            return;
        }

        customGifs.forEach(gif => {
            const item = document.createElement('div');
            item.className = 'emoji-helper-item gif-item';

            const img = document.createElement('img');
            img.src = gif.url;
            img.alt = gif.alt;
            img.loading = 'lazy';

            img.onerror = () => {
                item.style.display = 'none';
                Logger.warn('UI', 'GIF加载失败', gif.url);
            };

            const actions = document.createElement('div');
            actions.className = 'item-actions';
            actions.innerHTML = `
                <button class="item-action-btn" title="添加文字">T</button>
            `;

            actions.querySelector('.item-action-btn').addEventListener('click', (e) => {
                e.stopPropagation();
                TextEditor.open(gif.url);
            });

            item.appendChild(img);
            item.appendChild(actions);
            item.addEventListener('click', () => insertGif(gif));
            grid.appendChild(item);
        });

        Logger.debug('UI', '自定义GIF显示完成', customGifs.length);
    }

    // 显示空状态
    function showEmptyState(message) {
        const grid = emojiPanel?.querySelector('.emoji-helper-grid');
        if (!grid) return;

        grid.innerHTML = `
            <div class="emoji-helper-empty">
                <div class="emoji-helper-empty-icon">🤔</div>
                <div>${message}</div>
            </div>
        `;
    }

    // 插入表情符号
    function insertEmoji(emoji) {
        Logger.info('EVENT', '插入表情符号', emoji);
        insertToActiveElement(emoji);

        if (Config.get('autoInsert')) {
            hideEmojiPanel();
        }
    }

    // 插入 GIF / 图片:在 linux.do 直接插入 Markdown 链接;其他站点保持原逻辑
    async function insertGif(gif) {
        const isDiscourse = location.hostname.endsWith('linux.do');
        const textArea = document.querySelector('.d-editor-input');

        if (isDiscourse && textArea) {
            // 参照“人家的机制”,插入 Markdown(避免 blob:)
            const alt = (gif.alt || gif.title || 'gif').replace(/\|/g, ' ');
            const md = `![${alt}|2048x2048,10%](${gif.url})`;
            insertToActiveElement(md);
            if (Config.get('autoInsert')) hideEmojiPanel();
            return;
        }

        // 非 linux.do:沿用原“复制到剪贴板”的逻辑
        Logger.info('EVENT', '复制图像到剪贴板', { url: gif.url });
        try {
            await copyImageLikeBrowser(gif.url);
            showMessage(t().messages.copied);
        } catch (err) {
            Logger.warn('EVENT', '复制图像失败,回退为插入链接', err);
            insertToActiveElement(gif.url);
        }
        if (Config.get('autoInsert')) hideEmojiPanel();
    }

    // 插入到活动元素
    function insertToActiveElement(content) {
        const activeElement = document.activeElement;

        if (activeElement && (
            activeElement.tagName === 'INPUT' ||
            activeElement.tagName === 'TEXTAREA' ||
            activeElement.contentEditable === 'true'
        )) {
            if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') {
                const start = activeElement.selectionStart;
                const end = activeElement.selectionEnd;
                const text = activeElement.value;

                activeElement.value = text.slice(0, start) + content + text.slice(end);
                activeElement.selectionStart = activeElement.selectionEnd = start + content.length;

                // 触发输入事件
                activeElement.dispatchEvent(new Event('input', { bubbles: true }));
            } else {
                // 对于contentEditable元素
                const selection = window.getSelection();
                if (selection.rangeCount > 0) {
                    const range = selection.getRangeAt(0);
                    range.deleteContents();

                    if (content.startsWith('http')) {
                        // 插入图片
                        const img = document.createElement('img');
                        img.src = content;
                        img.style.maxWidth = '200px';
                        img.style.height = 'auto';
                        range.insertNode(img);
                    } else {
                        // 插入文本
                        const textNode = document.createTextNode(content);
                        range.insertNode(textNode);
                    }

                    // 移动光标到插入内容后
                    range.setStartAfter(range.commonAncestorContainer.lastChild);
                    range.collapse(true);
                    selection.removeAllRanges();
                    selection.addRange(range);
                }
            }

            Logger.debug('EVENT', '内容已插入到活动元素', {
                tag: activeElement.tagName,
                contentLength: content.length
            });
        } else {
            // 复制到剪贴板作为备选方案
            copyToClipboard(content);
        }
    }

    // 复制到剪贴板
    function copyToClipboard(text) {
        try {
            if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(text).then(() => {
                    showMessage(t().messages.copied);
                    Logger.info('EVENT', '已复制到剪贴板(Clipboard API)', text.substring(0, 50));
                }).catch(() => {
                    fallbackCopy(text);
                });
            } else {
                fallbackCopy(text);
            }
        } catch (error) {
            Logger.warn('EVENT', '复制失败', error);
            fallbackCopy(text);
        }
    }

    // 备用复制方法
    function fallbackCopy(text) {
        try {
            const textarea = document.createElement('textarea');
            textarea.value = text;
            textarea.style.position = 'fixed';
            textarea.style.opacity = '0';
            document.body.appendChild(textarea);
            textarea.select();
            document.execCommand('copy');
            document.body.removeChild(textarea);
            showMessage(t().messages.copied);
            Logger.info('EVENT', '已复制到剪贴板(备用方法)', text.substring(0, 50));
        } catch (error) {
            Logger.error('EVENT', '备用复制方法失败', error);
        }
    }

    // 防抖函数
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // 切换设置面板
    function toggleSettingsPanel() {
        if (!settingsPanel) {
            createSettingsPanel();
        }

        const isVisible = settingsPanel.style.display !== 'none';

        if (isVisible) {
            hideSettingsPanel();
        } else {
            showSettingsPanel();
        }

        Logger.debug('UI', '切换设置面板', !isVisible);
    }

    // 显示设置面板
    function showSettingsPanel() {
        if (!settingsPanel) {
            createSettingsPanel();
        }

        settingsPanel.classList.add('show');
        settingsPanel.style.display = 'flex';
        updateSettingsPanelPosition();

        Logger.info('UI', '显示设置面板');
    }

    // 隐藏设置面板
    function hideSettingsPanel() {
        if (settingsPanel) {
            settingsPanel.classList.remove('show');
            settingsPanel.style.display = 'none';
            Logger.debug('UI', '隐藏设置面板');
        }
    }

    // 创建设置面板
    function createSettingsPanel() {
        if (settingsPanel) {
            settingsPanel.remove();
        }

        const lang = t();
        const panel = document.createElement('div');
        panel.className = 'emoji-helper-settings-panel';
        panel.id = 'emoji-helper-settings-panel';

        panel.innerHTML = `
            <div class="emoji-helper-header">
                <div class="emoji-helper-title">${lang.settingsPanel.title}</div>
                <button class="emoji-helper-btn close">×</button>
            </div>
            <div class="settings-content">
                <div class="setting-group">
                    <label class="setting-label">${lang.settingsPanel.language}</label>
                    <select class="setting-input" id="setting-lang">
                        <option value="zh-CN" ${Config.get('lang') === 'zh-CN' ? 'selected' : ''}>中文</option>
                        <option value="en" ${Config.get('lang') === 'en' ? 'selected' : ''}>English</option>
                    </select>
                </div>

                <div class="setting-group">
                    <label class="setting-label">${lang.settingsPanel.theme}</label>
                    <select class="setting-input" id="setting-theme">
                        <option value="light" ${Config.get('theme') === 'light' ? 'selected' : ''}>${lang.themes.light}</option>
                        <option value="dark" ${Config.get('theme') === 'dark' ? 'selected' : ''}>${lang.themes.dark}</option>
                    </select>
                </div>

                <div class="setting-group">
                    <label class="setting-checkbox">
                        <input type="checkbox" id="setting-auto-insert" ${Config.get('autoInsert') ? 'checked' : ''}>
                        <span>${lang.settingsPanel.autoInsert}</span>
                    </label>
                </div>

                <div class="setting-group">
                    <label class="setting-checkbox">
                        <input type="checkbox" id="setting-show-floating" ${Config.get('showFloatingButton') ? 'checked' : ''}>
                        <span>${lang.settingsPanel.showFloatingButton}</span>
                    </label>
                </div>

                <div class="setting-group">
                    <label class="setting-label">${lang.settingsPanel.gifSize}</label>
                    <select class="setting-input" id="setting-gif-size">
                        <option value="small" ${Config.get('gifSize') === 'small' ? 'selected' : ''}>${lang.sizes.small}</option>
                        <option value="medium" ${Config.get('gifSize') === 'medium' ? 'selected' : ''}>${lang.sizes.medium}</option>
                        <option value="large" ${Config.get('gifSize') === 'large' ? 'selected' : ''}>${lang.sizes.large}</option>
                    </select>
                </div>

                <div class="setting-group">
                    <label class="setting-label">${lang.settingsPanel.searchEngine}</label>
                    <select class="setting-input" id="setting-search-engine">
                        <option value="giphy" ${Config.get('searchEngine') === 'giphy' ? 'selected' : ''}>Giphy</option>
                        <option value="tenor" ${Config.get('searchEngine') === 'tenor' ? 'selected' : ''}>Tenor</option>
                    </select>
                </div>

                <div class="setting-group">
                    <label class="setting-checkbox">
                        <input type="checkbox" id="setting-auto-update" ${Config.get('autoUpdate') ? 'checked' : ''}>
                        <span>${lang.settingsPanel.autoUpdate}</span>
                    </label>
                </div>

                <div class="setting-group">
                    <label class="setting-label">${lang.settingsPanel.customUpdateUrl}</label>
                    <input type="url" class="setting-input" id="setting-update-url" value="${Config.get('customUpdateUrl')}">
                </div>

                <div class="setting-group">
                    <label class="setting-label">${lang.settingsPanel.logLevel}</label>
                    <select class="setting-input" id="setting-log-level">
                        <option value="ERROR" ${Config.get('logLevel') === 'ERROR' ? 'selected' : ''}>${lang.logLevels.ERROR}</option>
                        <option value="WARN" ${Config.get('logLevel') === 'WARN' ? 'selected' : ''}>${lang.logLevels.WARN}</option>
                        <option value="INFO" ${Config.get('logLevel') === 'INFO' ? 'selected' : ''}>${lang.logLevels.INFO}</option>
                        <option value="DEBUG" ${Config.get('logLevel') === 'DEBUG' ? 'selected' : ''}>${lang.logLevels.DEBUG}</option>
                        <option value="TRACE" ${Config.get('logLevel') === 'TRACE' ? 'selected' : ''}>${lang.logLevels.TRACE}</option>
                    </select>
                </div>

                <div class="setting-group">
                    <label class="setting-label">${lang.settingsPanel.cacheSize}</label>
                    <input type="number" class="setting-input" id="setting-cache-size" value="${Config.get('cacheSize')}" min="10" max="1000">
                </div>

                <div class="setting-group">
                    <small style="color: var(--eh-text-secondary);">
                        ${lang.settingsPanel.dataVersion}: ${Config.get('dataVersion')}<br>
                        ${lang.settingsPanel.lastUpdate}: ${Config.get('lastUpdateCheck') ? new Date(Config.get('lastUpdateCheck')).toLocaleString() : '从未'}
                    </small>
                </div>
            </div>
            <div class="settings-actions">
                <button class="settings-btn" id="export-logs-btn">${lang.settingsPanel.exportLogs}</button>
                <button class="settings-btn" id="clear-cache-btn">${lang.settingsPanel.clearCache}</button>
                <button class="settings-btn" id="update-now-btn">${lang.settingsPanel.updateNow}</button>
                <button class="settings-btn danger" id="clear-all-btn">${lang.settingsPanel.clearAllData}</button>
                <button class="settings-btn danger" id="reset-settings-btn">${lang.settingsPanel.reset}</button>
                <button class="settings-btn primary" id="close-settings-btn">${lang.settingsPanel.close}</button>
            </div>
        `;

        settingsPanel = panel;
        document.body.appendChild(panel);

        bindSettingsPanelEvents();
        DragManager.makeDraggable(panel);

        Logger.debug('UI', '设置面板创建完成');
    }

    // 绑定设置面板事件
    function bindSettingsPanelEvents() {
        if (!settingsPanel) return;

        const closeBtn = settingsPanel.querySelector('.close');
        const closeSettingsBtn = settingsPanel.querySelector('#close-settings-btn');

        if (closeBtn) closeBtn.addEventListener('click', hideSettingsPanel);
        if (closeSettingsBtn) closeSettingsBtn.addEventListener('click', hideSettingsPanel);

        // 设置项事件
        const langSelect = settingsPanel.querySelector('#setting-lang');
        const themeSelect = settingsPanel.querySelector('#setting-theme');
        const autoInsertCheck = settingsPanel.querySelector('#setting-auto-insert');
        const showFloatingCheck = settingsPanel.querySelector('#setting-show-floating');
        const gifSizeSelect = settingsPanel.querySelector('#setting-gif-size');
        const searchEngineSelect = settingsPanel.querySelector('#setting-search-engine');
        const autoUpdateCheck = settingsPanel.querySelector('#setting-auto-update');
        const updateUrlInput = settingsPanel.querySelector('#setting-update-url');
        const logLevelSelect = settingsPanel.querySelector('#setting-log-level');
        const cacheSizeInput = settingsPanel.querySelector('#setting-cache-size');

        if (langSelect) langSelect.addEventListener('change', () => Config.set('lang', langSelect.value));
        if (themeSelect) themeSelect.addEventListener('change', () => Config.set('theme', themeSelect.value));
        if (autoInsertCheck) autoInsertCheck.addEventListener('change', () => Config.set('autoInsert', autoInsertCheck.checked));
        if (showFloatingCheck) showFloatingCheck.addEventListener('change', () => Config.set('showFloatingButton', showFloatingCheck.checked));
        if (gifSizeSelect) gifSizeSelect.addEventListener('change', () => Config.set('gifSize', gifSizeSelect.value));
        if (searchEngineSelect) searchEngineSelect.addEventListener('change', () => Config.set('searchEngine', searchEngineSelect.value));
        if (autoUpdateCheck) autoUpdateCheck.addEventListener('change', () => Config.set('autoUpdate', autoUpdateCheck.checked));
        if (updateUrlInput) updateUrlInput.addEventListener('change', () => Config.set('customUpdateUrl', updateUrlInput.value));
        if (logLevelSelect) logLevelSelect.addEventListener('change', () => Config.set('logLevel', logLevelSelect.value));
        if (cacheSizeInput) cacheSizeInput.addEventListener('change', () => Config.set('cacheSize', parseInt(cacheSizeInput.value)));

        // 操作按钮事件
        const exportLogsBtn = settingsPanel.querySelector('#export-logs-btn');
        const clearCacheBtn = settingsPanel.querySelector('#clear-cache-btn');
        const updateNowBtn = settingsPanel.querySelector('#update-now-btn');
        const clearAllBtn = settingsPanel.querySelector('#clear-all-btn');
        const resetSettingsBtn = settingsPanel.querySelector('#reset-settings-btn');

        if (exportLogsBtn) exportLogsBtn.addEventListener('click', () => Logger.exportLogs());
        if (clearCacheBtn) clearCacheBtn.addEventListener('click', () => {
            CacheManager.clear();
            showMessage(t().messages.cacheCleared);
        });
        if (updateNowBtn) updateNowBtn.addEventListener('click', () => CloudDataManager.manualUpdate());
        if (clearAllBtn) clearAllBtn.addEventListener('click', () => {
            if (confirm('确定要清理所有数据吗?这将清除所有设置和缓存。')) {
                Storage.clearAll();
                CacheManager.clear();
                Logger.clearHistory();
                showMessage(t().messages.dataCleared);
                setTimeout(() => location.reload(), 1000);
            }
        });
        if (resetSettingsBtn) resetSettingsBtn.addEventListener('click', () => {
            if (confirm('确定要重置所有设置吗?')) {
                Config.reset();
            }
        });

        Logger.debug('UI', '设置面板事件绑定完成');
    }

    // 显示消息
    function showMessage(message, type = 'success') {
        const messageEl = document.createElement('div');
        messageEl.className = `emoji-helper-message ${type}`;
        messageEl.textContent = message;

        document.body.appendChild(messageEl);

        setTimeout(() => messageEl.classList.add('show'), 100);

        setTimeout(() => {
            messageEl.classList.remove('show');
            setTimeout(() => {
                if (messageEl.parentNode) {
                    messageEl.parentNode.removeChild(messageEl);
                }
            }, 300);
        }, 3000);

        Logger.info('UI', '显示消息', { message, type });
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initEmojiHelper);
    } else {
        // 延迟初始化以确保页面完全加载
        setTimeout(initEmojiHelper, 100);
    }

    // 全局点击事件,点击面板外部时关闭面板
    document.addEventListener('click', (e) => {
        const target = e.target;

        // 检查是否点击在任何面板内
        const clickedInPanel = target.closest('.emoji-helper-panel') ||
              target.closest('.emoji-helper-settings-panel') ||
              target.closest('.emoji-helper-text-editor') ||
              target.closest('.emoji-helper-floating-btn');

        if (!clickedInPanel) {
            hideEmojiPanel();
            hideSettingsPanel();
            if (textEditorPanel) {
                TextEditor.close();
            }
        }
    });

    // 键盘快捷键
    document.addEventListener('keydown', (e) => {
        // Ctrl/Cmd + Shift + E 切换表情面板
        if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'E') {
            e.preventDefault();
            toggleEmojiPanel();
            Logger.debug('EVENT', '快捷键切换表情面板');
        }

        // ESC 关闭所有面板
        if (e.key === 'Escape') {
            hideEmojiPanel();
            hideSettingsPanel();
            if (textEditorPanel) {
                TextEditor.close();
            }
            Logger.debug('EVENT', 'ESC关闭面板');
        }
    });

    // 窗口大小改变时调整面板位置
    window.addEventListener('resize', debounce(() => {
        updatePanelPosition();
        updateSettingsPanelPosition();
        Logger.debug('EVENT', '窗口大小改变,调整面板位置');
    }, 250));

    // 导出全局API(用于调试)
    window.EmojiHelperPro = {
        Config,
        Logger,
        Storage,
        CacheManager,
        CloudDataManager,
        TextEditor,
        GifSearchAPI,
        showPanel: showEmojiPanel,
        hidePanel: hideEmojiPanel,
        togglePanel: toggleEmojiPanel,
        showSettings: showSettingsPanel,
        version: '1.1.0'
    };

    Logger.info('INIT', '表情符号助手 Pro v1.1.0 加载完成 🎉');

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址