Web+

综合性网页体验增强脚本。模块化设计,按需启用,精准匹配,性能优先。

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Advertisement:

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

Advertisement:

// ==UserScript==
// @name         Web+
// @namespace    WebPlus
// @description  综合性网页体验增强脚本。模块化设计,按需启用,精准匹配,性能优先。
// @version      1.0.1
// @license      MIT
// @author       Qiu Zongman
// @homepageURL  https://gitee.com/qiuzongman/WebPlus
// @match        *://*/*
// @connect      edge.microsoft.com
// @connect      api-edge.cognitive.microsofttranslator.com
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM.xmlHttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// @icon         data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTI5OSIgaGVpZ2h0PSIxMzAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiBvdmVyZmxvdz0iaGlkZGVuIj48ZGVmcz48Y2xpcFBhdGggaWQ9ImNsaXAwIj48cmVjdCB4PSIzMTAxIiB5PSI0ODIiIHdpZHRoPSIxMjk5IiBoZWlnaHQ9IjEzMDAiLz48L2NsaXBQYXRoPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjY2xpcDApIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzEwMSAtNDgyKSI+PHJlY3QgeD0iMzEwMSIgeT0iNDgzIiB3aWR0aD0iMTI5OSIgaGVpZ2h0PSIxMjk5IiBmaWxsPSIjRkZGRkZGIi8+PHJlY3QgeD0iMzEwMSIgeT0iMTE5NyIgd2lkdGg9IjU4NCIgaGVpZ2h0PSI1ODUiIGZpbGw9IiMzNEE4NTMiLz48cmVjdCB4PSIzMTAxIiB5PSI0ODIiIHdpZHRoPSI1ODQiIGhlaWdodD0iNTg0IiBmaWxsPSIjRUE0MzM1Ii8+PHJlY3QgeD0iMzgxNSIgeT0iMTE5NyIgd2lkdGg9IjU4NSIgaGVpZ2h0PSI1ODUiIGZpbGw9IiNGQkJDMDQiLz48cmVjdCB4PSIzODE1IiB5PSI3MDkiIHdpZHRoPSI1ODUiIGhlaWdodD0iMTMwIiBmaWxsPSIjNDI4NUY0Ii8+PHJlY3QgeD0iNDA0MyIgeT0iNDg0IiB3aWR0aD0iMTMwIiBoZWlnaHQ9IjU4NSIgZmlsbD0iIzQyODVGNCIvPjwvZz48L3N2Zz4=
// ==/UserScript==

(function () {
    'use strict';

    if (window.top !== window.self) return;

    // ===================================================================
    //  核心框架 — 设置管理
    // ===================================================================
    const STORAGE_KEY = 'wp_settings';
    const DEFAULT_SETTINGS = {
        lastTab: 4,
        pageToggleKeys: {
            translate: '',
        },
        pageEnabled: {
            translate: true,
            global: true,
            other: true,
            visual: true,
            speed: true,
            about: true,
        },
        // 各模块专属设置
        modules: {
            translate: {
                blockedSites: '',
                selTranslate: false,
                autoTranslate: false,
                btnMode: 'hide',
                pageShortcut: 'Alt+A',
                selShortcut: 'Alt+S',
                bilingualKey: '',
            },
            bilibili: {
                showIp: true,
                colorMode: '默认',
                cleanLinks: true,
                directLink: true,
                preloadBoost: true,
                hoverDelay: 65,
                cleanSources: {},
                highlightEnabled: false,
                highlightKeywords: '',
                highlightColor: '#FF0000',
                scrollbarStyle: 'default',
                scrollbarColor: '#FF0000',
                backToTop: false,
                spacingEnabled: false,
                unlockEnabled: false,
            }
        },
        // 全局设置
        openSettingsKey: '',
    };

    let settings = {};

    function loadSettings() {
        try {
            const raw = typeof GM_getValue === 'function' ? GM_getValue(STORAGE_KEY, null) : null;
            settings = raw ? { ...DEFAULT_SETTINGS, ...JSON.parse(raw) } : { ...DEFAULT_SETTINGS };
            if (!settings.pageEnabled) settings.pageEnabled = { ...DEFAULT_SETTINGS.pageEnabled };
            if (!settings.modules) settings.modules = { ...DEFAULT_SETTINGS.modules };
            // 确保每个模块默认值存在
            for (const key in DEFAULT_SETTINGS.modules) {
                if (!settings.modules[key]) settings.modules[key] = { ...DEFAULT_SETTINGS.modules[key] };
                else for (const dk in DEFAULT_SETTINGS.modules[key]) {
                    if (settings.modules[key][dk] === undefined) settings.modules[key][dk] = DEFAULT_SETTINGS.modules[key][dk];
                }
            }
        } catch (e) {
            settings = { ...DEFAULT_SETTINGS };
        }
    }

    function saveSettings() {
        try {
            if (typeof GM_setValue === 'function') {
                GM_setValue(STORAGE_KEY, JSON.stringify(settings));
            }
        } catch (e) { console.error('wp saveSettings error:', e); }
    }

    // ===================================================================
    //  核心框架 — 页面定义
    // ===================================================================
    const PAGES = [
        { id: 'translate',  icon: '🌐', label: '网页翻译' },
        { id: 'visual',    icon: '🎨', label: '视觉效果' },
        { id: 'speed',     icon: '⚡', label: '优化提速' },
        { id: 'other',     icon: '🧩', label: '零散工具' },
        { id: 'global',     icon: '⚙️', label: '全局设置' },
        { id: 'about',     icon: '📋', label: '关于' },
    ];

    // ===================================================================
    //  核心框架 — UI 面板
    // ===================================================================
    function buildPageContent(pageId) {
        // 由各模块覆盖扩展
        return '<div class="wp-demo-hint">功能待开发</div>';
    }

    function openSettings() {
        const existing = document.getElementById('wp-settings-panel');
        if (existing) { existing.remove(); return; }

        const panel = document.createElement('div');
        panel.id = 'wp-settings-panel';
        panel.innerHTML = buildHTML();
        document.body.appendChild(panel);
        bindEvents(panel);
        // 通知各模块刷新 UI(如翻译快捷键输入框的重置)
        document.dispatchEvent(new CustomEvent('wp-panel-open'));
    }

    function buildHTML() {
        const esc = (v) => String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
        const navItems = PAGES.map((p, i) => `
            <button class="wp-nav-btn" data-page="${p.id}" data-index="${i}">
                <span class="wp-nav-icon">${p.icon}</span>
                <span class="wp-nav-label">${p.label}</span>
            </button>
        `).join('');

        const pageContents = PAGES.map(p => {
            
            
            return `
                <div class="wp-page" data-page-id="${p.id}" style="display:none">
                    <div class="wp-page-body">
                        ${buildPageContent(p.id)}
                    </div>
                </div>
            `;
        }).join('');

        return `
<style>
/* ===================================================================
   🎨 web+ 设计系统
   =================================================================== */
#wp-settings-panel {
    --wp-panel-width:        550px;
    --wp-panel-max-w:        90vw;
    --wp-panel-max-h:        85vh;
    --wp-panel-h:            50vh;
    --wp-bg:                 #fff;
    --wp-border-clr:         #ddd;
    --wp-shadow:             0 8px 40px rgba(0,0,0,0.18);
    --wp-text:               #333;
    --wp-text-secondary:     #555;
    --wp-text-muted:         #888;
    --wp-text-hint:          #999;
    --wp-font:               -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
    --wp-fz-base:            13px;
    --wp-fz-body:            13px;
    --wp-fz-sm:              12px;
    --wp-fz-note:            12px;
    --wp-fz-nav-label:       12px;
    --wp-fz-icon:            18px;
    --wp-fz-title:           16px;
    --wp-lh-tight:           1.2;
    --wp-lh-base:            1.6;
    --wp-fw-title:           600;
    --wp-fw-active:          600;
    --wp-gap-2:              2px;
    --wp-gap-8:              8px;
    --wp-gap-10:             10px;
    --wp-gap-12:             12px;
    --wp-gap-14:             14px;
    --wp-pad-nav:            8px 0;
    --wp-pad-nav-btn:        10px 4px 8px;
    --wp-pad-page:           16px 20px;
    --wp-pad-footer:         12px 20px;
    --wp-pad-section:        14px 16px;
    --wp-pad-input:          5px 10px;
    --wp-pad-btn:            8px 18px;
    --wp-mr-nav-btn:         0 6px;
    --wp-mr-section-btm:     12px;
    --wp-mr-row-btm:         8px;
    --wp-mr-label-btm:       8px;
    --wp-mr-header-btm:      14px;
    --wp-mr-header-pad-btm:  10px;
    --wp-mr-hint-top:        4px;
    --wp-bdr-w:              1px;
    --wp-bdr-style:          solid;
    --wp-bdr-dashed:         dashed;
    --wp-radius-panel:       12px;
    --wp-radius-btn:         6px;
    --wp-radius-section:     8px;
    --wp-radius-input:       4px;
    --wp-t-fast:             0.15s;
    --wp-t-normal:           0.2s;
    --wp-hover-brightness:   0.85;
    --wp-nav-w:              88px;
    --wp-nav-bg:             #f5f7fa;
    --wp-nav-bdr-clr:        #e8ecf1;
    --wp-nav-btn-hover-bg:   #e3ecf7;
    --wp-nav-btn-hover-clr:  #000;
    --wp-nav-btn-active-bg:  #90caf9;
    --wp-nav-btn-active-clr: #000;
    --wp-nav-indicator-clr:  #1565c0;
    --wp-nav-indicator-w:    3px;
    --wp-nav-indicator-h:    28px;
    --wp-nav-indicator-left: -6px;
    --wp-nav-indicator-rad:  0 2px 2px 0;
    --wp-title-clr:          #222;
    --wp-header-bdr-clr:     #eee;
    --wp-body-clr:           #555;
    --wp-toggle-on:          #4CAF50;
    --wp-toggle-off:         #ccc;
    --wp-toggle-w:           36px;
    --wp-toggle-h:           20px;
    --wp-toggle-knob:        16px;
    --wp-toggle-knob-top:    2px;
    --wp-toggle-knob-left:   2px;
    --wp-toggle-knob-shadow: 0 1px 3px rgba(0,0,0,0.2);
    --wp-toggle-label-min-w: 3em;
    --wp-btn-reset-bg:       #ff9800;
    --wp-btn-close-bg:       #f44336;
    --wp-section-bg:         #f8f9fb;
    --wp-section-bdr-clr:    #d0d5dd;
    --wp-input-bdr-clr:      #ccc;
    --wp-input-max-w:        200px;
    --wp-input-label-min-w:  60px;
    --wp-footer-bg:          #fafbfc;
    --wp-footer-bdr-clr:     #e8ecf1;

    all: initial;
    position: fixed; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2147483647;
    width: var(--wp-panel-width); max-width: var(--wp-panel-max-w);
    height: var(--wp-panel-h); max-height: var(--wp-panel-max-h);
    background: var(--wp-bg);
    border: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-border-clr);
    border-radius: var(--wp-radius-panel);
    box-shadow: var(--wp-shadow);
    font-family: var(--wp-font);
    font-size: var(--wp-fz-base);
    color: var(--wp-text);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    pointer-events: auto;
    box-sizing: border-box;
}
#wp-settings-panel * { box-sizing: border-box; }
.wp-body { display: flex; flex: 1; min-height: 0; }

/* ---- 导航 ---- */
.wp-nav {
    width: var(--wp-nav-w); flex-shrink: 0;
    background: var(--wp-nav-bg);
    border-right: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-nav-bdr-clr);
    padding: var(--wp-pad-nav);
    display: flex; flex-direction: column; gap: var(--wp-gap-2); overflow-y: auto;
}
.wp-nav-btn {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--wp-gap-2); padding: var(--wp-pad-nav-btn); border: none;
    background: transparent; cursor: pointer; margin: var(--wp-mr-nav-btn);
    border-radius: var(--wp-radius-btn); color: var(--wp-text-secondary);
    transition: all var(--wp-t-fast) ease; font-family: inherit;
    font-size: var(--wp-fz-note); line-height: var(--wp-lh-tight); position: relative;
}
.wp-nav-btn:hover { background: var(--wp-nav-btn-hover-bg); color: var(--wp-nav-btn-hover-clr); }
.wp-nav-btn.active {
    background: var(--wp-nav-btn-active-bg); color: var(--wp-nav-btn-active-clr);
    font-weight: var(--wp-fw-active);
}
.wp-nav-btn.active::before {
    content: ''; position: absolute; left: var(--wp-nav-indicator-left);
    top: 50%; transform: translateY(-50%);
    width: var(--wp-nav-indicator-w); height: var(--wp-nav-indicator-h);
    background: var(--wp-nav-indicator-clr); border-radius: var(--wp-nav-indicator-rad);
}
.wp-nav-icon { font-size: var(--wp-fz-icon); line-height: 1; }
.wp-nav-label { font-size: var(--wp-fz-nav-label); line-height: var(--wp-lh-tight); white-space: nowrap; }

/* ---- 内容 ---- */
.wp-content { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; padding: 0; }
.wp-page { flex: 1; display: flex; flex-direction: column; min-height: 0; padding: var(--wp-pad-page); overflow-y: auto; scrollbar-gutter: stable; }
/* 自定义滚动条 — 始终可见 */
.wp-page::-webkit-scrollbar { width: 7px; }
.wp-page::-webkit-scrollbar-track { background: transparent; }
.wp-page::-webkit-scrollbar-thumb {
    background: #c0c8d4;
    border-radius: 4px;
    min-height: 40px;
}
.wp-page::-webkit-scrollbar-thumb:hover { background: #90a0b4; }
.wp-page-body { flex: 1; color: var(--wp-body-clr); font-size: var(--wp-fz-base); line-height: var(--wp-lh-base); }

/* ---- 开关 ---- */
.wp-toggle { display: inline-flex; align-items: center; gap: var(--wp-gap-8); cursor: pointer; user-select: none; }
.wp-toggle input { display: none; }
.wp-toggle-slider {
    position: relative; width: var(--wp-toggle-w); height: var(--wp-toggle-h);
    background: var(--wp-toggle-off); border-radius: calc(var(--wp-toggle-h) / 2);
    transition: background var(--wp-t-normal);
}
.wp-toggle-slider::after {
    content: ''; position: absolute;
    top: var(--wp-toggle-knob-top); left: var(--wp-toggle-knob-left);
    width: var(--wp-toggle-knob); height: var(--wp-toggle-knob);
    background: #fff; border-radius: 50%; transition: transform var(--wp-t-normal);
    box-shadow: var(--wp-toggle-knob-shadow);
}
.wp-toggle input:checked + .wp-toggle-slider { background: var(--wp-toggle-on); }
.wp-toggle input:checked + .wp-toggle-slider::after {
    transform: translateX(calc(var(--wp-toggle-w) - var(--wp-toggle-knob) - 4px));
}
.wp-toggle-label { font-size: var(--wp-fz-sm); color: var(--wp-text-muted); min-width: var(--wp-toggle-label-min-w); }
.wp-toggle input:checked ~ .wp-toggle-label { color: var(--wp-toggle-on); }

/* ---- 底部 ---- */
.wp-footer {
    display: flex; justify-content: flex-end; align-items: center;
    gap: var(--wp-gap-10); padding: var(--wp-pad-footer);
    border-top: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-footer-bdr-clr);
    background: var(--wp-footer-bg);
}
.wp-btn {
    padding: var(--wp-pad-btn); border: none; border-radius: var(--wp-radius-btn);
    cursor: pointer; font-size: var(--wp-fz-base); font-family: inherit;
    transition: filter var(--wp-t-fast);
}
.wp-btn:hover { filter: brightness(var(--wp-hover-brightness)); }
.wp-btn-reset { background: var(--wp-btn-reset-bg); color: #fff; margin-right: auto; }
.wp-btn-close { background: var(--wp-btn-close-bg); color: #fff; }

/* ---- 表单控件 ---- */
.wp-section {
    background: var(--wp-section-bg);
    border: var(--wp-bdr-w) var(--wp-bdr-dashed) var(--wp-section-bdr-clr);
    border-radius: var(--wp-radius-section); padding: var(--wp-pad-section);
    margin-bottom: var(--wp-mr-section-btm);
}
.wp-section-label {
    font-size: var(--wp-fz-base); font-weight: normal; color: var(--wp-text);
    margin-bottom: var(--wp-mr-label-btm); display: block;
}
.wp-row {
    display: flex; align-items: center; gap: var(--wp-gap-10); margin-bottom: var(--wp-mr-row-btm);
}
.wp-row label { min-width: var(--wp-input-label-min-w); color: var(--wp-text-secondary); }
.wp-row select, .wp-row input[type="text"], .wp-row input[type="number"] {
    padding: var(--wp-pad-input);
    border: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-input-bdr-clr);
    border-radius: var(--wp-radius-input); font-size: var(--wp-fz-base);
    background: var(--wp-bg); flex: 1; max-width: var(--wp-input-max-w);
}
.wp-hint { font-size: var(--wp-fz-sm); color: var(--wp-text-hint); margin-top: var(--wp-mr-hint-top); }

/* ---- 快捷键捕获输入框 ---- */
.wp-row input.wp-key-input,
input.wp-key-input {
    padding: var(--wp-pad-input);
    border: var(--wp-bdr-w) var(--wp-bdr-style) #90caf9;
    border-radius: var(--wp-radius-input); font-size: var(--wp-fz-base);
    background: #90caf9; color: #000; text-align: center; cursor: pointer;
    flex: 1; max-width: var(--wp-input-max-w);
    transition: border-color var(--wp-t-fast), box-shadow var(--wp-t-fast);
}
.wp-row input.wp-key-input:focus,
input.wp-key-input:focus {
    outline: none;
    border-color: #1976d2;
    box-shadow: 0 0 0 2px rgba(25,118,210,0.25);
    background: #90caf9;
}

/* ---- 双选项按钮 ---- */
.wp-dual {
    display: inline-flex; gap: 0;
    flex: 1; max-width: var(--wp-input-max-w);
    border-radius: var(--wp-radius-input);
    overflow: hidden;
    border: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-nav-btn-active-bg);
}
.wp-dual-btn {
    flex: 1; padding: 5px 0;
    border: none; cursor: pointer;
    font-size: var(--wp-fz-sm); font-family: inherit;
    background: #e3f2fd; color: #000;
    transition: background var(--wp-t-fast), color var(--wp-t-fast);
}
.wp-dual-btn:first-child { border-right: var(--wp-bdr-w) var(--wp-bdr-style) var(--wp-nav-btn-active-bg); }
.wp-dual-btn.active {
    background: #90caf9; color: #000;
}
</style>

<div class="wp-body">
    <nav class="wp-nav">${navItems}</nav>
    <div class="wp-content">${pageContents}</div>
</div>
<div class="wp-footer">
    <button class="wp-btn wp-btn-close" id="wp-close">关闭</button>
</div>
`;
    }

    function bindEvents(panel) {
        const navBtns = panel.querySelectorAll('.wp-nav-btn');
        const pages = panel.querySelectorAll('.wp-page');
        const toggles = panel.querySelectorAll('.wp-page-toggle');

        function switchPage(index) {
            const pageId = PAGES[index].id;
            navBtns.forEach((btn, i) => btn.classList.toggle('active', i === index));
            pages.forEach(p => { p.style.display = p.dataset.pageId === pageId ? 'flex' : 'none'; });
            settings.lastTab = index;
            saveSettings();
        }

        navBtns.forEach((btn, i) => { btn.addEventListener('click', () => switchPage(i)); });

        const initIndex = Math.min(settings.lastTab || 0, PAGES.length - 1);
        switchPage(initIndex);

        // 页面开关 — 即时保存
        toggles.forEach(t => {
            t.addEventListener('change', function () {
                const label = this.nextElementSibling.nextElementSibling;
                if (this.checked) {
                    label.textContent = '已启用';
                    label.style.color = 'var(--wp-toggle-on, #4CAF50)';
                } else {
                    label.textContent = '已关闭';
                    label.style.color = 'var(--wp-text-muted, #888)';
                }
                settings.pageEnabled[this.dataset.page] = this.checked;
                saveSettings();
                document.dispatchEvent(new CustomEvent('wp-module-toggle', {
                    detail: { page: this.dataset.page, enabled: this.checked }
                }));
            });
        });
        // 监听外部(如快捷键)触发的模块切换,同步 UI
        document.addEventListener('wp-module-toggle', function syncToggle(e) {
            var cb = panel.querySelector('.wp-page-toggle[data-page="' + e.detail.page + '"]');
            if (!cb) return;
            cb.checked = e.detail.enabled;
            var label = cb.nextElementSibling.nextElementSibling;
            if (label) {
                label.textContent = e.detail.enabled ? '已启用' : '已关闭';
                label.style.color = e.detail.enabled ? 'var(--wp-toggle-on, #4CAF50)' : 'var(--wp-text-muted, #888)';
            }
        });
        // 全局设置 — 网页翻译开关
        var globalTrans = panel.querySelector('#wp-global-translate');
        if (globalTrans) {
            globalTrans.querySelectorAll('.wp-dual-btn').forEach(function(btn) {
                btn.addEventListener('click', function() {
                    var v = this.dataset.value === 'true';
                    settings.pageEnabled.translate = v;
                    saveSettings();
                    globalTrans.querySelectorAll('.wp-dual-btn').forEach(function(x) {
                        x.classList.toggle('active', x.dataset.value === String(v));
                    });
                    document.dispatchEvent(new CustomEvent('wp-module-toggle', {
                        detail: { page: 'translate', enabled: v }
                    }));
                });
            });
        }

        // 关闭
        panel.querySelector('#wp-close').addEventListener('click', () => panel.remove());

        // 恢复默认(在全局页面底部)
        panel.querySelector('#wp-reset-global').addEventListener('click', () => {
            settings = { ...DEFAULT_SETTINGS };
            saveSettings();
            panel.remove();
            location.reload();
        });

        // 快捷键捕获
        document.querySelectorAll('.wp-key-input').forEach(function(inp) {
            inp.addEventListener('focus', function() { console.log('wp-key focus on', this.id); this.value = ''; });
            inp.addEventListener('keydown', function(e) {
                console.log('wp-key keydown on', this.id, 'key:', e.key);
                if (e.key === 'Escape' || e.key === 'Delete' || e.key === 'Backspace') {
                    this.value = ''; saveSettings(); this.blur(); e.preventDefault(); return;
                }
                if (['Control','Alt','Shift','Meta'].indexOf(e.key) >= 0) return;
                var k = e.key === ' ' ? 'Space' : e.key;
                if (k.length > 1 && !/^F\d+$/.test(k)) return;
                if (e.ctrlKey) k = 'Ctrl+' + k;
                if (e.altKey) k = 'Alt+' + k;
                if (e.shiftKey) k = 'Shift+' + k;
                this.value = k; saveSettings(); this.blur(); e.preventDefault();
            });
            inp.addEventListener('blur', function() {
                var val = this.value;
                if (this.id === 'wp-openSettingsKey') settings.openSettingsKey = val;
                else if (this.id === 'wp-toggle-key-translate') { if (!settings.pageToggleKeys) settings.pageToggleKeys = {}; settings.pageToggleKeys.translate = val; }
                else if (this.classList.contains('wp-trans-key')) { settings.modules.translate[this.dataset.key] = val; }
                else if (this.dataset.key) {
                    var p = this.dataset.key.split('.');
                    var o = settings;
                    for (var pi = 0; pi < p.length - 1; pi++) { if (!o[p[pi]]) o[p[pi]] = {}; o = o[p[pi]]; }
                    o[p[p.length - 1]] = val;
                }
                saveSettings();
            });
        });

        function onEsc(e) { if (e.key === 'Escape') { panel.remove(); document.removeEventListener('keydown', onEsc); } }
        document.addEventListener('keydown', onEsc);

        // 更新所有外部规则
        var updateBtn = document.getElementById('wp-update-rules');
        var updateStatus = document.getElementById('wp-update-rules-status');
        if (updateBtn && updateStatus) {
            updateBtn.addEventListener('click', function() {
                updateBtn.disabled = true; updateBtn.textContent = '更新中…';
                var srcs = settings.modules.bilibili && settings.modules.bilibili.cleanSources || {};
                var urls = Object.keys(srcs);
                if (!urls.length) { updateStatus.textContent = '没有已加载的外部规则。'; updateBtn.disabled = false; updateBtn.textContent = '🔄 更新所有外部链接文件'; return; }
                var done = 0, total = urls.length;
                urls.forEach(function(url) {
                    try {
                        BILI_MODULE.loadCleanSource(url, function() { done++; updateStatus.textContent = '更新中 ' + done + '/' + total; if (done >= total) { updateStatus.textContent = '✅ 更新完成'; updateBtn.disabled = false; updateBtn.textContent = '🔄 更新所有外部链接文件'; } });
                    } catch(e) { done++; if (done >= total) { updateStatus.textContent = '更新完成'; updateBtn.disabled = false; updateBtn.textContent = '🔄 更新所有外部链接文件'; } }
                });
            });
        }

        // 通知各模块绑定面板事件
        try { TRANS_MODULE.bindPanelEvents(); } catch(e) {}
        try { BILI_MODULE.bindPanelEvents(); } catch(e) {}
    }

    // ===================================================================
    //  翻译模块
    // ===================================================================

    const TRANS_MODULE = (function() {
        'use strict';
        if (window.top !== window.self) return;

    const AUTH = 'https://edge.microsoft.com/translate/auth';
    const API = 'https://api-edge.cognitive.microsofttranslator.com/translate?api-version=3.0&to=zh-Hans&textType=plain';
    const API_HTML = 'https://api-edge.cognitive.microsofttranslator.com/translate?api-version=3.0&to=zh-Hans&textType=html';
    const MAX_UNITS = 5000, MAX_CHARS = 500000, CHUNK_NODES = 25, CHUNK_CHARS = 8000, CONCURRENCY = 6, CACHE_LIMIT = 5000;

    let token = '', tokenTime = 0, running = false;
    let cache = {}, revCache = {};
    let cacheKeys = [];
    let isTranslated = false, translateInProgress = false, translateCount = 0;
    let showRestore = false;

    function mod(){return settings.modules.translate||{}}
    function mod(){return settings.modules.translate||{}}
    let everTranslated = false;

    const ZH_RE = /[\u4e00-\u9fff]/;
    const HTML_PREFIX = '__MS_AUTO_ZH_HTML__';
    function htmlKey(s) { return HTML_PREFIX + s; }
    function stripHtmlKey(s) { return s.startsWith(HTML_PREFIX) ? s.slice(HTML_PREFIX.length) : s; }

    function setCache(src, dst) {
        if (!src || !dst || cache[src]) return;
        dst = dst.trim();
        cache[src] = dst;
        // 多对一映射:相同译文可对应多份原文,存数组
        if (revCache[dst] === undefined) {
            revCache[dst] = src;
        } else if (Array.isArray(revCache[dst])) {
            revCache[dst].push(src);
        } else if (revCache[dst] !== src) {
            revCache[dst] = [revCache[dst], src];
        }
        cacheKeys.push(src);
        if (cacheKeys.length > CACHE_LIMIT) {
            const old = cacheKeys.shift();
            const oldDst = cache[old];
            if (oldDst) {
                if (Array.isArray(revCache[oldDst])) {
                    revCache[oldDst] = revCache[oldDst].filter(v => v !== old);
                    if (revCache[oldDst].length === 0) delete revCache[oldDst];
                    else if (revCache[oldDst].length === 1) revCache[oldDst] = revCache[oldDst][0];
                } else {
                    delete revCache[oldDst];
                }
            }
            delete cache[old];
        }
    }
    // revCache 读值辅助(兼容多对一)
    function revGet(dst) {
        const v = revCache[dst];
        if (v === undefined) return null;
        return Array.isArray(v) ? v[0] : v;
    }

    // ---------- 扫描渲染函数 ----------
    const MODE_ORIG = 0, MODE_TRANS = 1, MODE_BI = 2;
    let currentMode = MODE_ORIG;

    function escHtml(s) {
        return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
    }

    function cleanupBiSpans() {
        let list = document.querySelectorAll('.ms-bi');
        let iter = 0;
        while (list.length > 0 && iter < 10) {
            for (let i = list.length - 1; i >= 0; i--) {
                const w = list[i];
                if (!w.parentNode) continue;
                const biType = w.dataset.biType || 'text';
                const transEl = w.querySelector('.ms-bi-trans');
                const transText = transEl ? (biType === 'block' ? transEl.innerHTML : transEl.textContent)
                                           : (w.dataset.trans || '');
                if (biType === 'block') {
                    w.parentNode.innerHTML = transText;
                } else {
                    w.parentNode.replaceChild(document.createTextNode(transText), w);
                }
            }
            list = document.querySelectorAll('.ms-bi');
            iter++;
        }
    }

    function renderOriginalFromCache() {
        cleanupBiSpans();
        const all = document.querySelectorAll('body *');
        for (let i = 0; i < all.length; i++) {
            const el = all[i];
            const cur = el.innerHTML;
            var orig = revGet(cur);
            if (orig) {
                el.innerHTML = stripHtmlKey(orig);
            }
        }
        const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
        let n;
        while ((n = walker.nextNode())) {
            const v = n.nodeValue;
            const trimmed = v.trim();
            var origText = revGet(trimmed);
            if (origText) {
                n.nodeValue = v.split(trimmed).join(origText);
            }
        }
        currentMode = MODE_ORIG;
    }

    function renderTranslationFromCache() {
        cleanupBiSpans();
        const all = document.querySelectorAll('body *');
        for (let i = 0; i < all.length; i++) {
            const el = all[i];
            const key = htmlKey(el.innerHTML);
            if (cache[key]) {
                el.innerHTML = cache[key];
            }
        }
        const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
        let n;
        while ((n = walker.nextNode())) {
            const v = n.nodeValue;
            const trimmed = v.trim();
            if (cache[trimmed]) {
                n.nodeValue = v.split(trimmed).join(cache[trimmed]);
            }
        }
        currentMode = MODE_TRANS;
    }

    function renderBilingual() {
        cleanupBiSpans();
        const processedEls = new WeakSet();
        const nodesToReplace = [];

        const allEls = document.querySelectorAll('body *');
        for (const el of allEls) {
            const curHtml = el.innerHTML;
            const key = htmlKey(curHtml);
            let origHtml, transHtml;
            if (cache[key]) {
                origHtml = stripHtmlKey(key);
                transHtml = cache[key];
            } else {
                var origFromHtml = revGet(curHtml);
                if (origFromHtml) {
                    transHtml = curHtml;
                    origHtml = stripHtmlKey(origFromHtml);
                } else continue;
            }

            processedEls.add(el);
            nodesToReplace.push({ type: 'block', el, origHtml, transHtml });
        }

        const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
        let n;
        while ((n = walker.nextNode())) {
            if (n.parentElement && processedEls.has(n.parentElement)) continue;
            const v = n.nodeValue;
            const trimmed = v.trim();
            let orig, trans;
            if (cache[trimmed]) {
                orig = trimmed;
                trans = cache[trimmed];
            } else {
                var origTextFromRev = revGet(trimmed);
                if (origTextFromRev) {
                    orig = origTextFromRev;
                    trans = trimmed;
                } else continue;
            }

            const idx = v.indexOf(trimmed);
            if (idx < 0) continue;
            const head = v.slice(0, idx);
            const tail = v.slice(idx + trimmed.length);
            nodesToReplace.push({ type: 'text', node: n, head, orig, trans, tail });
        }

        for (const item of nodesToReplace) {
            if (item.type === 'block') {
                const wrapper = document.createElement('span');
                wrapper.className = 'ms-bi';
                wrapper.dataset.biType = 'block';
                wrapper.dataset.trans = item.transHtml;
                wrapper.innerHTML = `<span class="ms-bi-orig">${item.origHtml}</span><span class="ms-bi-gap"> | </span><span class="ms-bi-trans">${item.transHtml}</span>`;
                item.el.innerHTML = '';
                item.el.appendChild(wrapper);
            } else {
                const wrapper = document.createElement('span');
                wrapper.className = 'ms-bi';
                wrapper.dataset.biType = 'text';
                wrapper.dataset.trans = item.trans;
                wrapper.innerHTML = `<span class="ms-bi-orig">${escHtml(item.orig)}</span><span class="ms-bi-gap"> | </span><span class="ms-bi-trans">${escHtml(item.trans)}</span>`;
                const parent = item.node.parentNode;
                if (!parent) continue;
                parent.insertBefore(document.createTextNode(item.head), item.node);
                parent.insertBefore(wrapper, item.node);
                parent.insertBefore(document.createTextNode(item.tail), item.node);
                parent.removeChild(item.node);
            }
        }

        currentMode = MODE_BI;
    }

    // ---------- 翻译收集与 API 调用 ----------
    const EXT_RE = /\.(apk|zip|7z|rar|tar|gz|iso|json|xml|yaml|txt|md|exe|dll|js|css|html)$/i;
    const URL_RE = /^https?:\/\//i;
    const TECH_STR_RE = /^[A-Za-z0-9._+@#:/\\()[\]-]+$/;
    const HEX_RE = /^[a-f0-9]{7,40}$/i;
    const VERSION_RE = /^v?\d+(\.\d+){1,4}([-+][A-Za-z0-9._-]+)?$/i;
    const CONST_RE = /^[A-Z0-9_]{2,12}$/;
    const CAMEL_RE = /^[A-Za-z]+[A-Z][A-Za-z0-9_.$-]*$/;
    const PUNCT_CODE_RE = /[,:;()]/;
    const METHOD_RE = /\b[A-Za-z_$][A-Za-z0-9_$]*\s*\(\s*\)/g;
    const ID_RE = /\b[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*\b/g;
    const COMMON_EN_WORDS = new Set(['the','a','an','is','are','was','were','be','been','have','has','had','do','does','did','will','would','shall','should','may','might','can','could','of','in','on','at','to','for','with','and','or','but','not','this','that','these','those','it','they','we','you','he','she','i','from','by','about','as','into','like','through','after','over','between','out','against','during','without','before','under','around','among']);

    function hasNaturalLanguage(s) {
        let count = 0;
        const words = s.toLowerCase().split(/\s+/);
        for (const w of words) if (COMMON_EN_WORDS.has(w) && ++count >= 2) return true;
        return false;
    }
    function req(o, ok, bad) {
        const opt = { method: o.method || 'GET', url: o.url, headers: o.headers || {}, data: o.data, timeout: o.timeout || 16000 };
        if (typeof GM !== 'undefined' && GM.xmlHttpRequest)
            GM.xmlHttpRequest(opt).then(ok).catch(e => bad(e?.message || String(e)));
        else if (typeof GM_xmlhttpRequest === 'function')
            GM_xmlhttpRequest({ method: opt.method, url: opt.url, headers: opt.headers, data: opt.data, timeout: opt.timeout, onload: ok, onerror: () => bad('请求失败'), ontimeout: () => bad('请求超时') });
        else
            fetch(opt.url, { method: opt.method, headers: opt.headers, body: opt.data })
                .then(r => r.text().then(t => ok({ status: r.status, responseText: t })))
                .catch(e => bad(e?.message || String(e)));
    }
    function cleanup(s) {
        s = String(s || '').trim();
        s = s.replace(/([\u4e00-\u9fff])\s+(?=[\u4e00-\u9fff])/g, '$1');
        s = s.replace(/\s+([,。!?:;、)】》])/g, '$1');
        s = s.replace(/([(【《])\s+/g, '$1');
        s = s.replace(/([\u4e00-\u9fff])\s+([,。!?:;、])/g, '$1$2');
        return s;
    }
    function isCJK(cp) { return cp >= 0x4E00 && cp <= 0x9FFF; }
    function isLatin(cp) { return (cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A); }
    function isLetter(cp) {
        return (cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A) ||
               (cp >= 0xC0 && cp <= 0x24F) || (cp >= 0x370 && cp <= 0x3FF) ||
               (cp >= 0x400 && cp <= 0x52F) || (cp >= 0x590 && cp <= 0x5FF) ||
               (cp >= 0x600 && cp <= 0x6FF) || (cp >= 0x900 && cp <= 0xDFF) ||
               (cp >= 0xE00 && cp <= 0xE7F) || (cp >= 0x1000 && cp <= 0x109F) ||
               (cp >= 0x10A0 && cp <= 0x10FF) || (cp >= 0x1200 && cp <= 0x137F) ||
               (cp >= 0x1780 && cp <= 0x17FF) || (cp >= 0x3040 && cp <= 0x309F) ||
               (cp >= 0x30A0 && cp <= 0x30FF) || (cp >= 0xAC00 && cp <= 0xD7AF);
    }
    function countLetters(t, nonLatinOnly) {
        return Array.from(String(t || '')).filter(c => {
            const cp = c.charCodeAt(0);
            if (isCJK(cp)) return false;
            if (nonLatinOnly && isLatin(cp)) return false;
            return isLetter(cp);
        }).length;
    }
    function should(t) {
        if (!t || t.length < 2) return false;
        if (protect(t)) return false;
        let zhCount = 0;
        for (let i = 0; i < t.length; i++) if (isCJK(t.charCodeAt(i))) zhCount++;
        if (zhCount > 0) return countLetters(t, true) >= 1;
        return countLetters(t, false) >= 1;
    }
    function protect(t) {
        const s = String(t || '').trim();
        if (!s || s.length < 2) return true;
        if (EXT_RE.test(s)) return true;
        if (URL_RE.test(s)) return true;
        if (TECH_STR_RE.test(s) && (s.match(/[._@#:/\\()[\]-]/g) || []).length >= 2) return true;
        if (HEX_RE.test(s)) return true;
        if (VERSION_RE.test(s)) return true;
        if (CONST_RE.test(s) && !/^(ALL|TOP|NEW|YES|NO|OK)$/.test(s)) return true;
        if (/^\.[a-zA-Z]/.test(s)) return true;
        if (/^[a-zA-Z0-9][a-zA-Z0-9._-]*\.[a-zA-Z0-9]{2,6}$/.test(s)) return true;
        if (CAMEL_RE.test(s)) return true;
        const methodCalls = s.match(METHOD_RE) || [];
        if (methodCalls.length >= 2) return true;
        const ids = s.match(ID_RE) || [];
        let codeIds = 0;
        for (const id of ids) {
            if (/[a-z][A-Z]|[_$]/.test(id) || /^[A-Z][A-Za-z0-9_$]*[A-Z][A-Za-z0-9_$]*$/.test(id)) codeIds++;
        }
        if (methodCalls.length >= 1 && codeIds >= 1) return true;
        if (codeIds >= 3 && PUNCT_CODE_RE.test(s)) return !hasNaturalLanguage(s);
        let hasAnyLetter = false;
        for (let i = 0; i < s.length; i++) {
            const cp = s.charCodeAt(i);
            if (isCJK(cp) || isLetter(cp)) { hasAnyLetter = true; break; }
        }
        if (!hasAnyLetter || /^[A-Z]$/.test(s)) return true;
        return false;
    }
    function rectEl(el) {
        if (!el || el.nodeType !== 1) return null;
        const r = el.getBoundingClientRect();
        if (!r || (r.width === 0 && r.height === 0)) return { top: 1e9, bottom: 0, left: 0, right: 0, width: 0, height: 0 };
        return r;
    }
    function simpleRichBlock(el) {
        if (!el || !el.querySelector) return false;
        if (el.querySelector('pre,code,kbd,samp,var,script,style,textarea,input,select,button,svg,canvas,math,table')) return false;
        const forbidden = el.querySelectorAll('*:not(a,span,b,strong,i,em,u,mark,small,sub,sup,br,ul,ol,li,nav,aside,header,footer)');
        return forbidden.length === 0;
    }
    function collectShadowRoots(root) {
        const roots = [];
        const all = root.querySelectorAll('*');
        for (const el of all) {
            if (el.shadowRoot) {
                roots.push(el.shadowRoot);
                roots.push(...collectShadowRoots(el.shadowRoot));
            }
        }
        return roots;
    }

    let pendingWrites = [], rafId = null;
    function writeUnit(unit, dst) {
        if (!unit) return;
        if (unit.type === 'html') {
            if (unit.el?.isConnected) unit.el.innerHTML = dst;
        } else if (unit.type === 'el') {
            if (unit.el?.isConnected) unit.el.textContent = dst;
        } else if (unit.type === 'attr') {
            if (unit.el?.isConnected) unit.el.setAttribute(unit.attr, dst);
        } else {
            if (unit.node?.parentNode) unit.node.nodeValue = unit.head + dst + unit.tail;
        }
    }
    function scheduleWrite(unit, dst) {
        pendingWrites.push({ unit, dst });
        if (!rafId) {
            rafId = requestAnimationFrame(() => {
                const writes = pendingWrites;
                pendingWrites = [];
                rafId = null;
                for (const w of writes) writeUnit(w.unit, w.dst);
            });
        }
    }

    function collectUnits() {
        let cacheHits = 0;
        const list = [];
        const processedEls = new Set();
        const processedNodes = new WeakSet();

        function walkTextNodes(root) {
            const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
            let n;
            while ((n = w.nextNode())) {
                if (n.parentElement?.closest('pre,code,kbd,samp,var')) continue;
                if (processedNodes.has(n)) continue;
                const raw = n.nodeValue || '';
                const text = raw.trim();
                if (!should(text)) continue;
                const r = rectEl(n.parentElement);
                if (!r) continue;
                const trimmedStart = raw.trimStart();
                const head = raw.slice(0, raw.length - trimmedStart.length);
                const trimmedEnd = raw.trimEnd();
                const tail = raw.slice(trimmedEnd.length);
                if (cache[text]) {
                    // 立即写入已缓存内容,恢复初版的速度感
                    writeUnit({ type: 'node', node: n, raw, text, head, tail, top: r.top }, cache[text]);
                    cacheHits++;
                    continue;
                }
                list.push({ type: 'node', node: n, raw, text, head, tail, top: r.top });
                processedNodes.add(n);
                if (list.length >= MAX_UNITS) return;
            }
        }
        function walkAttrs(root) {
            const ATTR_NAMES = ['title', 'placeholder', 'aria-label'];
            const all = root.querySelectorAll('*');
            for (const el of all) {
                for (const attr of ATTR_NAMES) {
                    const val = (el.getAttribute(attr) || '').trim();
                    if (!val || !should(val)) continue;
                    if (cache[val]) {
                        // 属性立即写入
                        el.setAttribute(attr, cache[val]);
                        cacheHits++;
                        continue;
                    }
                    list.push({ type: 'attr', el, attr, text: val, top: 0 });
                    if (list.length >= MAX_UNITS) return;
                }
                if (list.length >= MAX_UNITS) return;
            }
        }

        const blocks = document.querySelectorAll('p,blockquote,dd,figcaption,summary,h1,h2,h3,h4,h5,h6');
        for (const el of blocks) {
            if (processedEls.has(el)) continue;
            if (!simpleRichBlock(el)) continue;
            const trimmed = el.textContent.trim();
            if (!trimmed || trimmed.length > 1600) continue;
            if (!should(trimmed) && countLetters(trimmed, true) < 2) continue;
            const html = el.innerHTML.trim();
            const key = htmlKey(html);
            if (cache[key]) {
                const r = rectEl(el);
                if (r) {
                    // 立即写入已缓存的 HTML 块
                    writeUnit({ type: 'html', el, text: key, top: r.top }, cache[key]);
                    cacheHits++;
                    processedEls.add(el);
                }
                continue;
            }
            const r = rectEl(el);
            if (!r) continue;
            processedEls.add(el);
            list.push({ type: 'html', el, text: key, top: r.top });
            const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
            let node;
            while ((node = walker.nextNode())) processedNodes.add(node);
            if (list.length >= MAX_UNITS) break;
        }

        const elements = document.querySelectorAll('p,li,h1,h2,h3,h4,h5,h6,dt,dd,figcaption,summary,blockquote,a');
        for (const el of elements) {
            if (processedEls.has(el)) continue;
            if (el.tagName === 'A' && el.children.length > 0) {
                if (el.querySelector('img,svg,canvas') || el.children.length > 3) continue;
            } else if (el.children.length > 0) continue;
            const trimmed = el.textContent.trim();
            if (!should(trimmed) || trimmed.length > 1200) continue;
            if (cache[trimmed]) {
                const r = rectEl(el);
                if (r) {
                    // 立即写入已缓存的纯文本元素
                    writeUnit({ type: 'el', el, text: trimmed, top: r.top }, cache[trimmed]);
                    cacheHits++;
                    processedEls.add(el);
                }
                continue;
            }
            const r = rectEl(el);
            if (!r) continue;
            processedEls.add(el);
            list.push({ type: 'el', el, text: trimmed, top: r.top });
            if (list.length >= MAX_UNITS) break;
        }

        walkTextNodes(document.body);
        walkAttrs(document.body);
        const shadowRoots = collectShadowRoots(document.body);
        for (const shadow of shadowRoots) {
            walkTextNodes(shadow);
            walkAttrs(shadow);
            if (list.length >= MAX_UNITS) break;
        }

        return { list, cacheHits };
    }

    function collect() {
        return new Promise(resolve => {
            const doCollect = () => {
                const result = collectUnits();
                result.list.sort((a, b) => {
                    const av = a.top >= 0 && a.top <= innerHeight ? 0 : 1;
                    const bv = b.top >= 0 && b.top <= innerHeight ? 0 : 1;
                    if (av !== bv) return av - bv;
                    return Math.abs(a.top - innerHeight / 2) - Math.abs(b.top - innerHeight / 2);
                });
                const out = [];
                let chars = 0;
                for (const unit of result.list) {
                    out.push(unit);
                    chars += unit.text.length;
                    if (out.length >= MAX_UNITS || chars >= MAX_CHARS) break;
                }
                resolve({ list: out, cacheHits: result.cacheHits });
            };
            if (typeof requestIdleCallback === 'function') {
                requestIdleCallback(doCollect, { timeout: 200 });
            } else {
                setTimeout(doCollect, 0);
            }
        });
    }

    function group(list) {
        const map = {};
        const texts = [];
        for (const unit of list) {
            const t = unit.text;
            if (!map[t]) {
                map[t] = [];
                texts.push(t);
            }
            map[t].push(unit);
        }
        return { map, texts };
    }
    function chunks(texts) {
        const out = [];
        let cur = [], len = 0, mode = '';
        for (const t of texts) {
            const nextMode = t.startsWith(HTML_PREFIX) ? 'html' : 'plain';
            if (cur.length && (nextMode !== mode || cur.length >= CHUNK_NODES || len + (nextMode === 'html' ? stripHtmlKey(t).length : t.length) > CHUNK_CHARS)) {
                out.push(cur);
                cur = [];
                len = 0;
                mode = '';
            }
            if (!cur.length) mode = nextMode;
            cur.push(t);
            len += nextMode === 'html' ? stripHtmlKey(t).length : t.length;
        }
        if (cur.length) out.push(cur);
        return out;
    }
    function traceId() { return Date.now() + '-' + Math.random().toString(16).slice(2); }
    function trans(arr, retry, cb) {
        const isHtml = arr.length && arr[0].startsWith(HTML_PREFIX);
        const url = isHtml ? API_HTML : API;
        const body = arr.map(t => ({ Text: isHtml ? stripHtmlKey(t) : t }));
        req({
            method: 'POST', url,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + token,
                'X-ClientTraceId': traceId()
            },
            data: JSON.stringify(body)
        }, r => {
            if ((r.status === 401 || r.status === 403) && retry) {
                token = ''; tokenTime = 0;
                getToken(err => { if (err) { cb(err); return; } trans(arr, false, cb); });
                return;
            }
            if (r.status < 200 || r.status >= 300) { cb('翻译请求失败'); return; }
            let data;
            try { data = JSON.parse(r.responseText); } catch (e) { cb('返回格式错误'); return; }
            if (!Array.isArray(data)) { cb('返回格式错误'); return; }
            for (let i = 0; i < arr.length; i++) {
                const dst = data[i]?.translations?.[0]?.text;
                if (dst) setCache(arr[i], dst);
            }
            cb('');
        }, e => { cb(e); });
    }
    function apply(g) {
        let n = 0;
        for (const src in g.map) {
            const dst = cache[src];
            if (!dst) continue;
            for (const unit of g.map[src]) {
                scheduleWrite(unit, dst);
                n++;
            }
        }
        return n;
    }
    function getToken(cb) {
        if (token && Date.now() - tokenTime < 8 * 60 * 1000) { cb(''); return; }
        req({ method: 'GET', url: AUTH }, r => {
            const t = String(r.responseText || '').trim();
            if (r.status < 200 || r.status >= 300 || !t) { cb('获取令牌失败'); return; }
            token = t;
            tokenTime = Date.now();
            cb('');
        }, cb);
    }

    async function startManual(callback) {
        if (running) { callback?.(false, '翻译进行中'); return; }
        running = true;
        translateInProgress = true;
        const { list, cacheHits } = await collect();
        if (!list.length) {
            running = false;
            translateInProgress = false;
            if (cacheHits > 0) {
                isTranslated = true;
                everTranslated = true;
                callback?.(true, cacheHits);
            } else {
                callback?.(false, '没有需要翻译的内容');
            }
            return;
        }
        const g = group(list);
        const cs = chunks(g.texts);
        let index = 0, active = 0, changed = cacheHits;
        const total = cs.length;
        let done = 0;
        updateBtnProgress(0, total);

        function doneTranslate() {
            const finalize = () => {
                running = false;
                translateInProgress = false;
                if (changed > cacheHits) {
                    isTranslated = true;
                    everTranslated = true;
                    callback?.(true, changed);
                } else {
                    callback?.(false, '翻译失败');
                }
            };
            if (rafId) requestAnimationFrame(finalize);
            else finalize();
        }

        getToken(err => {
            if (err) {
                running = false;
                translateInProgress = false;
                callback?.(false, err);
                return;
            }
            function next() {
                if (!document.body) {
                    running = false;
                    translateInProgress = false;
                    callback?.(false, '已停止');
                    return;
                }
                while (active < CONCURRENCY && index < cs.length) {
                    active++;
                    trans(cs[index++], true, e => {
                        active--;
                        done++;
                        if (e) {
                            if (index >= cs.length && active === 0) doneTranslate();
                            return;
                        }
                        changed += apply(g);
                        updateBtnProgress(done, total);
                        if (index >= cs.length && active === 0) doneTranslate();
                        else next();
                    });
                }
            }
            next();
        });
    }

    function restoreManual() {
        if (running || translateInProgress) return;
        renderOriginalFromCache();
        isTranslated = false;
        showRestore = false;
    }

    // ---------- 界面 ----------
    function updateBtnProgress(num, total) {
        const btn = document.getElementById('ms-manual-trans-btn');
        if (!btn) return;
        let pn = btn.querySelector('.ms-bp');
        if (!pn) {
            pn = document.createElement('span');
            pn.className = 'ms-bp';
            btn.appendChild(pn);
        }
        pn.textContent = num + '/' + total;
    }
    function clearBtnProgress() {
        const btn = document.getElementById('ms-manual-trans-btn');
        btn?.querySelector('.ms-bp')?.remove();
    }

    let selBtn = null, selResult = null;
    function createSelectionUI() {
        if (selBtn) return;
        selBtn = document.createElement('button');
        selBtn.id = 'ms-sel-trans-btn';
        selBtn.textContent = '译';
        Object.assign(selBtn.style, { position: 'fixed', display: 'none', zIndex: '999998', padding: '4px 10px', backgroundColor: '#4285f4', color: 'white', border: 'none', borderRadius: '4px' });
        document.body.appendChild(selBtn);
        selResult = document.createElement('div');
        selResult.id = 'ms-sel-trans-result';
        Object.assign(selResult.style, { position: 'fixed', display: 'none', zIndex: '999998', padding: '10px 14px', backgroundColor: 'white', color: '#333', border: '1px solid #999', maxWidth: '420px' });
        document.body.appendChild(selResult);
    }
    function getSelText() { const sel = window.getSelection(); return sel ? sel.toString().trim() : ''; }
    function showSelBtn(x, y) {
        if (!selBtn || !mod().selTranslate) return;
        const t = getSelText();
        if (!t || t.length < 2 || ZH_RE.test(t)) { hideSelBtn(); return; }
        selBtn.style.left = x + 'px';
        selBtn.style.top = y + 'px';
        selBtn.style.display = 'block';
    }
    function hideSelBtn() { if (selBtn) selBtn.style.display = 'none'; }
    function showSelResult(text, x, y) {
        if (!selResult) return;
        selResult.textContent = text || '';
        selResult.style.left = x + 'px';
        selResult.style.top = y + 'px';
        selResult.style.display = 'block';
    }
    function hideSelResult() { if (selResult) selResult.style.display = 'none'; }
    function translateSelection() {
        const text = getSelText();
        if (!text) return;
        showSelResult('翻译中…', parseInt(selResult.style.left) || 100, parseInt(selResult.style.top) || 100);
        if (cache[text]) { selResult.textContent = cache[text]; return; }
        trans([text], true, err => {
            selResult.textContent = err ? '翻译失败' : (cache[text] || text);
        });
    }


    function createButton() {
        if (document.getElementById('ms-manual-trans-btn')) return;
        // 容器 — 主按钮 + 禁止按钮作为一个整体
        var box = document.createElement('div');
        box.id = 'ms-btn-box';
        Object.assign(box.style, { position: 'fixed', top: '50%', transform: 'translateY(-50%)', zIndex: '999999', display: 'flex', flexDirection: 'column', gap: '1px', left: '0px', transition: 'left 0.3s ease' });
        const btn = document.createElement('button');
        btn.id = 'ms-manual-trans-btn';
        btn.textContent = '翻译';
        Object.assign(btn.style, { fontSize: 'medium', padding: '10px 18px', backgroundColor: '#4285f4', color: 'white', border: 'none', borderRadius: '0 6px 0 0', cursor: 'pointer', fontFamily: 'sans-serif' });
        var blockBtn = document.createElement('button');
        blockBtn.id = 'ms-block-btn';
        blockBtn.textContent = '禁止此站';
        Object.assign(blockBtn.style, { fontSize: '12px', padding: '4px 10px', backgroundColor: '#f5f5f5', color: '#c62828', border: '1px solid #e0e0e0', borderTop: 'none', borderRadius: '0 0 6px 0', cursor: 'pointer', fontFamily: 'sans-serif', whiteSpace: 'nowrap' });
        var hideLeft = '0px';
        btn._isHovered = false;

        btn.addEventListener('click', () => {
            if (translateInProgress) return;
            if (showRestore) {
                restoreManual();
                btn.textContent = '翻译';
                btn.style.backgroundColor = '#4285f4';
                syncPosition();
            } else if (translateCount >= 2) {
                renderTranslationFromCache();
                isTranslated = true;
                showRestore = true;
                btn.textContent = '恢复';
                btn.style.backgroundColor = '#db4437';
                syncPosition();
            } else {
                if (!everTranslated) updateBtnProgress(0, 0);
                btn.textContent = '翻译中';
                btn.style.backgroundColor = '#f0ad4e';
                syncPosition();
                startManual((success, result) => {
                    clearBtnProgress();
                    if (success) {
                        translateCount++;
                        showRestore = true;
                        btn.textContent = '恢复';
                        btn.style.backgroundColor = '#db4437';
                    } else {
                        btn.textContent = '翻译';
                        btn.style.backgroundColor = '#4285f4';
                        console.error('翻译失败:', result);
                        const tip = document.createElement('div');
                        tip.textContent = '翻译失败: ' + (result || '未知错误');
                        Object.assign(tip.style, { position: 'fixed', bottom: '90px', left: '20px', backgroundColor: 'rgba(0,0,0,0.7)', color: 'white', padding: '6px 12px', borderRadius: '6px', fontSize: '12px', zIndex: '999999' });
                        document.body.appendChild(tip);
                        setTimeout(() => tip.remove(), 2000);
                    }
                    syncPosition();
                });
            }
        });
        // 块按钮事件
        blockBtn.addEventListener('mouseenter', function() { blockBtn.style.backgroundColor = '#e0e0e0'; });
        blockBtn.addEventListener('mouseleave', function() { blockBtn.style.backgroundColor = '#f5f5f5'; });
        blockBtn.addEventListener('click', function(e) {
            e.stopPropagation();
            if (blockBtn._blocked) return;
            blockBtn._blocked = true;
            var host = location.hostname;
            var cur = (mod().blockedSites || '').split('\n').map(function(s) { return s.trim(); }).filter(Boolean);
            var wildcard = '*.' + host.split('.').slice(-2).join('.');
            if (cur.some(function(b) { return host === b || (b.startsWith('*.') && host.endsWith(b.slice(2))); })) {
                blockBtn.textContent = '✅ 已禁止'; setTimeout(function() { blockBtn.textContent = '禁止此站'; blockBtn._blocked = false; }, 1500); return;
            }
            cur.push(wildcard);
            settings.modules.translate.blockedSites = cur.join('\n');
            saveSettings();
            blockBtn.textContent = '✅ 已禁止';
            setTimeout(function() { blockBtn.textContent = '禁止此站'; blockBtn._blocked = false; }, 1500);
        });
        box.appendChild(btn);
        box.appendChild(blockBtn);
        document.body.appendChild(box);
        var hideLeft = '0px';
        function syncPosition() {
            if (showRestore || translateInProgress || (mod().btnMode === 'show' && everTranslated)) { box.style.left = '0px'; return; }
            box.style.left = btn._isHovered ? '0px' : hideLeft;
        }
        function updateHideLeft() {
            const showEdge = 10;
            hideLeft = -(box.offsetWidth - showEdge) + 'px';
            syncPosition();
        }
        requestAnimationFrame(function(){updateHideLeft();});
        if (typeof ResizeObserver !== 'undefined') new ResizeObserver(function(){updateHideLeft();}).observe(box);
        box.addEventListener('mouseenter', function(){btn._isHovered = true; syncPosition();});
        box.addEventListener('mouseleave', function(){btn._isHovered = false; syncPosition();});
    }

    function initSelectionEvents() {
        document.addEventListener('mouseup', function(e) {
            if (e.target.id === 'ms-manual-trans-btn' || e.target.id === 'ms-sel-trans-btn' || selResult?.contains(e.target)) return;
            setTimeout(() => {
                if (!mod().selTranslate) { hideSelBtn(); return; }
                const text = getSelText();
                if (!text || text.length < 2 || ZH_RE.test(text)) { hideSelBtn(); return; }
                const range = window.getSelection().getRangeAt(0);
                const rect = range.getBoundingClientRect();
                if (rect && (rect.width > 0 || rect.height > 0)) showSelBtn(rect.right + 4, rect.top - 30);
            }, 10);
        });
        document.addEventListener('mousedown', function(e) {
            if (e.target.id === 'ms-sel-trans-btn' || selResult?.contains(e.target)) return;
            hideSelBtn();
            hideSelResult();
        });
        selBtn?.addEventListener('click', function(e) {
            e.stopPropagation();
            const rect = selBtn.getBoundingClientRect();
            showSelResult('翻译中…', rect.left, rect.bottom + 4);
            translateSelection();
        });
    }

    function matchShortcut(combo, e) {
        if (!combo) return false;
        const parts = combo.split('+');
        const key = parts.pop();
        const mods = { Ctrl: false, Alt: false, Shift: false };
        for (const p of parts) mods[p] = true;
        if (mods.Ctrl !== e.ctrlKey || mods.Alt !== e.altKey || mods.Shift !== e.shiftKey) return false;
        if (/^[A-Z]$/.test(key)) return e.code === 'Key' + key;
        if (/^\d$/.test(key)) return e.code === 'Digit' + key;
        return e.code === key || e.key === key;
    }
    function initShortcuts() {
        document.addEventListener('keydown', function(e) {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return;
            if (matchShortcut(mod().pageShortcut, e)) {
                e.preventDefault();
                document.getElementById('ms-manual-trans-btn')?.click();
                return;
            }
            if (matchShortcut(mod().selShortcut, e)) {
                if (!mod().selTranslate) return;
                e.preventDefault();
                const text = getSelText();
                if (text && text.length >= 2 && !ZH_RE.test(text)) {
                    if (!selBtn) { createSelectionUI(); initSelectionEvents(); }
                    selResult.textContent = '翻译中…';
                    selResult.style.display = 'block';
                    const rect = window.getSelection().getRangeAt(0).getBoundingClientRect();
                    selResult.style.left = rect.left + 'px';
                    selResult.style.top = (rect.bottom + 4) + 'px';
                    translateSelection();
                }
                return;
            }
        });
    }

    function tryAutoTranslate() {
        if (!mod().autoTranslate) return;
        const doit = () => {
            setTimeout(() => {
                const btn = document.getElementById('ms-manual-trans-btn');
                if (btn && !isTranslated) btn.click();
            }, 1500);
        };
        if (document.readyState === 'complete') doit();
        else window.addEventListener('load', doit);
    }

    function init() {
        if (!settings.pageEnabled.translate) return;
        var blocked = (mod().blockedSites || '').split('\n').map(function(s) { return s.trim(); }).filter(Boolean);
        for (var i = 0; i < blocked.length; i++) { if (location.hostname === blocked[i] || (blocked[i].startsWith('*.') && location.hostname.endsWith(blocked[i].slice(1)))) return; }
        if (!document.getElementById('ms-bi-style')) { var s = document.createElement('style'); s.id = 'ms-bi-style'; s.textContent = '.ms-bi-gap{color:#aaa}.ms-bi-trans{background:#fffbe6;border-radius:2px;padding:0 2px;color:#d4380d;font-weight:500}'; document.head.appendChild(s); }
        getToken(function(){});
        createButton();
        if (mod().selTranslate) { createSelectionUI(); initSelectionEvents(); }
        initShortcuts();
        tryAutoTranslate();
    }
    function destroy() { var bx = document.getElementById('ms-btn-box'); if (bx) bx.remove(); if (selBtn) { selBtn.remove(); selResult.remove(); selBtn = null; selResult = null; } if (isTranslated) renderOriginalFromCache(); isTranslated = false; translateInProgress = false; everTranslated = false; }
    function buildTranslatePage() {
        var m = mod();
        var esc = function(v) { return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); };
        function dual(key, t, f, val) { return '<span class="wp-dual" data-key="' + key + '"><button type="button" class="wp-dual-btn' + (val ? ' active' : '') + '" data-value="true">' + t + '</button><button type="button" class="wp-dual-btn' + (val ? '' : ' active') + '" data-value="false">' + f + '</button></span>'; }
        return '\
            <div class="wp-section"><span class="wp-section-label">⚙️ 翻译</span><div class="wp-row"><label>划词翻译</label>' + dual('selTranslate','开启','关闭',m.selTranslate) + '</div><div class="wp-row"><label>自动翻译</label>' + dual('autoTranslate','开启','关闭',m.autoTranslate) + '</div><div class="wp-hint">自动翻译:页面加载完成后自动翻译全文。</div></div>\
            <div class="wp-section"><span class="wp-section-label">🔘 翻译按钮</span><div class="wp-row"><label>按钮模式</label><select class="wp-trans-sel" data-key="btnMode" style="padding:5px 10px;border:1px solid #90caf9;border-radius:4px;background:#90caf9;color:#000;font-size:12px;cursor:pointer"><option value="hide"' + (m.btnMode === 'hide' ? ' selected' : '') + '>原文下自动隐藏(默认)</option><option value="show"' + (m.btnMode === 'show' ? ' selected' : '') + '>翻译后总是显示</option></select></div><div class="wp-hint">左侧边缘悬停显示翻译按钮。</div></div>\
            <div class="wp-section"><span class="wp-section-label">⌨️ 快捷键</span><div class="wp-row"><label>页面翻译</label><input type="text" class="wp-key-input wp-trans-key" data-key="pageShortcut" value="' + esc(m.pageShortcut) + '" readonly placeholder="点击后按键"></div><div class="wp-row"><label>划词翻译</label><input type="text" class="wp-key-input wp-trans-key" data-key="selShortcut" value="' + esc(m.selShortcut) + '" readonly placeholder="点击后按键"></div><div class="wp-row"><label>双语对照</label><input type="text" class="wp-key-input wp-trans-key" data-key="bilingualKey" value="' + esc(m.bilingualKey) + '" readonly placeholder="点击后按键"></div></div>\
            <div class="wp-section"><div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px"><span class="wp-section-label" style="margin-bottom:0">🚫 禁止站点</span><button id="wp-trans-block-site" style="padding:4px 12px;border:none;border-radius:4px;background:#90caf9;color:#000;cursor:pointer;font-size:12px">移除当前网站</button></div><textarea id="wp-trans-blocked" style="width:100%;min-height:50px;resize:vertical;padding:5px 10px;border:1px solid #ccc;border-radius:4px;font-size:13px;box-sizing:border-box">' + esc(m.blockedSites || '') + '</textarea><div class="wp-hint">每行一个域名。禁止后翻译按钮不会出现在这些网站上。</div></div>';
    }
    function bindPanelEvents() {
        document.querySelectorAll('.wp-trans-sel').forEach(function(el) { el.addEventListener('change', function() { var v = this.value === 'true' ? true : (this.value === 'false' ? false : this.value); settings.modules.translate[this.dataset.key] = v; saveSettings(); }); });
        document.querySelectorAll('.wp-dual[data-key]').forEach(function(c) { c.querySelectorAll('.wp-dual-btn').forEach(function(b) { b.addEventListener('click', function() { var v = this.dataset.value === 'true'; settings.modules.translate[c.dataset.key] = v; saveSettings(); c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); }); }); }); });
        document.querySelectorAll('.wp-trans-key').forEach(function(inp) { inp.addEventListener('focus', function() { this.value = ''; this._cap = true; }); inp.addEventListener('blur', function() { this._cap = false; }); });
        var bt = document.getElementById('wp-trans-blocked'); if (bt) bt.addEventListener('input', function() { settings.modules.translate.blockedSites = this.value; saveSettings(); });
        var bb = document.getElementById('wp-trans-block-site'); if (bb && bt) bb.addEventListener('click', function() { var h = location.hostname; var cur = bt.value; var lines = cur.split('\n').map(function(s) { return s.trim(); }).filter(Boolean); var newLines = lines.filter(function(b) { return h !== b && !(b.startsWith('*.') && h.endsWith(b.slice(1))); }); if (newLines.length < lines.length) { bt.value = newLines.join('\n'); settings.modules.translate.blockedSites = bt.value; saveSettings(); } });
    }

    document.addEventListener('wp-module-toggle', function(e) { if (e.detail.page === 'translate') { if (e.detail.enabled) init(); else destroy(); } });

    // ─── 导出 ───
        return {
            init: init,
            destroy: destroy,
            buildPage: buildTranslatePage,
            bindPanelEvents: bindPanelEvents,
            toggleBilingual: function() { if (!isTranslated) return; if (currentMode === MODE_BI) renderTranslationFromCache(); else renderBilingual(); }
        };
    })();

    const BILI_MODULE = (function() {
        function mod() { return settings.modules.bilibili || {}; }

        // ─── IP 属地显示 ───
        function startIP() {
            if (mod().showIp === false) return;
            var code = '(' + function() {
                var origFetch = window.fetch;
                window.fetch = function() {
                    var url = (typeof arguments[0] === 'string') ? arguments[0] : (arguments[0] && arguments[0].url) || '';
                    if (url.indexOf('/x/v2/reply/wbi/main') === -1 && url.indexOf('/x/v2/reply/reply') === -1) return origFetch.apply(this, arguments);
                    return origFetch.apply(this, arguments).then(function(r) {
                        try {
                            var clone = r.clone();
                            return clone.json().then(function(json) {
                                if (!json || !json.data) return r;
                                function process(c) {
                                    if (c && c.reply_control && c.reply_control.location && c.member) {
                                        var loc = c.reply_control.location.replace(/IP属地:?/ig, '').trim();
                                        if (loc) c.member.uname += ' (' + loc + ')';
                                    }
                                    if (c && c.replies) for (var ri = 0; ri < c.replies.length; ri++) process(c.replies[ri]);
                                }
                                var lists = [json.data.top_replies, json.data.replies, json.data.root];
                                for (var li = 0; li < lists.length; li++) {
                                    if (Array.isArray(lists[li])) for (var ci = 0; ci < lists[li].length; ci++) process(lists[li][ci]);
                                }
                                return new Response(JSON.stringify(json), { status: r.status, statusText: r.statusText, headers: r.headers });
                            });
                        } catch(e) { return r; }
                    });
                };
            } + ')();';
            var s = document.createElement('script');
            s.textContent = code;
            (document.head || document.documentElement).appendChild(s);
            s.remove();
        }

        // ─── 干净链接 ───
        function startClean() {
            if (mod().cleanLinks === false) return;
            var P = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content','utm_id','fbclid','gclid','msclkid','twclid','_ga','_gl','ref','source','via','share','from','spm'];
            var S = {};
            // 合并外部来源规则
            var ext = mod().cleanSources || {};
            for (var url in ext) { var src = ext[url]; if (!src || !src.enabled || !src.data) continue;
                var providers = src.data.providers || src.data;
                for (var key in providers) { var rule = providers[key]; if (rule.rules) {
                    if (!S[key]) S[key] = [];
                    for (var ri = 0; ri < rule.rules.length; ri++) { var p = rule.rules[ri].replace(/^\(?:\?%3F\)?\?/,''); if (S[key].indexOf(p) === -1) S[key].push(p); }
                } }
            }
            function getParams(h) {
                var a = P.slice();
                for (var k in S) { var ds = k.split(','); for (var i = 0; i < ds.length; i++) { if (h.indexOf(ds[i]) !== -1) { a = a.concat(S[k]); break; } } }
                return a;
            }
            function clean(u) {
                try { var o = new URL(u, location.href); var ps = getParams(o.hostname); var c = false;
                    for (var i = 0; i < ps.length; i++) { if (o.searchParams.has(ps[i])) { o.searchParams.delete(ps[i]); c = true; } }
                    return c ? o.toString() : u;
                } catch(e) { return u; }
            }
            // 拦截 history.pushState 和 replaceState(B站通过它加 ?vd_source=)
            var origPush = history.pushState;
            history.pushState = function(s, t, u) { return origPush.call(this, s, t, u ? clean(String(u)) : u); };
            var origReplace = history.replaceState;
            history.replaceState = function(s, t, u) { return origReplace.call(this, s, t, u ? clean(String(u)) : u); };
            // 定期检查地址栏(B站可能通过 location.href 跳转)
            var lastUrl = location.href;
            setInterval(function() {
                try {
                    var cur = clean(location.href);
                    if (cur !== location.href) { history.replaceState(null, '', cur); lastUrl = cur; }
                } catch(e) {}
            }, 500);
            // 清理所有链接
            function cleanAll() {
                var links = document.querySelectorAll('a[href]');
                for (var i = 0; i < links.length; i++) { try { var h = links[i].getAttribute('href'); var ch = clean(h); if (ch !== h) links[i].setAttribute('href', ch); } catch(e) {} }
            }
            cleanAll();
            new MutationObserver(function() { cleanAll(); }).observe(document.body, { childList: true, subtree: true });
            document.addEventListener('click', function(e) {
                var t = e.target; while (t && t.tagName !== 'A') t = t.parentNode;
                if (!t) return; var h = t.getAttribute('href'); var ch = clean(h); if (ch !== h) t.setAttribute('href', ch);
            }, true);
        }

        function loadCleanSource(url, cb) {
            if (!url) { cb('请输入 URL'); return; }
            function extractName(url) {
                var source = '';
                if (url.indexOf('raw.githubusercontent.com') > -1) source = 'github - ';
                else if (url.indexOf('gitee.com') > -1) source = 'gitee - ';
                var m;
                m = url.match(/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/(.+)/);
                if (m) return source + m[1] + '/' + m[2] + '/' + m[3];
                m = url.match(/gitee\.com\/([^/]+)\/([^/]+)\/raw\/([^/]+)\/(.+)/);
                if (m) return source + m[1] + '/' + m[2] + '/' + m[4];
                return source + url.split('/').pop();
            }
            function onOk(t) {
                // 去掉开头的注释行(// 前缀的整行)
                var lines = t.split('\n');
                if (lines.length > 1 && lines[0].trim().indexOf('//') === 0) { lines.shift(); t = lines.join('\n'); }
                try { var data = JSON.parse(t); } catch(e) { cb('JSON 解析失败'); return; }
                var srcs = settings.modules.bilibili.cleanSources || {}; srcs[url] = { name: extractName(url), enabled: true, data: data }; settings.modules.bilibili.cleanSources = srcs; saveSettings(); cb(null, Object.keys(data.providers || data).length);
            }
            if (typeof GM_xmlhttpRequest === 'function') {
                GM_xmlhttpRequest({ method: 'GET', url: url + (url.indexOf('?') > -1 ? '&' : '?') + '_t=' + Date.now(), onload: function(r) { if (r.status >= 200 && r.status < 300) onOk(r.responseText); else cb('HTTP ' + r.status); }, onerror: function() { cb('网络错误(检查 @connect 授权)'); }, ontimeout: function() { cb('请求超时'); }, timeout: 12000 });
            } else if (typeof GM !== 'undefined' && GM.xmlHttpRequest) {
                GM.xmlHttpRequest({ method: 'GET', url: url + (url.indexOf('?') > -1 ? '&' : '?') + '_t=' + Date.now() }).then(function(r) { if (r.status >= 200 && r.status < 300) onOk(r.responseText); else cb('HTTP ' + r.status); }).catch(function() { cb('网络错误'); });
            } else {
                fetch(url + (url.indexOf('?') > -1 ? '&' : '?') + '_t=' + Date.now()).then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); }).then(onOk).catch(function(e) { cb('加载失败: ' + (e.message || '网络错误')); });
            }
        }

        function startDirectLink() {
            if (mod().directLink === false) return;
            var rules = [
                [/link\.zhihu\.com.*target=/, function() { var m = location.href.match(/target=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }],
                [/link\.csdn\.net.*target=/, function() { var m = location.href.match(/target=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }],
                [/jianshu\.com\/go-wild.*url=/, function() { var m = location.href.match(/url=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }],
                [/link\.juejin\.cn.*target=/, function() { var m = location.href.match(/target=([^&]+)/); if (m) location.href = decodeURIComponent(m[1]); }],
                [/mail\.qq\.com.*gourl=/, function() { var p = new URL(location.href).searchParams.get('gourl'); if (p) location.href = decodeURIComponent(p); }],
                [/\.google\.com\/url\?/, function() { var p = new URL(location.href).searchParams.get('url') || new URL(location.href).searchParams.get('q'); if (p) location.href = p; }],
            ];
            for (var i = 0; i < rules.length; i++) { if (rules[i][0].test(location.href)) { rules[i][1](); return; } }
        }

        var _hlObserver = null;
        function removeHighlight() {
            if (_hlObserver) { _hlObserver.disconnect(); _hlObserver = null; }
            document.querySelectorAll('.wp-hl').forEach(function(el) {
                var p = el.parentNode;
                if (p) {
                    var txt = document.createTextNode(el.textContent);
                    p.replaceChild(txt, el);
                    p.normalize();
                }
            });
            var st = document.getElementById('wp-hl-style');
            if (st) st.remove();
        }
        function startHighlight() {
            if (!mod().highlightEnabled) return;
            var kw = mod().highlightKeywords || '';
            var clr = mod().highlightColor || '#FF0000';
            if (!kw) return;
            var keywords = kw.split(',').map(function(s) { return s.trim(); }).filter(Boolean);
            if (!keywords.length) return;
            // 样式
            var st = document.getElementById('wp-hl-style');
            if (!st) { st = document.createElement('style'); st.id = 'wp-hl-style'; document.head.appendChild(st); }
            st.textContent = '.wp-hl{background:' + clr + '!important;border-radius:2px;padding:0 1px}';
            // 高亮单个文本节点
            function highlightNode(n) {
                if (!n || n.nodeType !== 3 || !n.parentNode) return;
                if (n.parentNode.closest('pre,code,kbd,samp,var,script,style,textarea,.wp-hl')) return;
                var text = n.nodeValue;
                for (var ki = 0; ki < keywords.length; ki++) {
                    try {
                        var kw = keywords[ki];
                        var re;
                        if (kw.startsWith('/') && kw.lastIndexOf('/') > 0) {
                            var lastSlash = kw.lastIndexOf('/');
                            var pattern = kw.substring(1, lastSlash);
                            var flags = kw.substring(lastSlash + 1) || 'gi';
                            re = new RegExp('(' + pattern + ')', flags);
                        } else {
                            re = new RegExp('(' + kw.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + ')', 'gi');
                        }
                        if (re.test(text)) {
                            re.lastIndex = 0;
                            text = text.replace(re, '<span class="wp-hl">$1</span>');
                            n.parentNode.replaceChild(function(){var s=document.createElement('span');s.innerHTML=text;return s;}(), n);
                            return;
                        }
                    } catch(e) {}
                }
            }
            function walk() {
                var w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
                var nodes = [];
                while (w.nextNode()) nodes.push(w.currentNode);
                for (var i = 0; i < nodes.length; i++) highlightNode(nodes[i]);
            }
            // 断开旧 observer
            if (_hlObserver) _hlObserver.disconnect();
            walk();
            _hlObserver = new MutationObserver(function() { walk(); });
            _hlObserver.observe(document.body, { childList: true, subtree: true });
        }
        var _preloadReady = false;
        function startPreload() {
            if (mod().preloadBoost === false) return;
            if (_preloadReady) return;
            _preloadReady = true;
            // 1. 图片预加载:将懒加载图片改为立即加载
            document.querySelectorAll('img[loading="lazy"]').forEach(function(img) { img.loading = 'eager'; });
            document.querySelectorAll('img').forEach(function(img) {
                var attrs = ['data-src','data-srcset','data-lazy-src','data-lazy-srcset','data-original','data-original-src','data-bg'];
                for (var ai = 0; ai < attrs.length; ai++) {
                    if (img.hasAttribute(attrs[ai])) {
                        var v = img.getAttribute(attrs[ai]);
                        if (v && (img.src.indexOf('data:image') === 0 || img.src !== v || !img.complete)) {
                            if (attrs[ai].indexOf('srcset') > -1) img.srcset = v; else img.src = v;
                        }
                        img.removeAttribute(attrs[ai]);
                        break;
                    }
                }
            });
            // MutationObserver 监控新图片
            new MutationObserver(function() {
                document.querySelectorAll('img[loading="lazy"]').forEach(function(img) { img.loading = 'eager'; });
                document.querySelectorAll('img').forEach(function(img) {
                    var attrs2 = ['data-src','data-srcset','data-lazy-src','data-original','data-bg'];
                    for (var ai2 = 0; ai2 < attrs2.length; ai2++) {
                        if (img.hasAttribute(attrs2[ai2])) { var v2 = img.getAttribute(attrs2[ai2]); if (v2) { if (attrs2[ai2].indexOf('srcset') > -1) img.srcset = v2; else img.src = v2; } img.removeAttribute(attrs2[ai2]); break; }
                    }
                });
            }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['loading','src','data-src'] });

            // 2. 悬停预读
            var delay = mod().hoverDelay || 100;
            var timer = null;
            document.addEventListener('mouseover', function(e) {
                var a = e.target.closest('a');
                if (!a || !a.href || a.href.indexOf('http') !== 0) return;
                if (/login|logout|register|signin|signup|pay|download|delete/.test(a.pathname)) return;
                if (timer) { clearTimeout(timer); timer = null; }
                timer = setTimeout(function() {
                    var link = document.querySelector('link[rel="prefetch"][href="' + a.href.replace(/"/g,'') + '"]');
                    if (!link) { var l = document.createElement('link'); l.rel = 'prefetch'; l.href = a.href; document.head.appendChild(l); }
                }, delay);
            }, { passive: true });
            document.addEventListener('mouseout', function() { if (timer) { clearTimeout(timer); timer = null; } }, { passive: true });
        }

        var colorStyleId = 'wp-color-mode';
        var colorModes = {
            '默认': '',
            '夜间模式': 'html{filter:invert(0.88)hue-rotate(180deg)!important}img,video,canvas,svg,iframe,[style*="background-image"]{filter:invert(1)hue-rotate(180deg)!important}',
            '护眼模式': 'html{filter:sepia(0.35)hue-rotate(340deg)!important}',
            '灰度模式': 'html{filter:grayscale(1)!important}',
            '高对比度': 'html{filter:contrast(1.5)brightness(1.1)!important}',
            '复古模式': 'html{filter:sepia(0.4)contrast(0.9)brightness(0.95)!important}',
        };
        function applyColorMode() {
            var mode = mod().colorMode || '默认';
            var existing = document.getElementById(colorStyleId);
            if (existing) existing.remove();
            if (mode === '默认') return;
            if (colorModes[mode]) {
                var s = document.createElement('style');
                s.id = colorStyleId;
                s.textContent = colorModes[mode];
                document.documentElement.appendChild(s);
            }
        }

        var _scrollbarStyleId = 'wp-sb-style';
        function applyScrollbar() {
            var style = mod().scrollbarStyle || 'default';
            var old = document.getElementById(_scrollbarStyleId);
            if (style === 'default') {
                if (old) old.remove();
                return;
            }
            var thumb = mod().scrollbarColor || '#FF0000';
            var css = '::-webkit-scrollbar{width:8px;height:8px}' +
                      '::-webkit-scrollbar-thumb{background:' + thumb + ';border-radius:4px}' +
                      '::-webkit-scrollbar-track{background:transparent}' +
                      'html{scrollbar-color:' + thumb + ' transparent;scrollbar-width:thin}';
            var st = old || document.createElement('style');
            st.id = _scrollbarStyleId;
            st.textContent = css;
            if (!old) document.head.appendChild(st);
        }

        function startScrollToTop() {
            if (!mod().backToTop) return;
            if (document.getElementById('wp-scroll-btns')) return;
            var div = document.createElement('div');
            div.id = 'wp-scroll-btns';
            div.innerHTML = '<button style="display:block;width:36px;height:36px;border:none;border-radius:6px;background:#90caf9;color:#000;font-size:16px;cursor:pointer;margin-bottom:4px;box-shadow:0 2px 6px rgba(0,0,0,0.15)">▲</button>' +
                            '<button style="display:block;width:36px;height:36px;border:none;border-radius:6px;background:#90caf9;color:#000;font-size:16px;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.15)">▼</button>';
            div.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:9999;display:flex;flex-direction:column;gap:4px';
            // 全屏时隐藏
            function fsHandler() { div.style.display = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement ? 'none' : 'flex'; }
            document.addEventListener('fullscreenchange', fsHandler);
            document.addEventListener('webkitfullscreenchange', fsHandler);
            document.addEventListener('mozfullscreenchange', fsHandler);
            var btns = div.children;
            btns[0].addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); });
            btns[1].addEventListener('click', function() { window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); });
            document.body.appendChild(div);
        }

        var _spacingNodes = null;
        function applySpacing() {
            // 清除之前的修改
            if (_spacingNodes) {
                for (var si = 0; si < _spacingNodes.length; si++) {
                    var sn = _spacingNodes[si];
                    if (sn.el && sn.el.parentNode && sn.orig !== undefined) sn.el.nodeValue = sn.orig;
                }
            }
            _spacingNodes = null;
            if (!mod().spacingEnabled) return;
            var saved = [];
            var w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
            var n, re1 = /([\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff])([^\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\s\.\,\;\:\!\?\(\)\[\]\{\}\u3000-\u303f\uff00-\uffef\u2000-\u206f])/g, re2 = /([^\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\s\.\,\;\:\!\?\(\)\[\]\{\}\u3000-\u303f\uff00-\uffef\u2000-\u206f])([\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff])/g;
            while ((n = w.nextNode())) {
                if (n.parentElement && n.parentElement.closest('pre,code,kbd,samp,var,script,style')) continue;
                var old = n.nodeValue;
                var nw = old.replace(re1, '$1 $2').replace(re2, '$1 $2');
                if (nw !== old) { n.nodeValue = nw; saved.push({ el: n, orig: old }); }
            }
            _spacingNodes = saved;
        }

        var _unlockStyle = null, _unlockApplied = false;
        function applyUnlock() {
            if (_unlockApplied) return;
            _unlockApplied = true;
            if (!mod().unlockEnabled) { _unlockApplied = false; return; }
            // 拦截事件(capture 阶段)
            ['contextmenu','copy','cut','dragstart'].forEach(function(ev) {
                document.addEventListener(ev, function(e) { e.stopPropagation(); e.preventDefault(); }, true);
            });
            // 覆盖 DOM0 事件
            document.oncontextmenu = null; document.onselectstart = null; document.oncopy = null;
            // 覆盖 CSS user-select
            if (!_unlockStyle) { _unlockStyle = document.createElement('style'); _unlockStyle.id = 'wp-unlock-style'; document.head.appendChild(_unlockStyle); }
            _unlockStyle.textContent = '* { user-select: auto !important; -webkit-user-select: auto !important; } body, body * { -webkit-touch-callout: default !important; }';
            // 移除内联 user-select 样式
            document.querySelectorAll('[style*="user-select"],[style*="user-select"]').forEach(function(el) {
                el.style.setProperty('user-select', 'auto', 'important');
                el.style.setProperty('-webkit-user-select', 'auto', 'important');
            });
        }

        function init() {
            applyColorMode();
            applyScrollbar();
            startDirectLink();
            startPreload();
            startHighlight();
            startScrollToTop();
            applySpacing();
            applyUnlock();
            var defaultUrls = ['https://gitee.com/qiuzongman/WebPlus/raw/master/ClearURLs/me.json','https://gitee.com/qiuzongman/WebPlus/raw/master/ClearURLs/clearURLs.json'];
            var srcs = mod().cleanSources || {};
            var needLoad = false;
            if (mod().cleanLinks !== false) {
                for (var ui = 0; ui < defaultUrls.length; ui++) { if (!srcs[defaultUrls[ui]]) { needLoad = true; loadCleanSource(defaultUrls[ui], function() { startClean(); }); } }
            }
            if (!needLoad) startClean();
            if (location.hostname.indexOf('bilibili.com') === -1) return;
            startIP();
        }

        function refreshCleanSourceList(list) {
            if (!list) return;
            var srcs = mod().cleanSources || {};
            var esc = function(v) { return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); };
            var html = '';
            for (var url in srcs) {
                var src = srcs[url];
                var count = src.data ? Object.keys(src.data.providers || src.data).length : 0;
                html += '<div style="display:flex;align-items:center;margin:2px 0;gap:4px"><label style="cursor:pointer;font-size:12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><input type="checkbox" class="wp-clean-source-enable" data-url="' + esc(url) + '"' + (src.enabled ? ' checked' : '') + '> ' + esc(src.name || url) + ' (' + count + ')</label> <button class="wp-clean-source-remove" data-url="' + esc(url) + '" style="background:none;border:none;color:#f44336;cursor:pointer;font-size:14px;padding:0 2px;flex-shrink:0">✕</button></div>';
            }
            if (!html) html = '<div style="font-size:12px;color:#888">暂无外部规则。建议加载 ClearURLs 规则。</div>';
            list.innerHTML = html;
            // 绑定事件
            list.querySelectorAll('.wp-clean-source-enable').forEach(function(cb) {
                cb.addEventListener('change', function() {
                    var srcs = settings.modules.bilibili.cleanSources || {};
                    if (srcs[this.dataset.url]) { srcs[this.dataset.url].enabled = this.checked; saveSettings(); }
                });
            });
            list.querySelectorAll('.wp-clean-source-remove').forEach(function(b) {
                b.addEventListener('click', function() {
                    var srcs = settings.modules.bilibili.cleanSources || {};
                    delete srcs[this.dataset.url];
                    settings.modules.bilibili.cleanSources = srcs; saveSettings();
                    refreshCleanSourceList(list);
                });
            });
        }


        function bindPanelEvents() {
            function bindDual(id, key) {
                var c = document.getElementById(id);
                if (!c) return;
                c.querySelectorAll('.wp-dual-btn').forEach(function(b) {
                    b.addEventListener('click', function() {
                        var v = this.dataset.value === 'true';
                        settings.modules.bilibili[key] = v; saveSettings();
                        c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); });
                    });
                });
            }
            bindDual('wp-bili-ip', 'showIp');
            bindDual('wp-direct-link', 'directLink');
            bindDual('wp-preload', 'preloadBoost');
            var hd = document.getElementById('wp-hover-delay');
            if (hd) hd.addEventListener('change', function() { settings.modules.bilibili.hoverDelay = parseInt(this.value) || 100; saveSettings(); });
            var cm = document.getElementById('wp-color-mode-select');
            if (cm) cm.addEventListener('change', function() { settings.modules.bilibili.colorMode = this.value; saveSettings(); applyColorMode(); });
            // 自定义高亮开关,需要即时生效
            (function() {
                var c = document.getElementById('wp-hl-switch');
                if (!c) return;
                c.querySelectorAll('.wp-dual-btn').forEach(function(b) {
                    b.addEventListener('click', function() {
                        var v = this.dataset.value === 'true';
                        settings.modules.bilibili.highlightEnabled = v; saveSettings();
                        c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); });
                        removeHighlight(); if (v) startHighlight();
                    });
                });
            })();
            var hlKeywords = document.getElementById('wp-hl-keywords');
            if (hlKeywords) hlKeywords.addEventListener('change', function() { 
                settings.modules.bilibili.highlightKeywords = this.value;
                saveSettings();
                removeHighlight(); startHighlight();
            });
            var hlColor = document.getElementById('wp-hl-color');
            if (hlColor) hlColor.addEventListener('change', function() {
                settings.modules.bilibili.highlightColor = this.value;
                saveSettings();
                removeHighlight(); startHighlight();
            });
            // 中英间距
            (function() {
                var c = document.getElementById('wp-spacing');
                if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) {
                    b.addEventListener('click', function() {
                        var v = this.dataset.value === 'true';
                        settings.modules.bilibili.spacingEnabled = v; saveSettings();
                        c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); });
                        applySpacing();
                    });
                });
            })();
            bindDual('wp-clean-links', 'cleanLinks');
            var sc = document.getElementById('wp-scrollbar-color');
            if (sc) sc.addEventListener('change', function() { settings.modules.bilibili.scrollbarColor = this.value; saveSettings(); applyScrollbar(); });
            // 滚动条滑块颜色
            (function() {
                var c = document.getElementById('wp-scrollbar-style');
                if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) {
                    b.addEventListener('click', function() {
                        var v = this.dataset.value;
                        settings.modules.bilibili.scrollbarStyle = v; saveSettings();
                        c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === v); });
                        var cp = document.getElementById('wp-scrollbar-color');
                        if (cp) cp.style.opacity = v === 'default' ? '0.4' : '1';
                        applyScrollbar();
                    });
                });
            })();
            // 顶底按钮
            (function() {
                var c = document.getElementById('wp-scrolltop-switch');
                if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) {
                    b.addEventListener('click', function() {
                        var v = this.dataset.value === 'true';
                        settings.modules.bilibili.backToTop = v; saveSettings();
                        c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); });
                        if (v) startScrollToTop(); else {
                            var el = document.getElementById('wp-scroll-btns');
                            if (el) el.remove();
                        }
                    });
                });
            })();
            // 解除文本限制
            (function() {
                var c = document.getElementById('wp-unlock');
                if (c) c.querySelectorAll('.wp-dual-btn').forEach(function(b) {
                    b.addEventListener('click', function() {
                        var v = this.dataset.value === 'true';
                        settings.modules.bilibili.unlockEnabled = v; saveSettings();
                        c.querySelectorAll('.wp-dual-btn').forEach(function(x) { x.classList.toggle('active', x.dataset.value === String(v)); });
                        if (v) applyUnlock(); else { _unlockApplied = false; var us = document.getElementById('wp-unlock-style'); if (us) us.remove(); }
                    });
                });
            })();
            // 加载外部清洗规则
            var cleanLoadBtn = document.getElementById('wp-clean-load');
            var cleanUrlInput = document.getElementById('wp-clean-source-url');
            var cleanList = document.getElementById('wp-clean-source-list');
            if (cleanLoadBtn && cleanUrlInput) {
                cleanLoadBtn.addEventListener('click', function() {
                    var url = cleanUrlInput.value.trim();
                    if (!url) return;
                    cleanLoadBtn.disabled = true;
                    cleanLoadBtn.textContent = '…';
                    loadCleanSource(url, function(err, count) {
                        cleanLoadBtn.disabled = false;
                        cleanLoadBtn.textContent = '加载';
                        if (err) { alert(err); return; }
                        refreshCleanSourceList(cleanList);
                    });
                });
            }
            if (cleanList) refreshCleanSourceList(cleanList);
        }

        document.addEventListener('wp-panel-open', function() { bindPanelEvents(); });
        return { init: init, destroy: function() {}, buildPage: function(){return'';}, bindPanelEvents: bindPanelEvents, loadCleanSource: loadCleanSource };
    })();

    // ===================================================================
    var _origBuild = buildPageContent;
    var _esc = function(v) { return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); };
    buildPageContent = function(pageId) {
        if (pageId === 'translate') return TRANS_MODULE.buildPage();
        if (pageId === 'other') {
            var m = settings.modules.bilibili || {};
            return '\
                <div class="wp-section">\
                    <span class="wp-section-label">📺 B站</span>\
                    <div class="wp-row"><label>显示IP</label><span class="wp-dual" id="wp-bili-ip"><button type="button" class="wp-dual-btn' + (m.showIp ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.showIp ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                    <div class="wp-hint">拦截评论 API 响应,在用户名后显示 IP 属地。</div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">🔓 解除文本限制</span>\
                    <div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-unlock"><button type="button" class="wp-dual-btn' + (m.unlockEnabled ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.unlockEnabled ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                    <div class="wp-hint">解除禁止复制、选择文本、右键菜单的限制。</div>\
                </div>';
        }
        if (pageId === 'visual') {
            var m = settings.modules.bilibili || {};
            return '\
                <div class="wp-section">\
                    <span class="wp-section-label">🔍 关键词高亮</span>\
                    <div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-hl-switch"><button type="button" class="wp-dual-btn' + (m.highlightEnabled ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.highlightEnabled ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                    <div class="wp-row"><label>关键词</label><input type="text" id="wp-hl-keywords" value="' + _esc(m.highlightKeywords || '') + '" placeholder="用逗号分隔,支持正则 /pattern/i" style="flex:1;padding:4px 8px;border:1px solid #90caf9;border-radius:4px;font-size:12px"></div>\
                    <div class="wp-row"><label>颜色</label><input type="color" id="wp-hl-color" value="' + (m.highlightColor || '#FF0000') + '" style="width:40px;height:28px;padding:1px;border:1px solid #90caf9;border-radius:4px;cursor:pointer;background:none"></div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">🔤 文本混排间隔</span>\
                    <div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-spacing"><button type="button" class="wp-dual-btn' + (m.spacingEnabled ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.spacingEnabled ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                    <div class="wp-hint">在中外文字/数字/符号之间自动添加空格,提升阅读体验。</div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">🎨 网页颜色</span>\
                    <div class="wp-row"><label>模式</label><select id="wp-color-mode-select" style="padding:5px 10px;border:1px solid #90caf9;border-radius:4px;background:#90caf9;color:#000;font-size:12px;cursor:pointer">\
<option ' + (m.colorMode === '默认' ? 'selected' : '') + '>默认</option>\
<option ' + (m.colorMode === '夜间模式' ? 'selected' : '') + '>夜间模式</option>\
<option ' + (m.colorMode === '护眼模式' ? 'selected' : '') + '>护眼模式</option>\
<option ' + (m.colorMode === '灰度模式' ? 'selected' : '') + '>灰度模式</option>\
<option ' + (m.colorMode === '高对比度' ? 'selected' : '') + '>高对比度</option>\
<option ' + (m.colorMode === '复古模式' ? 'selected' : '') + '>复古模式</option>\
</select></div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">🖱️ 滚动条</span>\
                    <div class="wp-row"><label>滑块颜色</label><span class="wp-dual" id="wp-scrollbar-style"><button type="button" class="wp-dual-btn' + (m.scrollbarStyle === 'default' ? ' active' : '') + '" data-value="default">默认</button><button type="button" class="wp-dual-btn' + (m.scrollbarStyle === 'custom' ? ' active' : '') + '" data-value="custom">自定义</button></span><input type="color" id="wp-scrollbar-color" value="' + (m.scrollbarColor || '#FF0000') + '" style="width:36px;height:26px;padding:1px;border:1px solid #90caf9;border-radius:4px;cursor:pointer;background:none' + (m.scrollbarStyle === 'default' ? ';opacity:0.4' : '') + '"></div>\
                    <div class="wp-row"><label>顶底按钮</label><span class="wp-dual" id="wp-scrolltop-switch"><button type="button" class="wp-dual-btn' + (m.backToTop ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.backToTop ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                </div>';
        }
        if (pageId === 'speed') {
            var m = settings.modules.bilibili || {};
            return '\
                <div class="wp-section">\
                    <span class="wp-section-label">⚡ 网页预加载</span>\
                    <div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-preload"><button type="button" class="wp-dual-btn' + (m.preloadBoost ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.preloadBoost ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                    <div class="wp-row"><label>悬停预读</label><input type="number" id="wp-hover-delay" value="' + (m.hoverDelay || 65) + '" min="50" max="2000" step="10" style="width:80px;padding:4px 8px;border:1px solid #90caf9;border-radius:4px;background:#90caf9;color:#000;font-size:12px;text-align:center"> <span style="font-size:12px;color:#888">ms</span></div>\
                    <div class="wp-hint">图片懒加载提前 + 鼠标悬停预读链接(点击即开)。</div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">🔗 链接直达</span>\
                    <div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-direct-link"><button type="button" class="wp-dual-btn' + (m.directLink ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.directLink ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                    <div class="wp-hint">绕过知乎等网站的中转页面,直达目标链接。</div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">🔗 链接净化</span>\
                    <div class="wp-row"><label>开关</label><span class="wp-dual" id="wp-clean-links"><button type="button" class="wp-dual-btn' + (m.cleanLinks ? ' active' : '') + '" data-value="true">开启</button><button type="button" class="wp-dual-btn' + (m.cleanLinks ? '' : ' active') + '" data-value="false">关闭</button></span></div>\
                    <div class="wp-hint">清除 URL 中的跟踪参数。</div>\
                    <div style="margin-top:6px;font-size:12px;color:#888">载入外部规则(从 Gitee/GitHub):</div>\
                    <div style="margin-top:4px">\
                        <input type="text" id="wp-clean-source-url" value="https://gitee.com/qiuzongman/WebPlus/raw/master/ClearURLs/me.json" placeholder="https://..." style="width:calc(100% - 60px);padding:4px 8px;border:1px solid #ccc;border-radius:4px;font-size:12px">\
                        <button id="wp-clean-load" style="padding:4px 12px;border:none;border-radius:4px;background:#90caf9;color:#000;cursor:pointer;font-size:12px;margin-left:4px">加载</button>\
                    </div>\
                    <div id="wp-clean-source-list" style="margin-top:4px;font-size:12px"></div>\
                </div>';
        }
        if (pageId === 'global') return buildGlobalPage();
        if (pageId === 'about') {
            return '\
                <div class="wp-section">\
                    <div style="margin-top:4px;line-height:1.3;font-size:14px">\
                        <p style="margin-bottom:8px"><strong>Web+ v1.0.1</strong></p>\
                        <div class="wp-row"><label style="min-width:80px">作者</label><span><a href="https://space.bilibili.com/423767625" target="_blank" style="color:#1565c0;text-decoration:none">邱宗满</a></span></div>\
                        <div class="wp-row"><label style="min-width:80px">邮箱</label><span>[email protected]</span></div>\
                        <div class="wp-row"><label style="min-width:80px">许可证</label><span>MIT</span></div>\
                        <div class="wp-row"><label style="min-width:80px">项目地址</label><span><a href="https://gitee.com/qiuzongman/WebPlus" target="_blank" style="color:#1565c0;text-decoration:none">Gitee</a></span></div>\
                        <div class="wp-row"><label style="min-width:80px">开发工具</label><span><a href="https://reasonix.io/" target="_blank" style="color:#1565c0;text-decoration:none">Reasonix</a> + <a href="https://www.deepseek.com/" target="_blank" style="color:#1565c0;text-decoration:none">Deepseek</a></span></div>\
                    </div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">📦 脚本推荐</span>\
                    <div style="margin-top:6px;line-height:2;font-size:13px">\
                        <div><a href="https://scriptcat.org/zh-CN/script-show-page/6583" target="_blank" style="color:#1565c0;text-decoration:none">视频控制器</a></div>\
                        <div><a href="https://greasyfork.org/zh-CN/scripts/419215-autopager" target="_blank" style="color:#1565c0;text-decoration:none">自动无缝翻页</a></div>\
                        <div><a href="https://greasyfork.org/zh-CN/scripts/24204-picviewer-ce" target="_blank" style="color:#1565c0;text-decoration:none">Picviewer CE+</a></div>\
                        <div><a href="https://scriptcat.org/zh-CN/script-show-page/1604" target="_blank" style="color:#1565c0;text-decoration:none">LinkSwift</a></div>\
                        <div><a href="https://greasyfork.org/zh-CN/scripts/473912-github%E6%90%9C%E7%B4%A2%E5%87%80%E5%8C%96" target="_blank" style="color:#1565c0;text-decoration:none">GitHub搜索净化</a></div>\
                        <div><a href="https://greasyfork.org/zh-CN/scripts/412245-github-enhancement-high-speed-download" target="_blank" style="color:#1565c0;text-decoration:none">GitHub高速下载</a></div>\
                    </div>\
                </div>\
                <div class="wp-section">\
                    <span class="wp-section-label">🫶 支援我买 Token 继续改进代码</span>\
                    <div style="margin-top:6px">\
                        <p style="font-size:13px;font-weight:bold;margin-bottom:4px">微信</p>\
                        <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NTAiIGhlaWdodD0iNDUwIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiIHZpZXdCb3g9IjAgMCA0NTAgNDUwIj4KICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTAgMGg3MHYxMEgwem04MCAwaDEwdjEwSDgwem0zMCAwaDQwdjEwaC00MHptNTAgMGgyMHYxMGgtMjB6bTQwIDBoMTB2MjBoLTEwem0zMCAwaDIwdjIwaC0yMHptMzAgMGgyMHYxMGgtMjB6bTQwIDBoNDB2MTBoLTQwem02MCAwaDEwdjEwaC0xMHptMjAgMGg3MHYxMGgtNzB6TTAgMTBoMTB2NjBIMHptNjAgMGgxMHY2MEg2MHptNDAgMGg0MHYxMGgtNDB6bTUwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2NDBoLTEwem05MCAwaDEwdjQwaC0xMHptMzAgMGgxMHY2MGgtMTB6bTYwIDBoMTB2NjBoLTEwek0yMCAyMGgzMHYzMEgyMHptODAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MjBoLTEwem01MCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem01MCAwaDIwdjEwaC0yMHptMTEwIDBoMzB2MzBoLTMwek05MCAzMGgxMHYzMEg5MHptMzAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjIwaC0xMHptNjAgMGgxMHYyMGgtMTB6bTQwIDBoMjB2MTBoLTIwem00MCAwaDMwdjEwaC0zMHptNTAgMGgxMHYyMGgtMTB6TTgwIDQwaDEwdjMwSDgwem02MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTQwIDBoMzB2MTBoLTMwem00MCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTQwIDBoMzB2MTBoLTMwek0xODAgNTBoMzB2MTBoLTMwem02MCAwaDEwdjgwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoNDB2MTBoLTQwek0xMCA2MGg1MHYxMEgxMHptOTAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2NTBoLTEwem00MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjMwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjYwaC0xMHptMzAgMGg1MHYxMGgtNTB6TTkwIDcwaDEwdjEwSDkwem00MCAwaDEwdjEwaC0xMHptNjAgMGgxMHY3MGgtMTB6bTYwIDBoMTB2NjBoLTEwem00MCAwaDEwdjEwaC0xMHptNjAgMGgxMHYyMGgtMTB6TTEwIDgwaDcwdjEwSDEwem0xMzAgMGgyMHYxMGgtMjB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDMwdjEwaC0zMHptNTAgMGgyMHYxMGgtMjB6bTEzMCAwaDIwdjEwaC0yMHptNTAgMGgxMHYyMGgtMTB6TTEwIDkwaDIwdjIwSDEwem00MCAwaDEwdjIwSDUwem0yMCAwaDEwdjEwSDcwem00MCAwaDEwdjcwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTYwIDBoMjB2MTBoLTIwem03MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MzBoLTEwem01MCAwaDEwdjEwaC0xMHptMjAgMGg0MHYxMGgtNDB6TTAgMTAwaDEwdjEwSDB6bTMwIDBoMTB2NzBIMzB6bTMwIDBoMTB2MTBINjB6bTIwIDBoMjB2MjBIODB6bTUwIDBoMjB2MTBoLTIwem0zMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2NDBoLTEwem00MCAwaDIwdjEwaC0yMHptMzAgMGgyMHYyMGgtMjB6bTUwIDBoMjB2MTBoLTIwem03MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTEwIDExMGgxMHYxMEgxMHptMzAgMGgxMHYzMEg0MHptNjAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjE5MGgtMTB6bTYwIDBoMTB2MzBoLTEwem05MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0wIDEyMGgxMHYzMEgwem0yMCAwaDEwdjIwSDIwem00MCAwaDIwdjEwSDYwem05MCAwaDEwdjE4MGgtMTB6bTYwIDBoMTB2MjBoLTEwem04MCAwaDEwdjEwaC0xMHptOTAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHpNNzAgMTMwaDEwdjEwSDcwem0yMCAwaDEwdjEwSDkwem03MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxOTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNjAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2NDBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2MTBoLTEwek0xMCAxNDBoMTB2MTBIMTB6bTUwIDBoMTB2MTBINjB6bTIwIDBoMTB2MTBIODB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptMTQwIDBoMTB2MTcwaC0xMHptMzAgMGgxMHYxOTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTkwIDBoMTB2MTBoLTEwek0yMCAxNTBoMTB2MjBIMjB6bTIwIDBoMjB2MTBINDB6bTEyMCAwaDEwdjE1MGgtMTB6bTMwIDBoNzB2MTUwaC03MHptODAgMGgyMHYxNTBoLTIwem02MCAwaDEwdjIwaC0xMHptMzAgMGgyMHYxMGgtMjB6bTMwIDBoMjB2MTBoLTIwem0zMCAwaDEwdjEwaC0xMHpNMCAxNjBoMjB2MTBIMHptNTAgMGgyMHYxMEg1MHptOTAgMGgxMHYxMGgtMTB6bTE4MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTMwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjIwaC0xMHptNDAgMGgxMHYxMGgtMTB6TTAgMTcwaDEwdjEwSDB6bTgwIDBoMTB2ODBIODB6bTMzMCAwaDIwdjEwaC0yMHpNMTAgMTgwaDIwdjIwSDEwem00MCAwaDIwdjEwSDUwem00MCAwaDEwdjEwSDkwem01MCAwaDEwdjEwaC0xMHptMTcwIDBoMTB2MzBoLTEwem00MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MTBoLTEwek0zMCAxOTBoMTB2MTBIMzB6bTIwIDBoMTB2MjBINTB6bTYwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHptMTcwIDBoMTB2NTBoLTEwem02MCAwaDEwdjYwaC0xMHptMzAgMGgyMHYyMGgtMjB6bTUwIDBoMTB2MjBoLTEwek0wIDIwMGgyMHYyMEgwem00MCAwaDEwdjUwSDQwem0yMCAwaDIwdjEwSDYwem02MCAwaDEwdjMwaC0xMHptMjAwIDBoMzB2MTBoLTMwem01MCAwaDIwdjEwaC0yMHpNMjAgMjEwaDEwdjIwSDIwem0xMTAgMGgxMHYxMGgtMTB6bTE5MCAwaDIwdjEwaC0yMHptODAgMGg0MHYxMGgtNDB6TTYwIDIyMGgxMHYxMEg2MHptMzAgMGgxMHY0MEg5MHptMjQwIDBoMzB2MTBoLTMwem01MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0wIDIzMGgxMHYzMEgwem0xMTAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem0xODAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem05MCAwaDEwdjEwaC0xMHpNMTAgMjQwaDIwdjEwSDEwem00MCAwaDMwdjEwSDUwem01MCAwaDEwdjIwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTE5MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTQwIDBoMzB2MTBoLTMwem02MCAwaDEwdjQwaC0xMHpNMjAgMjUwaDEwdjEwSDIwem0zMCAwaDEwdjMwSDUwem02MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTE2MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2NDBoLTEwem0yMCAwaDIwdjEwaC0yMHptNDAgMGgyMHYzMGgtMjB6bTMwIDBoMTB2MTBoLTEwek00MCAyNjBoMTB2MzBINDB6bTIwIDBoMTB2MTBINjB6bTIwIDBoMTB2MTBIODB6bTUwIDBoMTB2NDBoLTEwem0xODAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHpNMTAgMjcwaDMwdjEwSDEwem02MCAwaDEwdjEwSDcwem0yMCAwaDEwdjIwSDkwem0yMCAwaDIwdjEwaC0yMHptMjUwIDBoMjB2MjBoLTIwem04MCAwaDEwdjEwaC0xMHpNMTAgMjgwaDIwdjEwSDEwem01MCAwaDEwdjEwSDYwem00MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTIxMCAwaDEwdjEwaC0xMHptNjAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2MjBoLTEwek0yMCAyOTBoMTB2MTBIMjB6bTUwIDBoMjB2MTBINzB6bTcwIDBoMTB2MzBoLTEwem0xNzAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0xMCAzMDBoMTB2MTBIMTB6bTMwIDBoMzB2MTBINDB6bTQwIDBoMTB2MjBIODB6bTMwIDBoMjB2MTBoLTIwem0xMjAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2NDBoLTEwem0zMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTEwMCAwaDEwdjEwaC0xMHpNMCAzMTBoMTB2MzBIMHptMjAgMGgxMHYxMEgyMHptMjAgMGgyMHYxMEg0MHptNTAgMGgxMHYxMEg5MHptMzAgMGgyMHYxMGgtMjB6bTQwIDBoMjB2MjBoLTIwem0zMCAwaDIwdjEwaC0yMHptOTAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTQwIDBoMjB2MTBoLTIwek0xMCAzMjBoMTB2MjBIMTB6bTIwIDBoMTB2MTBIMzB6bTMwIDBoMjB2MTBINjB6bTE0MCAwaDIwdjMwaC0yMHptNjAgMGgxMHYzMGgtMTB6bTgwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNjAgMGgyMHYxMGgtMjB6TTQwIDMzMGgyMHYxMEg0MHptNDAgMGg0MHYxMEg4MHptNjAgMGgxMHY3MGgtMTB6bTMwIDBoMTB2MTBoLTEwem01MCAwaDIwdjEwaC0yMHptNjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTQwIDBoNDB2MTBoLTQwem01MCAwaDEwdjEwaC0xMHpNNDAgMzQwaDEwdjMwSDQwem0yMCAwaDEwdjEwSDYwem0zMCAwaDMwdjEwSDkwem0xNDAgMGgzMHYxMGgtMzB6bTgwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjQwaC0xMHptNzAgMGgzMHYxMGgtMzB6TTEwIDM1MGgzMHYxMEgxMHptODAgMGgxMHYxMEg5MHptMjAgMGgzMHYxMGgtMzB6bTUwIDBoMzB2MTBoLTMwem00MCAwaDEwdjEwMGgtMTB6bTIwIDBoMzB2MjBoLTMwem01MCAwaDQwdjEwaC00MHptNTAgMGgxMHYzMGgtMTB6bTQwIDBoMTB2NjBoLTEwem01MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6TTAgMzYwaDEwdjEwSDB6bTMwIDBoMTB2MTBIMzB6bTMwIDBoMTB2MTBINjB6bTIwIDBoMTB2ODBIODB6bTIwIDBoMTB2MzBoLTEwem0yMCAwaDIwdjIwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTIwIDBoMjB2MjBoLTIwem0zMCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDQwdjEwaC00MHptNTAgMGgyMHYxMGgtMjB6TTkwIDM3MGgxMHYyMEg5MHptMjAgMGgxMHY0MGgtMTB6bTEzMCAwaDEwdjcwaC0xMHptMjAgMGgxMHY1MGgtMTB6bTMwIDBoMTB2MTBoLTEwem0xMTAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2MTBoLTEwek0wIDM4MGg3MHYxMEgwem0xMzAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MzBoLTEwem02MCAwaDEwdjEwaC0xMHptODAgMGgyMHYyMGgtMjB6bTMwIDBoMTB2MzBoLTEwem01MCAwaDEwdjEwaC0xMHptMjAgMGgxMHY0MGgtMTB6bTIwIDBoMTB2MTBoLTEwek0wIDM5MGgxMHY2MEgwem02MCAwaDEwdjYwSDYwem02MCAwaDEwdjMwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2MzBoLTEwem02MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MjBoLTEwem02MCAwaDEwdjEwaC0xMHptODAgMGgyMHYxMGgtMjB6TTIwIDQwMGgzMHYzMEgyMHptNzAgMGgxMHY0MEg5MHptOTAgMGgxMHY0MGgtMTB6bTMwIDBoMzB2MTBoLTMwem05MCAwaDEwdjEwaC0xMHptNDAgMGgxMHYzMGgtMTB6bTMwIDBoMzB2MTBoLTMwem0tMjMwIDEwaDEwdjMwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTExMCAwaDEwdjEwaC0xMHptMzAgMGgyMHYxMGgtMjB6bTYwIDBoMTB2MjBoLTEwem00MCAwaDMwdjEwaC0zMHptLTMxMCAxMGgyMHYxMGgtMjB6bTUwIDBoMjB2MTBoLTIwem0xMjAgMGgxMHYyMGgtMTB6bTMwIDBoMjB2MTBoLTIwem02MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MTBoLTEwem0tMzEwIDEwaDIwdjEwaC0yMHptNDAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MjBoLTEwem00MCAwaDEwdjEwaC0xMHptODAgMGgxMHYxMGgtMTB6bTIwIDBoMjB2MjBoLTIwem00MCAwaDEwdjIwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwek0xMCA0NDBoNTB2MTBIMTB6bTkwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MTBoLTEwem01MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEweiIvPgo8L3N2Zz4K" style="width:240px;height:240px;display:block;margin-bottom:16px">\
                        <p style="font-size:13px;font-weight:bold;margin-bottom:4px">支付宝</p>\
                        <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MTAiIGhlaWdodD0iNDEwIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiIHZpZXdCb3g9IjAgMCA0MTAgNDEwIj4KICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTAgMGg3MHYxMEgwem05MCAwaDIwdjEwSDkwem00MCAwaDcwdjEwaC03MHptODAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem01MCAwaDIwdjEwaC0yMHptNjAgMGg3MHYxMGgtNzB6TTAgMTBoMTB2NjBIMHptNjAgMGgxMHY2MEg2MHptODAgMGgyMHYxMGgtMjB6bTMwIDBoMzB2MTBoLTMwem0xMzAgMGgzMHYxMGgtMzB6bTQwIDBoMTB2NjBoLTEwem02MCAwaDEwdjYwaC0xMHpNMjAgMjBoMzB2MzBIMjB6bTYwIDBoMjB2MjBIODB6bTQwIDBoMjB2MTBoLTIwem0zMCAwaDEwdjMwaC0xMHptMjAgMGgyMHYxMGgtMjB6bTMwIDBoMjB2MTBoLTIwem0zMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTQwIDBoMjB2MTBoLTIwem01MCAwaDMwdjMwaC0zMHpNMTEwIDMwaDEwdjIwaC0xMHptNzAgMGgzMHYxMGgtMzB6bTQwIDBoMjB2MTBoLTIwem04MCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTEwMCA0MGgxMHYzMGgtMTB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDMwdjEwaC0zMHptNjAgMGg0MHYxMGgtNDB6bTYwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHpNOTAgNTBoMTB2MTBIOTB6bTQwIDBoMTB2MTBoLTEwem0zMCAwaDIwdjEwaC0yMHptMzAgMGgzMHYxMGgtMzB6bTQwIDBoMzB2MTBoLTMwem01MCAwaDEwdjQwaC0xMHptMjAgMGgxMHYyMGgtMTB6TTEwIDYwaDUwdjEwSDEwem03MCAwaDEwdjEwSDgwem00MCAwaDEwdjMwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjQwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjQwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTYwIDBoMTB2MTBoLTEwem0zMCAwaDUwdjEwaC01MHpNMTEwIDcwaDEwdjIwaC0xMHptODAgMGgxMHY0MGgtMTB6bTIwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHpNMzAgODBoMjB2MTBIMzB6bTMwIDBoMjB2MTBINjB6bTMwIDBoMTB2NDBIOTB6bTYwIDBoMzB2MTBoLTMwem03MCAwaDEwdjEwaC0xMHptNDAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MjBoLTEwem0yMCAwaDIwdjEwaC0yMHptNjAgMGgyMHYxMGgtMjB6TTIwIDkwaDEwdjIwSDIwem0yMCAwaDEwdjEwSDQwem0xMDAgMGgyMHYzMGgtMjB6bTMwIDBoMTB2MTBoLTEwem0zMCAwaDEwdjIwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem00MCAwaDEwdjEwaC0xMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2MTBoLTEwek0xMCAxMDBoMTB2MjBIMTB6bTIwIDBoMTB2MjBIMzB6bTIwIDBoMjB2MTBINTB6bTMwIDBoMTB2NDBIODB6bTMwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEwem03MCAwaDEwdjEwaC0xMHptMzAgMGgxMHY4MGgtMTB6bTMwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHpNNDAgMTEwaDIwdjEwSDQwem04MCAwaDEwdjEwaC0xMHptNTAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2NTBoLTEwem00MCAwaDIwdjEwaC0yMHptNDAgMGgxMHYxMGgtMTB6bTIwIDBoMjB2MTBoLTIwem0zMCAwaDIwdjIwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2NTBoLTEwek02MCAxMjBoMjB2MTBINjB6bTQwIDBoMjB2MTBoLTIwem01MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTQwIDBoMjB2MTBoLTIwem00MCAwaDEwdjEwaC0xMHptNjAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEwek0yMCAxMzBoMzB2MTBIMjB6bTUwIDBoMTB2MzBINzB6bTIwIDBoMTB2MzBIOTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem0zMCAwaDEwdjEwaC0xMHptNzAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MTBoLTEwem00MCAwaDQwdjEwaC00MHpNMzAgMTQwaDIwdjEwSDMwem0zMCAwaDEwdjEwSDYwem02MCAwaDIwdjIwaC0yMHptNTAgMGgyMHYxMGgtMjB6bTcwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptNDAgMGgxMHYyMGgtMTB6bTQwIDBoMjB2MTBoLTIwek0wIDE1MGgxMHYxMEgwem0yMCAwaDEwdjIwSDIwem02MCAwaDEwdjIwSDgwem0yMCAwaDIwdjEwaC0yMHptNTAgMGgxMHYzMGgtMTB6bTIwIDBoMTB2OTBoLTEwem0yMCAwaDIwdjEwaC0yMHptMzAgMGgyMHYxMGgtMjB6bTQwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHY1MGgtMTB6bTUwIDBoNDB2MTBoLTQwek0xMCAxNjBoMTB2NDBIMTB6bTIwIDBoMTB2MzBIMzB6bTMwIDBoMTB2MTBINjB6bTUwIDBoMTB2MjBoLTEwem01MCAwaDEwdjEwaC0xMHptMzAgMGgxMHY4MGgtMTB6bTMwIDBoMTB2OTBoLTEwem0zMCAwaDEwdjYwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTEwMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTAgMTcwaDEwdjEwSDB6bTQwIDBoMjB2MjBINDB6bTgwIDBoMzB2MTBoLTMwem02MCAwaDEwdjExMGgtMTB6bTIwIDBoMjB2ODBoLTIwem0zMCAwaDIwdjMwaC0yMHptMzAgMGgxMHYzMGgtMTB6bTQwIDBoMTB2MTBoLTEwem02MCAwaDEwdjQwaC0xMHptMjAgMGgxMHYxMGgtMTB6TTIwIDE4MGgxMHYxMEgyMHptNDAgMGg1MHYxMEg2MHptMTAwIDBoMTB2MzBoLTEwem0xMjAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwek0wIDE5MGgxMHY2MEgwem00MCAwaDEwdjEwSDQwem03MCAwaDEwdjEwaC0xMHptMjAgMGgzMHYxMGgtMzB6bTE2MCAwaDIwdjEwaC0yMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2MjBoLTEwek0yMCAyMDBoMjB2MjBIMjB6bTMwIDBoMjB2MTBINTB6bTMwIDBoMTB2MjBIODB6bTIwIDBoMTB2MzBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYyMGgtMTB6bTgwIDBoMTB2NDBoLTEwem00MCAwaDEwdjcwaC0xMHptNzAgMGgxMHYxMGgtMTB6bTUwIDBoMjB2MTBoLTIwek0xMCAyMTBoMTB2MTBIMTB6bTMwIDBoMjB2MTBINDB6bTkwIDBoMjB2MjBoLTIwem0xMTAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MTBoLTEwem0yMCAwaDIwdjEwaC0yMHptNTAgMGgxMHYyMGgtMTB6bTIwIDBoMTB2MTBoLTEwek0zMCAyMjBoMTB2MTBIMzB6bTMwIDBoMTB2MTBINjB6bTMwIDBoMTB2NDBIOTB6bTcwIDBoMTB2MjBoLTEwem0xNDAgMGgxMHY3MGgtMTB6bTQwIDBoMTB2NDBoLTEwek03MCAyMzBoMTB2MTBINzB6bTUwIDBoMjB2MTBoLTIwem0xMjAgMGgzMHYxMGgtMzB6bTUwIDBoMTB2MTBoLTEwem0yMCAwaDMwdjEwaC0zMHptNjAgMGg0MHYxMGgtNDB6TTIwIDI0MGgyMHYxMEgyMHptMzAgMGgyMHYxMEg1MHptMzAgMGgxMHYxMEg4MHptNDAgMGgxMHYxMGgtMTB6bTMwIDBoMTB2MTBoLTEwem0xMzAgMGgxMHYyMGgtMTB6bTQwIDBoMTB2MzBoLTEwem0zMCAwaDEwdjIwaC0xMHptMjAgMGgyMHYxMGgtMjB6TTEwIDI1MGgxMHYxMEgxMHptMjAgMGgyMHYxMEgzMHptNzAgMGgyMHYxMGgtMjB6bTEzMCAwaDIwdjIwaC0yMHptNjAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MTBoLTEwem0yMCAwaDMwdjEwaC0zMHpNNTAgMjYwaDIwdjEwSDUwem05MCAwaDEwdjIwaC0xMHptODAgMGgxMHYyMGgtMTB6bTMwIDBoMjB2MTBoLTIwem02MCAwaDEwdjMwaC0xMHptNjAgMGgxMHYzMGgtMTB6bTIwIDBoMjB2MTBoLTIwek0wIDI3MGgzMHYxMEgwem00MCAwaDIwdjEwSDQwem02MCAwaDEwdjEwaC0xMHptOTAgMGgxMHY3MGgtMTB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem03MCAwaDIwdjEwaC0yMHptMzAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwek0wIDI4MGgxMHY1MEgwem00MCAwaDEwdjEwSDQwem0yMCAwaDIwdjEwSDYwem0zMCAwaDEwdjEwSDkwem0yMCAwaDMwdjEwaC0zMHptNDAgMGgzMHYyMGgtMzB6bTkwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptNzAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2NDBoLTEwek0zMCAyOTBoMTB2MTBIMzB6bTIwIDBoMTB2MTBINTB6bTIwIDBoMTB2NDBINzB6bTMwIDBoMjB2MTBoLTIwem00MCAwaDEwdjIwaC0xMHptNjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0zMCAwaDIwdjMwaC0yMHptOTAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjUwaC0xMHpNMTAgMzAwaDEwdjMwSDEwem01MCAwaDEwdjEwSDYwem0yMCAwaDIwdjEwSDgwem0zMCAwaDEwdjIwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjIwaC0xMHptNzAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2NTBoLTEwem0yMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTUwIDBoMTB2MzBoLTEwek00MCAzMTBoMjB2MTBINDB6bTQwIDBoMTB2MTBIODB6bTIwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MTBoLTEwem0zMCAwaDIwdjEwaC0yMHptNDAgMGgyMHYxMGgtMjB6bTUwIDBoMTB2ODBoLTEwem0yMCAwaDEwdjIwaC0xMHptMzAgMGgxMHY3MGgtMTB6TTQwIDMyMGgxMHYxMEg0MHptMjAgMGgxMHYxMEg2MHptMzAgMGgxMHYxMEg5MHptMTMwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bTIwIDBoMjB2MTBoLTIwem01MCAwaDEwdjEwaC0xMHptMzAgMGgxMHY1MGgtMTB6TTgwIDMzMGgxMHYyMEg4MHptNjAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MjBoLTEwem0zMCAwaDIwdjIwaC0yMHptMzAgMGgxMHY1MGgtMTB6bTQwIDBoMTB2MzBoLTEwem0zMCAwaDEwdjIwaC0xMHptOTAgMGgxMHYxMGgtMTB6TTAgMzQwaDcwdjEwSDB6bTEwMCAwaDQwdjEwaC00MHptNTAgMGgxMHYyMGgtMTB6bTMwIDBoMTB2MTBoLTEwem00MCAwaDEwdjEwaC0xMHptMzAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem02MCAwaDEwdjEwaC0xMHpNMCAzNTBoMTB2NjBIMHptNjAgMGgxMHY2MEg2MHptNTAgMGgyMHYxMGgtMjB6bTMwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjIwaC0xMHptNDAgMGgxMHYyMGgtMTB6bTkwIDBoMTB2NDBoLTEwem0xMDAgMGgyMHYxMGgtMjB6TTIwIDM2MGgzMHYzMEgyMHptNjAgMGgzMHYxMEg4MHptNTAgMGgxMHY0MGgtMTB6bTUwIDBoMjB2MTBoLTIwem00MCAwaDEwdjIwaC0xMHptMzAgMGgyMHYxMGgtMjB6bTUwIDBoMjB2MTBoLTIwem0zMCAwaDMwdjEwaC0zMHptNjAgMGgxMHYxMGgtMTB6TTgwIDM3MGgxMHYxMEg4MHptNzAgMGgxMHYxMGgtMTB6bTQwIDBoMTB2MTBoLTEwem01MCAwaDEwdjEwaC0xMHptMjAgMGgyMHYyMGgtMjB6bTkwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYzMGgtMTB6bS0zMDAgMTBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTMwIDBoMjB2MTBoLTIwem01MCAwaDEwdjEwaC0xMHptNjAgMGgxMHYxMGgtMTB6bTIwIDBoMTB2MjBoLTEwem0zMCAwaDEwdjMwaC0xMHptMzAgMGgxMHYxMGgtMTB6bS0yODAgMTBoMTB2MTBoLTEwem0zMCAwaDMwdjIwaC0zMHptNTAgMGgxMHYxMGgtMTB6bTcwIDBoMTB2MjBoLTEwem0yMCAwaDEwdjEwaC0xMHptMjAgMGgxMHYxMGgtMTB6bTgwIDBoMTB2MjBoLTEwek0xMCA0MDBoNTB2MTBIMTB6bTE2MCAwaDEwdjEwaC0xMHptMzAgMGgzMHYxMGgtMzB6bTUwIDBoMTB2MTBoLTEwem0yMCAwaDEwdjEwaC0xMHptNTAgMGgxMHYxMGgtMTB6bTUwIDBoMTB2MTBoLTEweiIvPgo8L3N2Zz4K" style="width:240px;height:240px;display:block">\
                    </div>\
                </div>';
        }
        return _origBuild(pageId);
    };

    function buildGlobalPage() {
        var esc = function(v) { return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); };
        return `
            <div class="wp-section">
                <span class="wp-section-label">⌨️ 快捷键</span>
                <div class="wp-row">
                    <label>进入设置</label>
                    <input type="text" class="wp-key-input wp-global-key" id="wp-openSettingsKey" value="${esc(settings.openSettingsKey)}" readonly placeholder="点击后按键">
                </div>
            </div>
            <div class="wp-section">
                <span class="wp-section-label">🌐 子页面开关</span>
                <div class="wp-row">
                    <label>网页翻译</label>
                    <span class="wp-dual" id="wp-global-translate">
                        <button type="button" class="wp-dual-btn${settings.pageEnabled.translate !== false ? ' active' : ''}" data-value="true">开启</button>
                        <button type="button" class="wp-dual-btn${settings.pageEnabled.translate === false ? ' active' : ''}" data-value="false">关闭</button>
                    </span>
                </div>
            </div>
            <div class="wp-section">
                <span class="wp-section-label">⚙️ 设置</span>
                <div style="margin-top:6px">
                    <button id="wp-update-rules" style="padding:8px 18px;border:none;border-radius:4px;background:#90caf9;color:#000;cursor:pointer;font-size:13px">🔄 更新所有外部链接</button>
                    <div id="wp-update-rules-status" class="wp-hint" style="margin-top:6px"></div>
                </div>
                <div style="margin-top:12px;padding-top:12px;border-top:1px solid var(--wp-section-bdr-clr, #d0d5dd)">
                    <button class="wp-btn wp-btn-reset" id="wp-reset-global" style="padding:8px 18px;border:none;border-radius:4px;background:#e53935;color:#fff;cursor:pointer;font-size:13px">⚠️ 所有设置恢复默认</button>
                    <div class="wp-hint" style="margin-top:4px">将清空所有模块的配置,恢复为初始状态。此操作不可撤销。</div>
                </div>
            </div>
        `;
    }

    function matchShortcut(combo, e) {
        if (!combo) return false;
        var parts = combo.split('+'), key = parts.pop(), mods = { Ctrl: false, Alt: false, Shift: false };
        parts.forEach(function(p) { mods[p] = true; });
        if (mods.Ctrl !== e.ctrlKey || mods.Alt !== e.altKey || mods.Shift !== e.shiftKey) return false;
        if (/^[A-Z]$/.test(key)) return e.code === 'Key' + key;
        if (/^\d$/.test(key)) return e.code === 'Digit' + key;
        return e.code === key || e.key === key;
    }

    // ===================================================================
    //  初始化
    // ===================================================================
    function init() {
        loadSettings();

        // 设置入口 — 始终注册
        if (typeof GM_registerMenuCommand === 'function') {
            GM_registerMenuCommand('设置', openSettings);
        }

        // 全局快捷键 — 打开设置面板
        document.addEventListener('keydown', function(e) {
            if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable)) return;
            // 用户自定义快捷键
            if (settings.openSettingsKey && matchShortcut(settings.openSettingsKey, e)) {
                e.preventDefault();
                openSettings();
            }
            // 备用快捷键 Ctrl+Shift+. 始终可用
            if (e.ctrlKey && e.shiftKey && e.key === '.') {
                e.preventDefault();
                openSettings();
            }
            // 双语对照快捷键
            var bk = settings.modules.translate && settings.modules.translate.bilingualKey;
            if (bk && matchShortcut(bk, e)) {
                e.preventDefault();
                if (typeof TRANS_MODULE !== 'undefined') TRANS_MODULE.toggleBilingual();
            }
        });

        // 初始化各模块
        TRANS_MODULE.init();
        BILI_MODULE.init();
    }

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