Greasy Fork

来自缓存

Greasy Fork is available in English.

全局工具箱

全能网页工具箱:解除复制限制 + 全页翻译 + 聚合搜索;浏览器必备效率神器!一键解决网页痛点:支持解除右键/复制限制、沉浸式翻译、二维码生成与夜间模式。内置强大的自定义搜索面板(支持 JSON 配置与自动抓取 Favicon),现代化暗色 UI,轻量拖拽,即装即用。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         全局工具箱
// @namespace    http://iapp.run
// @version      2.2.1
// @description  全能网页工具箱:解除复制限制 + 全页翻译 + 聚合搜索;浏览器必备效率神器!一键解决网页痛点:支持解除右键/复制限制、沉浸式翻译、二维码生成与夜间模式。内置强大的自定义搜索面板(支持 JSON 配置与自动抓取 Favicon),现代化暗色 UI,轻量拖拽,即装即用。
// @author       zero-ljz
// @homepage     https://github.com/zero-ljz/scripts/blob/main/greasemonkey/tools.js
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @grant        GM_openInTab
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/qrcode/1.4.4/qrcode.min.js
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";

    // --- 0. 核心修复:Trusted Types (针对 innerHTML 限制) ---
    if (window.trustedTypes && window.trustedTypes.createPolicy) {
        try {
            window.trustedTypes.createPolicy('default', {
                createHTML: string => string,
                createScript: string => string,
                createScriptURL: string => string,
            });
        } catch (e) {
            // 忽略策略已存在的错误
        }
    }

    // 辅助:安全设置 HTML
    const setHTML = (el, html) => {
        if (!el) return;
        el.innerHTML = html;
    };

    // 默认的搜索增强列表
    // 占位符说明: %s = 选中的文本/输入内容, %host% = 当前网站域名, %url% = 当前页面URL
    const DEFAULT_SEARCH_ENGINES = [
        { name: "谷歌搜索", icon: "", url: "https://www.google.com/search?q=%s" },
        { name: "百度搜索", icon: "", url: "https://www.baidu.com/s?wd=%s" },
        { name: "搜中文(谷歌)", icon: "", url: "https://www.google.com/search?lr=lang_zh-CN&q=%s" },
        { name: "站内搜索(谷歌)", icon: "", url: "https://www.google.com/search?q=site:%host%+%22%s%22" },
        { name: "维基百科", icon: "", url: "https://zh.wikipedia.org/wiki/%s" },
        { name: "GitHub", icon: "", url: "https://github.com/search?q=%s" },
        { name: "有道词典", icon: "", url: "http://dict.youdao.com/w/eng/%s" },
        {
            "name": "页面快照(谷歌)",
            "icon": "",
            "url": "http://www.google.com/search?q=cache:%url%"
        },
        {
            "name": "网页时光机",
            "icon": "",
            "url": "http://web.archive.org/%url%"
        },
        {
            "name": "翻译页面(谷歌)",
            "icon": "",
            "url": "https://translate.google.com/translate?sl=auto&tl=zh-CN&u=%url%"
        }
    ];

    // 配置系统 (GM_config) ---
    const DEFAULT_CONFIG = {
        btn_text: "⚡️",
        init_pos_top: "15%",
        init_pos_left: "10px",
        // 将默认数组转为格式化的JSON字符串
        custom_search_json: JSON.stringify(DEFAULT_SEARCH_ENGINES, null, 4)
    };

    const gmc = new GM_config({
        id: "ToolboxConfig",
        title: "工具箱设置",
        fields: {
            btn_text: { label: "按钮图标/文字", type: "text", default: DEFAULT_CONFIG.btn_text },
            show_button: { label: "显示悬浮球", type: "checkbox", default: true },
            // 新增:自定义搜索配置
            custom_search_json: {
                label: "自定义搜索列表 (JSON格式)",
                type: "textarea",
                default: DEFAULT_CONFIG.custom_search_json,
                css: "height: 300px; width: 100%; font-family: monospace; font-size: 12px;" // 样式优化
            }
        },
        events: {
            save: () => {
                gmc.close();
                updateButtonState();
                // 配置保存后刷新页面以应用新的搜索列表,或者重新渲染面板(稍微复杂点,刷新最简单)
                if (confirm("设置已保存。是否刷新页面以应用新的搜索列表?")) {
                    location.reload();
                }
            }
        }
    });

    // [核心修复] 必须显式初始化,否则 get() 会报错
    gmc.init();

    // 运行时状态
    const STATE = {
        isDarkMode: false,
        isEyeProtect: false
    };

    const Utils = {
        getSelection: () => {
            const text = window.getSelection().toString().trim();
            return text.length > 0 ? text : null;
        },
        prompt: (msg, defaultVal = "") => {
            return prompt(msg, defaultVal);
        },
        toast: (msg, duration = 3000) => {
            const toast = document.getElementById('gm-toast');
            toast.textContent = msg; // 纯文本用 textContent 很安全
            toast.classList.add('show');
            clearTimeout(toast.timer);
            toast.timer = setTimeout(() => toast.classList.remove('show'), duration);
        },
        modal: (title, content) => {
            const modal = document.getElementById('gm-result-modal');
            const overlay = document.getElementById('gm-result-overlay');
            const contentBox = document.getElementById('gm-result-content');

            document.getElementById('gm-result-title').textContent = title;

            // 智能判断:如果是字符串,使用安全HTML设置;如果是DOM元素,直接追加
            if (typeof content === 'string') {
                setHTML(contentBox, content);
            } else if (content instanceof Node) {
                contentBox.replaceChildren(); // 清空
                contentBox.appendChild(content);
            }

            modal.style.display = 'flex';
            overlay.style.display = 'block';
        },
        copy: (text) => {
            try {
                GM_setClipboard(text);
                Utils.toast('✅ 已复制到剪贴板');
            } catch (e) {
                navigator.clipboard.writeText(text).then(() => Utils.toast('✅ 已复制'));
            }
        }
    };


    // --- 辅助:构建搜索功能的函数 (自动图标版) ---
    const buildSearchTools = () => {
        let searchData = [];
        try {
            const jsonStr = gmc.get('custom_search_json');
            searchData = JSON.parse(jsonStr);
        } catch (e) {
            console.error("配置解析失败", e);
            searchData = DEFAULT_SEARCH_ENGINES;
        }

        return searchData.map(item => {
            // --- 核心逻辑:自动获取图标 ---
            let iconHtml = item.icon; // 默认用配置里的图标

            // 如果配置里 icon 为空,或者用户想强制用自动图标,则计算
            if (!iconHtml || iconHtml.trim() === "") {
                try {
                    // 1. 简单的占位符替换,防止 URL 解析报错
                    const cleanUrl = item.url.replace(/%s|%url%|%host%/g, 'example.com');
                    // 2. 提取主机名 (例如 www.google.com)
                    const hostname = new URL(cleanUrl).hostname;
                    // 3. 拼接 Google API (sz=32 获取高清一点的)
                    const faviconUrl = `https://p.520999.xyz/https://www.google.com/s2/favicons?sz=32&domain=${hostname}`;
                    // 4. 生成 img 标签
                    iconHtml = `<img src="${faviconUrl}" onerror="this.style.display='none'">`;
                } catch (e) {
                    iconHtml = "🔗"; // 解析失败的回退图标
                }
            }
            // ---------------------------

            return {
                name: item.name,
                // 这里我们稍微 hack 一下,因为原始逻辑是把 icon 当字符串拼进去的
                // 这里的 icon 属性现在可能包含 HTML 标签
                icon: iconHtml,
                action: () => {
                    let targetUrl = item.url;
                    targetUrl = targetUrl.replace(/%host%/g, location.hostname);
                    targetUrl = targetUrl.replace(/%url%/g, encodeURIComponent(location.href));

                    if (targetUrl.includes('%s')) {
                        const q = Utils.getSelection() || Utils.prompt(`请输入 [${item.name}] 内容:`);
                        if (!q) return;
                        targetUrl = targetUrl.replace(/%s/g, encodeURIComponent(q));
                    }

                    window.open(targetUrl);
                }

            };
        });
    };


    // --- 3. 功能定义 (保留所有原有功能) ---
    // 为了更好的UI,我们给功能加了图标和分类
    const TOOLS = {
        "常用工具": [
            {
                name: "朗读文本", icon: "🗣️",
                action: () => {
                    let q = Utils.getSelection() || Utils.prompt("请输入朗读文本:");
                    if (q) window.speechSynthesis.speak(new window.SpeechSynthesisUtterance(q));
                }
            },
            {
                name: "生成二维码", icon: "📱",
                action: () => {
                    let q = Utils.getSelection() || window.location.href;
                    if (!q) return;

                    // 1. 创建 UI 容器
                    const wrapper = document.createElement('div');
                    wrapper.style.cssText = "display:flex; flex-direction:column; align-items:center; justify-content:center; padding:10px;";

                    const codeContainer = document.createElement('div');
                    // 使用 flex 居中
                    codeContainer.style.cssText = "background:white; padding:15px; border-radius:8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); display: flex; justify-content: center; align-items: center;";
                    wrapper.appendChild(codeContainer);

                    const textTip = document.createElement('div');
                    textTip.style.cssText = "text-align:center; font-size:12px; color:#999; margin-top:10px; word-break:break-all; max-width:250px; line-height: 1.4;";
                    textTip.textContent = q.length > 100 ? q.substring(0, 100) + '...' : q;
                    wrapper.appendChild(textTip);

                    // 2. 显示模态框
                    Utils.modal("📱 二维码生成", wrapper);

                    // 3. 延时调用库生成 (确保库已加载)
                    setTimeout(() => {
                        // node-qrcode 库加载后会暴露全局变量 QRCode
                        if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
                            // 创建 Canvas 元素
                            const canvas = document.createElement('canvas');
                            codeContainer.appendChild(canvas);

                            // 调用库绘制
                            // 优点:自动处理UTF-8中文,自动选择版本(不会overflow)
                            QRCode.toCanvas(canvas, q, {
                                width: 256,        // 宽度
                                margin: 0,         // 边距 (我们在外层容器控制了padding,这里设0)
                                color: {
                                    dark: '#000000',  // 前景色
                                    light: '#ffffff'  // 背景色
                                },
                                errorCorrectionLevel: 'M' // 中等容错率,平衡容量和清晰度
                            }, function (error) {
                                if (error) {
                                    console.error(error);
                                    codeContainer.replaceChildren(`<span style="color:red">生成失败: ${error.message}</span>`);
                                }
                            });
                        } else {
                            Utils.toast("❌ QRCode库未加载");
                        }
                    }, 50);
                }
            },
            {
                name: "翻译文本", icon: "🌐",
                action: () => {
                    let q = Utils.getSelection() || Utils.prompt("请输入翻译文本:");
                    if (!q) return;
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: "http://translate.google.com/translate_a/single?client=gtx&dt=t&dj=1&ie=UTF-8&sl=auto&tl=zh&q=" + encodeURIComponent(q),
                        onload: function (response) {
                            try {
                                const obj = JSON.parse(response.responseText);
                                let res = obj.sentences.map(s => s.trans).join("");
                                Utils.modal("翻译结果", res);
                            } catch (e) { Utils.toast("翻译解析失败"); }
                        }
                    });
                }
            },
            {
                name: "微信翻译", icon: "🟢",
                action: () => {
                    let q = Utils.getSelection() || Utils.prompt("请输入翻译文本:");
                    if (!q) return;

                    // 构造 URL 参数
                    const params = "source=auto&target=zh&platform=WeChat_APP&candidateLangs=en|zh&guid=cli_user&sourceText=" + encodeURIComponent(q);

                    GM_xmlhttpRequest({
                        method: "GET",
                        url: "https://wxapp.translator.qq.com/api/translate?" + params,
                        headers: {
                            "Content-Type": "application/json",
                            // 伪造 Referer 和 UA 是必须的
                            "Referer": "https://servicewechat.com/wxb1070eabc6f9107e/117/page-frame.html",
                            "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.32(0x18002035) NetType/WIFI Language/zh_TW"
                        },
                        onload: function (response) {
                            try {
                                const obj = JSON.parse(response.responseText);
                                if (obj && obj.targetText) {
                                    Utils.modal("微信翻译结果", obj.targetText);
                                } else {
                                    Utils.toast("微信接口返回异常");
                                }
                            } catch (e) { Utils.toast("翻译解析失败"); }
                        },
                        onerror: (e) => Utils.toast("网络请求失败")
                    });
                }
            },
            {
                name: "翻译整页", icon: "🔄",
                action: () => {
                    Utils.toast("⏳ 开始分析并翻译页面,这可能需要一点时间...");
                    const googleTranslateAPI = (text) => {
                        return new Promise((resolve, reject) => {
                            GM_xmlhttpRequest({
                                method: "GET",
                                url: "https://translate.googleapis.com/translate_a/single?" + new URLSearchParams({
                                    client: "gtx",
                                    dt: "t",
                                    sl: "auto",  // 源语言自动
                                    tl: "zh-CN", // 目标语言中文
                                    q: text
                                }).toString(),
                                onload: function (response) {
                                    try {
                                        const data = JSON.parse(response.responseText);
                                        // 解析逻辑参考了你的代码:result.data[0].map...
                                        if (data && data[0]) {
                                            const result = data[0].map(item => item[0]).join("");
                                            resolve(result);
                                        } else {
                                            resolve(text); // 失败返回原文
                                        }
                                    } catch (e) {
                                        reject(e);
                                    }
                                },
                                onerror: reject
                            });
                        });
                    };
                    async function translatePage() {
                        const walker = document.createTreeWalker(
                            document.body,
                            NodeFilter.SHOW_TEXT,
                            {
                                acceptNode: function (node) {
                                    const tag = node.parentElement.tagName;
                                    if (["SCRIPT", "STYLE", "NOSCRIPT", "CODE", "PRE"].includes(tag)) return NodeFilter.FILTER_REJECT;
                                    if (node.textContent.trim().length === 0) return NodeFilter.FILTER_REJECT;
                                    return NodeFilter.FILTER_ACCEPT;
                                }
                            }
                        );

                        const textNodes = [];
                        let node;
                        while (node = walker.nextNode()) {
                            textNodes.push(node);
                        }

                        Utils.toast(`🔍 发现 ${textNodes.length} 个文本段落,开始翻译...`);

                        // 3. 批量处理 (为了防止请求过快被封,每次处理一小批)
                        const BATCH_SIZE = 10; // 并发数
                        let completed = 0;

                        // 简单的队列处理
                        for (let i = 0; i < textNodes.length; i += BATCH_SIZE) {
                            const batch = textNodes.slice(i, i + BATCH_SIZE);
                            const promises = batch.map(async (textNode) => {
                                try {
                                    const originalText = textNode.textContent.trim();
                                    // 只有纯英文/非中文才翻译 (简单判断)
                                    if (!/[\u4e00-\u9fa5]/.test(originalText) && originalText.length > 2) {
                                        const translated = await googleTranslateAPI(originalText);
                                        if (translated && translated !== originalText) {
                                            textNode.textContent = translated;
                                            // 标记一下颜色,让人知道这里被翻译了
                                            if (textNode.parentElement) textNode.parentElement.style.backgroundColor = "rgba(255, 255, 0, 0.1)";
                                        }
                                    }
                                } catch (e) {
                                    console.error("翻译片段失败", e);
                                }
                            });

                            await Promise.all(promises);
                            completed += batch.length;

                            // 每完成50个节点提示一下进度
                            if (i % 50 === 0) {
                                Utils.toast(`正在翻译... ${Math.min(100, Math.round(completed / textNodes.length * 100))}%`);
                            }
                        }

                        Utils.toast("✅ 页面翻译完成");
                    }

                    translatePage();
                }
            },

        ],

        "搜索增强": [],

        "其他工具": [
            {
                name: "解除限制", icon: "🔓",
                action: () => {
                    // 增强版解除限制
                    const events = ["copy", "cut", "contextmenu", "selectstart", "mousedown", "mouseup", "mousemove", "keydown", "keypress", "keyup"];
                    events.forEach(e => document.documentElement.addEventListener(e, evt => { evt.stopPropagation(); }, { capture: true }));
                    const style = document.createElement('style');
                    style.replaceChildren(`* { user-select: text !important; -webkit-user-select: text !important; }`);
                    document.body.appendChild(style);
                    Utils.toast("🔓 已尝试解除右键和复制限制");
                }
            },
            {
                name: "显示密码", icon: "👀",
                action: () => {
                    document.querySelectorAll("input[type='password']").forEach(el => el.type = "text");
                    Utils.toast("👀 密码已明文显示");
                }
            },

            {
                name: "屏幕取色", icon: "🎨",
                action: async () => {
                    if (!window.EyeDropper) return Utils.toast("⚠️ 您的浏览器不支持取色 API (需 Chrome 95+)");
                    try {
                        const ed = new window.EyeDropper();
                        const result = await ed.open();
                        Utils.copy(result.sRGBHex);
                        Utils.toast(`🎨 颜色 ${result.sRGBHex} 已复制`);
                    } catch (e) {
                        if (!e.toString().includes('canceled')) Utils.toast("❌ 取色失败");
                    }
                }
            },
            {
                name: "护眼模式", icon: "👁️",
                action: (btn) => {
                    STATE.isEyeProtect = !STATE.isEyeProtect;
                    let mask = document.getElementById('gm-eye-protect');
                    if (!mask) {
                        mask = document.createElement('div');
                        mask.id = 'gm-eye-protect';
                        mask.style.cssText = "position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(255, 240, 0, 0.15);mix-blend-mode:multiply;pointer-events:none;z-index:2147483647;display:none;";
                        document.body.appendChild(mask);
                    }
                    mask.style.display = STATE.isEyeProtect ? 'block' : 'none';
                    btn.classList.toggle('active', STATE.isEyeProtect);
                    Utils.toast(STATE.isEyeProtect ? "✅ 护眼模式已开启" : "🚫 护眼模式已关闭");
                },
                hasDot: true
            },
            {
                name: "暗黑模式", icon: "🌙",
                action: (btn) => {
                    STATE.isDarkMode = !STATE.isDarkMode;
                    let style = document.getElementById('gm-dark-mode-style');
                    if (!style) {
                        style = document.createElement('style');
                        style.id = 'gm-dark-mode-style';
                        style.replaceChildren(`html { filter: invert(1) hue-rotate(180deg) !important; } img, video, iframe { filter: invert(1) hue-rotate(180deg) !important; }`);
                        document.head.appendChild(style);
                        style.disabled = true;
                    }
                    style.disabled = !STATE.isDarkMode;
                    btn.classList.toggle('active', STATE.isDarkMode);
                    Utils.toast(STATE.isDarkMode ? "🌙 智能夜间模式已开启" : "☀️ 已恢复日间模式");
                },
                hasDot: true
            },
            {
                name: "编辑网页", icon: "✏️",
                action: () => {
                    const isEditable = document.body.contentEditable === 'true';
                    document.body.contentEditable = !isEditable;
                    document.designMode = !isEditable ? 'on' : 'off';
                    Utils.toast(isEditable ? "🔒 已关闭编辑模式" : "✏️ 网页可随意编辑");
                }
            },
            {
                name: "屏蔽元素", icon: "🚫",
                action: () => {
                    Utils.toast("请点击要屏蔽的元素 (按ESC取消)");
                    const handler = (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        e.target.style.display = 'none';
                        document.removeEventListener('click', handler, true);
                        Utils.toast("🚫 元素已隐藏");
                    };
                    document.addEventListener('click', handler, true);
                }
            },

            {
                name: "网页标注(Spacing)", icon: "📏",
                action: () => {
                    // 使用动态导入,不阻塞主线程
                    const script = document.createElement('script');
                    script.src = "https://unpkg.com/spacingjs";
                    document.body.appendChild(script);
                    Utils.toast("📏 按住 Alt 键查看元素间距");
                }
            },
            {
                name: "执行JS", icon: "💻",
                action: () => {
                    let q = Utils.getSelection() || Utils.prompt("输入JavaScript代码:", "alert('Hello')");
                    if (q) {
                        try { eval(q); } catch (e) { Utils.modal("Error", e); }
                    }
                }
            },
            {
                name: "调试信息", icon: "🐞",
                action: () => {
                    const info = `
Title: ${document.title}
URL: ${location.href}
UserAgent: ${navigator.userAgent}
Screen: ${screen.width}x${screen.height}
Cookie: ${document.cookie}
LastModified: ${document.lastModified}
                    `.trim();
                    console.log(info);
                    Utils.modal("页面调试信息", info);
                }
            }
        ]
    };


    // 额外的设置项:脚本设置
    TOOLS["常用工具"].unshift({
        name: "脚本设置", icon: "⚙️",
        action: () => gmc.open()
    });
    TOOLS["常用工具"].unshift({
        name: "隐藏按钮", icon: "🙈",
        action: () => {
            const btn = document.getElementById('gm-float-btn');
            if (btn) {
                btn.style.setProperty('display', 'none', 'important');
            }
            Utils.toast("按钮已隐藏,请在脚本管理器菜单重新开启或刷新页面");
        }
    });




    //Config
    const CONSTANTS = {
        Z_INDEX: 2147483647,
        THEME_COLOR: '#007AFF', // iOS Blue
        GLASS_BG: 'rgba(255, 255, 255, 0.75)',
        GLASS_BG_DARK: 'rgba(30, 30, 30, 0.85)',
        ANIMATION_SPEED: '0.25s'
    };

    // --- 1. 样式系统 (CSS) ---
    GM_addStyle(`
        /* === 样式隔离与重置 (核心修复:防止网页样式污染) === */
        #gm-toolbox-panel, #gm-toolbox-panel * {
            box-sizing: border-box !important;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
            line-height: 1.5 !important;
            -webkit-font-smoothing: antialiased;
        }
        
        /* 强制重置面板内的图片和SVG,防止网页全局样式(如 max-width: 100%)导致图标变形 */
        #gm-toolbox-panel img, #gm-toolbox-panel svg {
            max-width: none !important;
            max-height: none !important;
            vertical-align: middle !important;
            margin: 0 !important;
            padding: 0 !important;
            border: none !important;
            box-shadow: none !important;
            background: transparent !important;
        }

        /* 悬浮球 (保持原样,微调) */
        #gm-float-btn {
            position: fixed;
            width: 44px !important;
            height: 44px !important;
            border-radius: 50% !important;
            background: ${CONSTANTS.GLASS_BG_DARK};
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            z-index: ${CONSTANTS.Z_INDEX};
            cursor: move;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            color: white !important;
            font-size: 20px !important;
            user-select: none;
            transition: transform 0.1s, background ${CONSTANTS.ANIMATION_SPEED};
            border: 1px solid rgba(255,255,255,0.1) !important;
            margin: 0 !important;
            padding: 0 !important;
        }
        #gm-float-btn:hover { transform: scale(1.1); background: #000; }
        #gm-float-btn:active { transform: scale(0.95); }

        /* 主菜单面板 */
        #gm-toolbox-panel {
            position: fixed;
            display: none; /* 配合 JS 的 toggle 逻辑 */
            width: 340px !important;
            max-height: 80vh !important;
            overflow: hidden !important;
            background: ${CONSTANTS.GLASS_BG_DARK};
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border-radius: 16px !important;
            box-shadow: 0 10px 40px rgba(0,0,0,0.4) !important;
            z-index: ${CONSTANTS.Z_INDEX};
            padding: 16px !important;
            color: #fff !important;
            border: 1px solid rgba(255,255,255,0.08) !important;
            
            /* === 核心修复:隐藏状态 === */
            opacity: 0;
            transform: scale(0.95);
            
            /* 1. 禁止鼠标穿透:确保看不见的时候点不到 */
            pointer-events: none !important; 
            
            /* 2. 移除可见性:确保不会触发 hover 和 tooltip */
            visibility: hidden !important;   
            /* ========================= */

            transition: opacity ${CONSTANTS.ANIMATION_SPEED}, transform ${CONSTANTS.ANIMATION_SPEED}, visibility ${CONSTANTS.ANIMATION_SPEED};
            text-align: left !important;
            flex-direction: column;
        }

        /* 显示状态 */
        #gm-toolbox-panel.show { 
            opacity: 1; 
            transform: scale(1); 
            
            /* === 核心修复:显示状态 === */
            /* 恢复鼠标交互 */
            pointer-events: auto !important; 
            /* 恢复可见性 */
            visibility: visible !important;
            /* ========================= */
        }

        #gm-search-wrapper {
            margin-bottom: 12px !important;
            position: relative !important;
            flex-shrink: 0;
            width: 100% !important;
            height: 36px !important; /* 固定高度,防止塌陷 */
        }
        #gm-search-input {
            width: 100% !important;
            height: 100% !important;
            background: rgba(255,255,255,0.1) !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
            border-radius: 8px !important;
            padding: 0 34px 0 10px !important; /* 右侧留出图标位置 */
            color: #fff !important;
            font-size: 14px !important;
            outline: none !important;
            transition: background 0.2s;
            margin: 0 !important;
            line-height: normal !important; /* 修复文字垂直对齐 */
            appearance: none !important; /* 去除浏览器默认样式 */
        }
        #gm-search-input:focus { background: rgba(255,255,255,0.15) !important; border-color: ${CONSTANTS.THEME_COLOR} !important; }
        
        #gm-search-icon {
            position: absolute !important;
            right: 0 !important;
            top: 0 !important;
            width: 34px !important; /* 宽度与 input padding 对应 */
            height: 100% !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            color: rgba(255,255,255,0.4) !important;
            pointer-events: none;
            font-size: 14px !important;
            line-height: 1 !important;
            margin: 0 !important;
            padding: 0 !important;
        }

        /* 内容滚动区域 */
        #gm-content-scroll {
            overflow-y: auto !important;
            flex-grow: 1;
            padding-right: 2px;
            /* 修复滚动条样式 */
            scrollbar-width: thin;
            scrollbar-color: rgba(255,255,255,0.2) transparent;
        }
        #gm-content-scroll::-webkit-scrollbar { width: 4px; }
        #gm-content-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 2px; }
        #gm-content-scroll::-webkit-scrollbar-track { background: transparent; }

        /* 分类标题 */
        .gm-category-title {
            font-size: 12px !important;
            color: rgba(255,255,255,0.5) !important;
            margin: 14px 0 6px 4px !important;
            padding: 0 !important;
            font-weight: 700 !important;
            text-transform: uppercase;
            letter-spacing: 0.5px !important;
            line-height: 1.2 !important;
        }
        .gm-category-title:first-child { margin-top: 0 !important; }

        /* 网格布局 */
        .gm-grid {
            display: grid !important;
            grid-template-columns: repeat(2, 1fr) !important;
            gap: 8px !important;
            margin: 0 !important;
            padding: 0 !important;
        }

        /* 功能按钮 */
        .gm-tool-btn {
            background: rgba(255,255,255,0.05) !important;
            border: none !important;
            color: #eee !important;
            padding: 8px 10px !important; /* 微调内边距 */
            margin: 0 !important;
            width: 100% !important;
            height: auto !important;
            min-height: 42px !important;
            border-radius: 8px !important;
            cursor: pointer;
            font-size: 13px !important;
            text-align: left !important;
            display: flex !important;
            align-items: center !important; /* 垂直居中 */
            justify-content: flex-start !important;
            transition: all 0.2s;
            user-select: none;
            position: relative !important;
            overflow: hidden !important; /* 防止内容溢出 */
        }
        .gm-tool-btn:hover { background: ${CONSTANTS.THEME_COLOR} !important; color: #fff !important; transform: translateY(-1px); }
        
        /* === 图标统一尺寸修复 (核心) === */
        .gm-tool-btn .icon {
            width: 24px !important;  /* 强制固定宽度 */
            height: 24px !important; /* 强制固定高度 */
            min-width: 24px !important;
            margin-right: 10px !important;
            font-size: 18px !important; /* Emoji 字体大小 */
            line-height: 1 !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            text-align: center !important;
            flex-shrink: 0 !important; /* 防止被挤压 */
        }

        /* 强制约束图片图标 */
        .gm-tool-btn .icon img, .gm-tool-btn .icon svg {
            width: 100% !important;
            height: 100% !important;
            object-fit: contain !important; /* 保持比例适应 */
            display: block !important;
            border-radius: 2px !important;
        }
        
        /* 收藏星星 */
        .gm-fav-star {
            position: absolute;
            top: 4px; right: 4px;
            font-size: 10px !important;
            line-height: 1 !important;
            color: #FFD60A;
            opacity: 0.8;
            text-shadow: 0 0 5px rgba(255, 214, 10, 0.5);
        }

        /* 状态圆点 */
        .gm-dot {
            width: 6px; height: 6px; border-radius: 50%;
            background: #ccc; margin-left: auto; flex-shrink: 0;
        }
        .gm-tool-btn.active .gm-dot { background: #34C759; box-shadow: 0 0 5px #34C759; }

        /* 拖拽样式 */
        .gm-tool-btn.gm-dragging { opacity: 0.4; transform: scale(0.95); border: 1px dashed rgba(255,255,255,0.5) !important; }
        .gm-tool-btn.gm-drag-over { border-top: 2px solid ${CONSTANTS.THEME_COLOR} !important; transform: translateY(2px); }

        /* Toast & Modal (保持不变) */
        #gm-toast {
            position: fixed; top: 20px; left: 50%; transform: translateX(-50%) translateY(-100%);
            background: rgba(0,0,0,0.85); color: #fff; padding: 10px 20px !important; border-radius: 50px !important;
            z-index: ${CONSTANTS.Z_INDEX + 10}; font-size: 14px !important; opacity: 0; pointer-events: none;
            transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55); backdrop-filter: blur(5px);
            box-shadow: 0 5px 15px rgba(0,0,0,0.3) !important; border: 1px solid rgba(255,255,255,0.1) !important;
            white-space: nowrap;
        }
        #gm-toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
        
        #gm-result-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: ${CONSTANTS.Z_INDEX + 15}; display: none; backdrop-filter: blur(2px); }
        #gm-result-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500px !important; max-width: 90vw !important; background: #fff !important; color: #333 !important; border-radius: 12px !important; padding: 20px !important; z-index: ${CONSTANTS.Z_INDEX + 20}; box-shadow: 0 20px 60px rgba(0,0,0,0.3) !important; display: none; flex-direction: column !important; text-align: left !important; }
        #gm-result-header { display: flex !important; justify-content: space-between !important; align-items: center !important; margin-bottom: 15px !important; border-bottom: 1px solid #eee !important; padding-bottom: 10px !important; }
        #gm-result-title { font-weight: bold !important; font-size: 16px !important; color: #000 !important; margin: 0 !important; }
        #gm-result-close { cursor: pointer; padding: 5px; font-weight: bold; color: #999; line-height: 1 !important; }
        #gm-result-content { max-height: 60vh !important; overflow-y: auto !important; white-space: pre-wrap !important; font-family: monospace !important; font-size: 14px !important; background: #f9f9f9 !important; padding: 10px !important; border-radius: 6px !important; border: 1px solid #eee !important; }
    `);


    // --- 2. 存储与辅助函数 ---

    // 收藏管理
    const FAV_KEY = 'tools_fav_list';
    const getFavorites = () => GM_getValue(FAV_KEY, []);
    const isFavorite = (name) => getFavorites().includes(name);
    const toggleFavorite = (name) => {
        let favs = getFavorites();
        if (favs.includes(name)) {
            favs = favs.filter(n => n !== name);
            Utils.toast(`已取消收藏: ${name}`);
        } else {
            favs.push(name);
            Utils.toast(`⭐ 已收藏: ${name}`);
        }
        GM_setValue(FAV_KEY, favs);
        return favs;
    };

    // 排序管理
    const getSavedOrder = () => GM_getValue('tools_order_map', {});
    const saveCategoryOrder = (categoryName, container) => {
        // 如果是“收藏”分类,不保存排序(或者你可以实现单独的收藏排序逻辑,这里为了简单跳过)
        if (categoryName === '⭐ 收藏置顶') return;

        const orderMap = getSavedOrder();
        const currentOrder = Array.from(container.children).map(btn => btn.dataset.name);
        orderMap[categoryName] = currentOrder;
        GM_setValue('tools_order_map', orderMap);
    };


    // --- 5. UI 构建与事件逻辑 ---

    function createUI() {
        // ... (Toast, Modal, Float Button 创建保持不变) ...
        const toast = document.createElement('div'); toast.id = 'gm-toast'; document.body.appendChild(toast);
        const overlay = document.createElement('div'); overlay.id = 'gm-result-overlay';
        const modal = document.createElement('div'); modal.id = 'gm-result-modal';
        setHTML(modal, `<div id="gm-result-header"><h3 id="gm-result-title">Title</h3><span id="gm-result-close">✕</span></div><div id="gm-result-content"></div>`);
        document.body.appendChild(overlay); document.body.appendChild(modal);
        const closeFn = () => { modal.style.display = 'none'; overlay.style.display = 'none'; };
        document.getElementById('gm-result-close').onclick = closeFn; overlay.onclick = closeFn;

        const btn = document.createElement('div');
        btn.id = 'gm-float-btn';
        setHTML(btn, gmc.get('btn_text'));
        btn.style.top = GM_getValue('pos_top', DEFAULT_CONFIG.init_pos_top);
        btn.style.left = GM_getValue('pos_left', DEFAULT_CONFIG.init_pos_left);
        if (!gmc.get('show_button')) btn.style.setProperty('display', 'none', 'important');
        document.body.appendChild(btn);

        // --- Panel 构建 ---
        const panel = document.createElement('div');
        panel.id = 'gm-toolbox-panel';

        // 搜索框区域
        const searchWrapper = document.createElement('div');
        searchWrapper.id = 'gm-search-wrapper';
        // 使用 type="search" 并禁止浏览器默认样式
        setHTML(searchWrapper, `
            <input type="text" id="gm-search-input" placeholder="搜索功能..." autocomplete="off" spellcheck="false">
            <div id="gm-search-icon">🔍</div>
        `);
        panel.appendChild(searchWrapper);

        // 内容滚动区域
        const contentScroll = document.createElement('div');
        contentScroll.id = 'gm-content-scroll';
        panel.appendChild(contentScroll);

        document.body.appendChild(panel);

        // --- 渲染逻辑 (Render Tool List) ---
        let dragSrcEl = null;

        function renderToolList() {
            contentScroll.replaceChildren(); // 清空内容区域

            TOOLS["搜索增强"] = buildSearchTools();

            const savedOrderMap = getSavedOrder();
            const favorites = getFavorites();
            const searchVal = panel.querySelector('#gm-search-input').value.toLowerCase().trim();

            let categoriesToRender = {};

            // 收藏置顶逻辑
            // 1. 收藏置顶逻辑 (修改版:支持排序)
            if (favorites.length > 0 && !searchVal) {
                let favTools = [];
                // 核心修改:遍历“收藏列表”而不是遍历“所有工具”,这样才能保证渲染顺序与存储顺序一致
                favorites.forEach(favName => {
                    // 在所有工具中查找对应的工具对象
                    for (const toolList of Object.values(TOOLS)) {
                        const foundTool = toolList.find(t => t.name === favName);
                        if (foundTool) {
                            favTools.push({ ...foundTool, isFavItem: true });
                            break; // 找到了就跳出当前分类循环
                        }
                    }
                });

                if (favTools.length > 0) categoriesToRender['⭐ 收藏置顶'] = favTools;
            }

            Object.assign(categoriesToRender, TOOLS);

            for (const [category, rawItems] of Object.entries(categoriesToRender)) {
                let items = [...rawItems];

                if (category !== '⭐ 收藏置顶' && savedOrderMap[category]) {
                    const orderList = savedOrderMap[category];
                    items.sort((a, b) => {
                        let indexA = orderList.indexOf(a.name); let indexB = orderList.indexOf(b.name);
                        if (indexA === -1) indexA = 9999; if (indexB === -1) indexB = 9999;
                        return indexA - indexB;
                    });
                }

                if (searchVal) {
                    items = items.filter(t => t.name.toLowerCase().includes(searchVal));
                    if (items.length === 0) continue;
                }

                const title = document.createElement('div');
                title.className = 'gm-category-title';
                title.innerText = category;
                contentScroll.appendChild(title);

                const grid = document.createElement('div');
                grid.className = 'gm-grid';
                grid.dataset.category = category;

                items.forEach(tool => {
                    const b = document.createElement('button');
                    b.className = 'gm-tool-btn';
                    b.dataset.name = tool.name;
                    b.setAttribute('draggable', 'true');
                    b.title = "左键运行,右键收藏/取消";

                    const isFav = isFavorite(tool.name);

                    // 这里 icon 容器已经被 CSS 强制锁定大小
                    let html = `<span class="icon">${tool.icon}</span><span style="flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${tool.name}</span>`;
                    if (tool.hasDot) html += `<div class="gm-dot"></div>`;
                    if (isFav) html += `<span class="gm-fav-star">★</span>`;
                    setHTML(b, html);

                    b.onclick = (e) => {
                        e.stopPropagation();
                        if (!tool.hasDot) togglePanel(false);
                        try { tool.action(b); } catch (err) { console.error(err); Utils.toast("❌ Error: " + err.message); }
                        togglePanel(false);
                    };

                    b.oncontextmenu = (e) => {
                        e.preventDefault(); e.stopPropagation();
                        toggleFavorite(tool.name);
                        renderToolList();
                        return false;
                    };

                    // 拖拽逻辑 (保持不变)
                    b.addEventListener('dragstart', function (e) {
                        dragSrcEl = this;
                        e.dataTransfer.effectAllowed = 'move';
                        e.dataTransfer.setData('text/html', this.innerHTML);
                        this.classList.add('gm-dragging');
                    });
                    b.addEventListener('dragend', function () {
                        this.classList.remove('gm-dragging');
                        grid.querySelectorAll('.gm-tool-btn').forEach(el => el.classList.remove('gm-drag-over'));
                    });
                    b.addEventListener('dragover', function (e) {
                        if (e.preventDefault) e.preventDefault();
                        e.dataTransfer.dropEffect = 'move';
                        return false;
                    });
                    b.addEventListener('dragenter', function () {
                        if (this !== dragSrcEl) this.classList.add('gm-drag-over');
                    });
                    b.addEventListener('dragleave', function () {
                        this.classList.remove('gm-drag-over');
                    });
                    b.addEventListener('drop', function (e) {
                        e.stopPropagation();

                        // 判断是否在同一个父容器内(同分类)
                        if (dragSrcEl !== this && dragSrcEl.parentNode === this.parentNode) {

                            // 1. DOM 操作:交换位置
                            this.parentNode.insertBefore(dragSrcEl, this);

                            // 2. 数据保存逻辑
                            if (category === '⭐ 收藏置顶') {
                                // === 核心修改:如果是收藏夹,直接更新收藏列表数据 ===
                                const newFavOrder = Array.from(this.parentNode.children).map(btn => btn.dataset.name);
                                GM_setValue(FAV_KEY, newFavOrder);
                                Utils.toast('收藏排序已更新');
                            } else {
                                // 普通分类,走原来的保存逻辑
                                saveCategoryOrder(category, this.parentNode);
                            }
                        }
                        return false;
                    });

                    grid.appendChild(b);
                });
                contentScroll.appendChild(grid);
            }

            if (contentScroll.children.length === 0 && searchVal) {
                const empty = document.createElement('div');
                empty.style.cssText = "text-align:center; color:#999; margin-top:20px; font-size:13px;";
                empty.innerText = "未找到相关功能";
                contentScroll.appendChild(empty);
            }
        }

        renderToolList();

        // 交互事件监听 (搜索框)
        const searchInput = panel.querySelector('#gm-search-input');
        searchInput.addEventListener('input', () => renderToolList());
        searchInput.addEventListener('click', (e) => e.stopPropagation()); // 防止点击输入框关闭面板

        // 交互事件监听 (悬浮球拖拽 & 面板开关 - 保持原逻辑)
        let isDragging = false, hasMoved = false, startX, startY, initLeft, initTop;
        btn.addEventListener('mousedown', (e) => {
            if (e.button !== 0) return;
            isDragging = true; hasMoved = false;
            startX = e.clientX; startY = e.clientY;
            const rect = btn.getBoundingClientRect();
            initLeft = rect.left; initTop = rect.top;
            btn.style.transition = 'none';
            e.preventDefault();
        });
        window.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const dx = e.clientX - startX; const dy = e.clientY - startY;
            if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
            let newLeft = initLeft + dx; let newTop = initTop + dy;
            const maxLeft = window.innerWidth - btn.offsetWidth; const maxTop = window.innerHeight - btn.offsetHeight;
            newLeft = Math.max(0, Math.min(newLeft, maxLeft)); newTop = Math.max(0, Math.min(newTop, maxTop));
            btn.style.left = newLeft + 'px'; btn.style.top = newTop + 'px';
        });
        window.addEventListener('mouseup', () => {
            if (!isDragging) return;
            isDragging = false;
            btn.style.transition = `transform 0.1s, background ${CONSTANTS.ANIMATION_SPEED}`;
            if (hasMoved) { GM_setValue('pos_top', btn.style.top); GM_setValue('pos_left', btn.style.left); }
        });

        btn.addEventListener('click', (e) => {
            // 1. 阻止默认行为和冒泡,防止触发页面其他点击事件
            e.preventDefault();
            e.stopPropagation();

            // 只有在没有拖动的情况下才视为“点击”
            if (!hasMoved) {
                togglePanel();

                // === 自动聚焦逻辑 ===
                // 使用 setTimeout 是为了等待面板从 display:none 变为可见
                setTimeout(() => {
                    const input = document.getElementById('gm-search-input');
                    const panel = document.getElementById('gm-toolbox-panel');

                    // 获取当前页面选中的文本
                    const selection = window.getSelection().toString();

                    // 逻辑判断:
                    // 1. 面板必须是显示状态 (含有 .show 类)
                    // 2. 页面上【没有】选中文本 (!selection)
                    //    原因:如果页面有选中文本,自动 focus 会导致选中文本丢失,无法使用"搜索选中"功能
                    if (panel.classList.contains('show') && !selection) {
                        input.focus();
                    }
                }, 50); // 50ms 延时足够让 CSS transition 开始生效
            }
        });

        // 同时也需要确保 mousedown 不会清除选中 (之前的代码已经包含了 e.preventDefault,这里再次确认)
        btn.addEventListener('mousedown', (e) => {
            if (e.button !== 0) return;
            // 这一行非常重要:阻止鼠标按下时浏览器默认清除选区的行为
            e.preventDefault();

            isDragging = true; hasMoved = false;
            startX = e.clientX; startY = e.clientY;
            const rect = btn.getBoundingClientRect();
            initLeft = rect.left; initTop = rect.top;
            btn.style.transition = 'none';
        });


        document.addEventListener('click', (e) => {
            if (panel.classList.contains('show') && !panel.contains(e.target) && e.target !== btn && !btn.contains(e.target)) {
                togglePanel(false);
            }
        });

        function togglePanel(forceState) {
            const isVisible = panel.classList.contains('show');
            const shouldShow = forceState !== undefined ? forceState : !isVisible;
            if (shouldShow) {
                const btnRect = btn.getBoundingClientRect();
                const panelWidth = 340;
                const panelHeight = Math.min(window.innerHeight * 0.8, 600);
                let left = btnRect.right + 15;
                let top = btnRect.top;
                if (left + panelWidth > window.innerWidth) left = btnRect.left - panelWidth - 15;
                if (top + panelHeight > window.innerHeight) top = window.innerHeight - panelHeight - 20;
                if (top < 10) top = 10;

                panel.style.left = left + 'px';
                panel.style.top = top + 'px';
                panel.style.height = panelHeight + 'px';
                panel.style.display = 'flex';

                requestAnimationFrame(() => panel.classList.add('show'));
            } else {
                panel.classList.remove('show');
                setTimeout(() => { if (!panel.classList.contains('show')) panel.style.display = 'none'; }, 300);
            }
        }
    }




    // 更新按钮状态(用于配置保存后)
    function updateButtonState() {
        let btn = document.getElementById('gm-float-btn');
        if (!btn) return;
        btn.replaceChildren(gmc.get('btn_text'));
        if (gmc.get('show_button')) {
            btn.style.setProperty('display', 'flex', 'important');
        } else {
            btn.style.setProperty('display', 'none', 'important');
        }
    }

    // 注册油猴菜单命令(作为备用入口)
    GM_registerMenuCommand("打开工具箱面板", () => {
        const panel = document.getElementById('gm-toolbox-panel');
        // 如果没有显示按钮,临时显示面板在屏幕中心
        if (!document.getElementById('gm-float-btn').offsetParent) {
            if (panel) {
                panel.style.top = '100px';
                panel.style.left = '50%';
                panel.style.transform = 'translateX(-50%)';
                panel.style.display = 'block';
                setTimeout(() => panel.classList.add('show'), 10);
            }
        } else {
            // 模拟点击按钮
            document.getElementById('gm-float-btn').click();
        }
    });

    GM_registerMenuCommand("打开脚本设置界面", () => {
        gmc.open();
    });

    GM_registerMenuCommand("重置悬浮球位置", () => {
        const btn = document.getElementById('gm-float-btn');
        if (btn) {
            btn.style.top = DEFAULT_CONFIG.init_pos_top;
            btn.style.left = DEFAULT_CONFIG.init_pos_left;
            GM_setValue('pos_top', DEFAULT_CONFIG.init_pos_top);
            GM_setValue('pos_left', DEFAULT_CONFIG.init_pos_left);
            Utils.toast("已重置位置");
        }
    });

    GM_registerMenuCommand("重置面板排序", () => {
        GM_setValue('tools_order_map', {});
        Utils.toast("排序已重置,请刷新页面");
    });


    // --- 启动脚本 ---
    // 延迟加载,确保页面主体渲染完成,减少冲突
    (function main() {
        setTimeout(createUI, 300);
    })();
})();