Greasy Fork

来自缓存

Greasy Fork is available in English.

AI语言学习专家

全DeepSeek驱动的英语学习专家。双击Alt查词(0.5s),Alt+1高亮,Alt+2开关侧边栏(自动吸附查词),Alt+3开关阅读模式。支持自定义跳转AI网站与生词本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        AI语言学习专家
// @namespace   http://tampermonkey.net/
// @version     1.4
// @license     MIT
// @description 全DeepSeek驱动的英语学习专家。双击Alt查词(0.5s),Alt+1高亮,Alt+2开关侧边栏(自动吸附查词),Alt+3开关阅读模式。支持自定义跳转AI网站与生词本。
// @author      杨俊贤 & Gemini & 豆包编程助手
// @match       *://*/*
// @run-at      document-end
// @grant       GM_addStyle
// @grant       GM_xmlhttpRequest
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_setClipboard
// @grant       GM_openInTab
// @require     https://cdn.jsdelivr.net/npm/@mozilla/[email protected]/Readability.min.js
// @connect     api.deepseek.com
// ==/UserScript==

(function() {
    'use strict';

// ==================== 0. DOM 构建工具 ====================
const UI = {
    el: (tag, attrs = {}, children = []) => {
        const element = document.createElement(tag);
        Object.entries(attrs).forEach(([key, val]) => {
            if (key === 'style' && typeof val === 'object') {
                Object.assign(element.style, val);
            } else if (key.startsWith('data-')) {
                element.setAttribute(key, val);
            } else if (key === 'className') {
                element.className = val;
            } else if (key === 'onclick' && typeof val === 'function') {
                element.onclick = val;
            } else if (key === 'onchange' && typeof val === 'function') {
                element.onchange = val;
            } else if (key === 'onmousedown' && typeof val === 'function') {
                element.onmousedown = val;
            } else if (key === 'onblur' && typeof val === 'function') {
                element.onblur = val;
            } else if (key === 'oninput' && typeof val === 'function') {
                element.oninput = val;
            } else if (key === 'checked') {
                element.checked = !!val;
            } else if (key === 'value') {
                element.value = val;
            } else if (key === 'type') {
                element.type = val;
            } else if (key === 'draggable') {
                element.draggable = val;
            } else {
                element[key] = val;
            }
        });
        const appendChild = (child) => {
            if (child === null || child === undefined) return;
            if (typeof child === 'string' || typeof child === 'number') {
                element.appendChild(document.createTextNode(child));
            } else if (child instanceof Node) {
                element.appendChild(child);
            } else if (Array.isArray(child)) {
                child.forEach(appendChild);
            }
        };
        if (Array.isArray(children)) children.forEach(appendChild);
        else appendChild(children);
        return element;
    },
    clear: (element) => {
        if (element) element.textContent = '';
    },
    renderMarkdown: (container, text, highlightWord = null) => {
        UI.clear(container);
        if (!text) return;
        const lines = text.split(/\n|\\n/);
        lines.forEach((line, index) => {
            if (index > 0) container.appendChild(document.createElement('br'));
            if (!line) return;
            const parts = line.split(/\*\*(.*?)\*\*/g);
            parts.forEach((part, i) => {
                if (!part) return;
                let node;
                const isBold = (i % 2 === 1);
                if (highlightWord) {
                    const regex = new RegExp(`(${highlightWord.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
                    const subParts = part.split(regex);
                    const frag = document.createDocumentFragment();
                    subParts.forEach((subPart, j) => {
                        if (!subPart) return;
                        if (j % 2 === 1) {
                            const span = UI.el('span', {
                                className: 'ds-target-italic',
                                style: { color: '#1E90FF', fontWeight: 'bold', fontStyle: 'italic' }
                            }, [subPart]);
                            frag.appendChild(span);
                        } else {
                            frag.appendChild(document.createTextNode(subPart));
                        }
                    });
                    if (isBold) {
                        node = document.createElement('strong');
                        node.appendChild(frag);
                    } else {
                        node = frag;
                    }
                } else {
                    if (isBold) {
                        node = document.createElement('strong');
                        node.textContent = part;
                    } else {
                        node = document.createTextNode(part);
                    }
                }
                if (node instanceof DocumentFragment) container.appendChild(node);
                else container.appendChild(node);
            });
        });
    },
    renderDict: (container, text) => {
        UI.clear(container);
        const lines = text.split('\n').filter(l => l.trim() !== '');
        if (lines.length === 0) return;
        const headword = lines[0].replace(/\*\*/g, '').trim();
        let ipa = "";
        let defStartIndex = 1;
        if (lines.length > 1 && (lines[1].trim().startsWith('/') || lines[1].trim().startsWith('['))) {
            ipa = lines[1].trim();
            defStartIndex = 2;
        }
        const headRow = UI.el('div', { className: 'ds-head-row' }, [
            UI.el('span', {
                className: 'ds-headword',
                title: '点击强制刷新 (重新联网获取)',
                style: { cursor: 'pointer', transition: 'color 0.2s' },
                onmouseover: (e) => e.target.style.setProperty('color', 'var(--ds-accent)', 'important'),
                onmouseout: (e) => e.target.style.setProperty('color', '#1E90FF', 'important')
            }, headword),
            ipa ? UI.el('span', { className: 'ds-clickable-ipa' }, ipa) : null
        ]);
        container.appendChild(headRow);
        const grid = UI.el('div', { className: 'ds-dict-grid' });
        let lastPos = "";
        for (let i = defStartIndex; i < lines.length; i++) {
            let lineText = lines[i].trim();
            if (/^(Exchange|Tags)/i.test(lineText)) continue;
            if (/^([a-z]+|[\u4e00-\u9fa5]+)\.$/i.test(lineText) && i + 1 < lines.length) {
                const nextLine = lines[i + 1].trim();
                if (!/^([a-z]+|[\u4e00-\u9fa5]+)\./i.test(nextLine) && !/^(Exchange|Tags)/i.test(nextLine)) {
                    lineText += " " + nextLine;
                    i++;
                }
            }
            const match = lineText.match(/^([a-z]+|[\u4e00-\u9fa5]+)\.\s*(.*)/i);
            let pos = "";
            let defText = lineText;
            if (match) {
                pos = match[1].toLowerCase();
                defText = match[2];
            }
            let displayPos = pos;
            if (pos && pos === lastPos) {
                displayPos = "";
            } else {
                if (pos) lastPos = pos;
            }
            grid.appendChild(UI.el('div', { className: 'ds-pos-label' }, displayPos));
            const segments = defText.split(/([;;])/);
            const defContent = UI.el('div', { className: 'ds-def-content' });
            segments.forEach(seg => {
                if (seg.match(/[;;]/)) {
                    defContent.appendChild(UI.el('span', { style: { marginRight: '4px', color: '#999' } }, seg));
                } else if (seg.trim()) {
                    defContent.appendChild(UI.el('span', {
                        className: 'ds-def-split',
                        'data-def': seg.trim(),
                        title: '点击为此义项生成例句'
                    }, seg.trim()));
                }
            });
            grid.appendChild(defContent);
        }
        container.appendChild(grid);
    }
};

// ==================== 1. 配置与状态管理 ====================
const DEFAULT_PROMPTS = [
    "同义词=请作为语言专家,列出与查询词【同语种】的至少5个同义词,不要废话。",
    "反义词=请作为语言专家,列出与查询词【同语种】的至少5个反义词,不要废话。",
    "同根词=请作为语言专家,列出与查询词【同语种】的至少5个同根词或派生词,不要废话。",
    "词源词根=先给我词根,再给我词源;关于词源内容一定要有趣,字数控制在50字到150字之间。"
];

const DEFAULT_SITES = [
    "DeepSeek=https://chat.deepseek.com/",
    "豆包=https://www.doubao.com/chat/",
    "元宝=https://yuanbao.tencent.com/chat",
    "Gemini=https://gemini.google.com/app",
    "ChatGPT=https://chatgpt.com/",
    "Grok=https://grok.com/"
];

const parseConfigLines = (list) => {
    let result = [];
    if (Array.isArray(list)) {
        list.forEach(item => {
            if (typeof item === 'string') {
                let parts = item.indexOf('=') > -1 ? item.split('=') : item.split('::');
                if (parts.length >= 2) {
                    result.push({name: parts[0].trim(), value: item.substring(item.indexOf('=') + 1).trim()});
                }
            } else if (typeof item === 'object') {
                result.push(item);
            }
        });
    }
    return result;
};

// Compatible parser for Prompts (uses 'template' instead of 'value')
const parsePrompts = (list) => {
    const parsed = parseConfigLines(list);
    return parsed.map(p => ({ name: p.name, template: p.template || p.value }));
};

const DS_CONFIG = {
    settings: {
        apiKey: GM_getValue('ds_api_key', ''),
        sidebarWidth: GM_getValue('sidebar_width', 450),
        sidebarSide: GM_getValue('ds_sidebar_side', 'right'),
        popupWidth: GM_getValue('ds_popup_width', '600px'),
        popupHeight: GM_getValue('ds_popup_height', '350px'),
        autoImport: true,
        autoCopy: GM_getValue('ds_auto_copy', false),
        jumpCopy: GM_getValue('ds_jump_copy', true),
        isDocked: GM_getValue('ds_is_docked', false),
        customPrompts: parsePrompts(GM_getValue('ds_custom_prompts', DEFAULT_PROMPTS)),
        customSites: parseConfigLines(GM_getValue('ds_custom_sites', DEFAULT_SITES)),
        disabledSites: GM_getValue('ds_disabled_sites', []),
        lastAiSite: GM_getValue('ds_last_ai_site', DEFAULT_SITES[0].split('=')[1]),
        historySort: GM_getValue('ds_history_sort', 'time_desc'),
        hiddenIcons: GM_getValue('ds_hidden_icons', [])
    },
    runtime: {
        activeTab: 'history',
        isPageTranslated: false,
        observer: null,
        observerTimeout: null,
        translationCache: {},
        exampleCache: {},
        popupCache: { dict: {}, context: {} },
        abortCtrl: null,
        rightPanelAbortCtrl: null,
        popupAbortCtrl: null,
        inlineAbortCtrl: null,
        currentAiContext: { messages: [], generatedText: "", element: null },
        lastSelection: { word: "", context: "" },
        isDraggingPopup: false,
        isResizingPopup: false,
        dragStartX: 0, dragStartY: 0,
        popupStartX: 0, popupStartY: 0,
        lastX: 0, lastY: 0,
        resizeDirection: '',
        resizeStartRect: {},
        currentPopupTrigger: null,
        sidebarLockUntil: 0,
        lastAltUpTime: 0,
        isRestoring: false,
        lastPopupParams: { left: null, right: null },
        isSwitchingContext: false,
        isReaderMode: false,
        readerFontSize: 20,
        readerLineHeight: 1.6,
        readerPageWidth: 800,
        preventContextMenuOnce: false,
        historyLimit: 50 // 新增:控制历史记录显示数量
    },
    consts: {
        API_URL: 'https://api.deepseek.com/v1/chat/completions',
        MODEL_NAME: 'deepseek-chat',
        HIGHLIGHT_CLASS: 'custom-web-highlight-tag',
        STORAGE_PREFIX: 'v3_pos_highlights_',
        GLOBAL_STORAGE_KEY: 'ds_global_history_v1',
        VOCAB_CACHE_KEY: 'v3_vocab_ds_cache',
        GLOBAL_DICT_CACHE_KEY: 'ds_dict_cache_global', // Cache for dictionaries
        STORAGE_KEY: 'v3_pos_highlights_' + btoa(encodeURIComponent(window.location.host + window.location.pathname)).substring(0, 50),
        PROMPTS: {
            DICT_CN: "你是一个专业的汉语词典接口。请严格按照词典格式输出,150字内。",
            DICT_EN: "你是一个专业的英语词典接口。请严格按照以下 ECDICT 数据结构输出信息,不要提供例句,150字内,禁止循环。\n\n格式要求:\n单词原型\n/音标/\n词性. 中文释义\n特殊的变体",
            CHAT: "你是一位专业的英语学习助手。",
            SUMMARY: "你是一位专业的文本分析师,需要对提供的文章内容进行结构化总结,要求:1. 分点呈现核心观点;2. 提炼文章关键信息、逻辑框架;3. 语言简洁专业,符合分析师报告风格;4. 忽略无关细节,聚焦文章主旨;5. 全部使用中文输出。"
        }
    }
};

const DOM = { sidebar: null, popup: null, highlightContent: null, readerWrapper: null };

// ==================== 2. 样式定义 ====================
function injectStyles() {
    const css = `:root{--ds-bg:#202328;--ds-text:#c0c4c9;--ds-msg-bg:#25282e;--ds-border:#3a3f47;--ds-user-bg:#c0c4c9;--ds-user-text:#1a1d21;--ds-header-bg:#2b3038;--ds-accent:#3a7bd5;--ds-highlight-bg:#8B0000;--ds-highlight-text:#ffffff;--ds-menu-bg:#202328;--ds-menu-active-bg:#353b45;--ds-tab-inactive-bg:#2a2f36;--ds-tab-active-bg:#4a5059;--ds-tab-inactive-text:#888;--ds-popup-bg:#202328;--ds-popup-border:#444;--ds-hover-bg:rgba(255,255,255,0.06);--ds-continue-color:#6db3f2;--ds-slider-off:#444;--ds-slider-on:#3a7bd5;--ds-modal-bg:rgba(32,35,40,0.98);--ds-scrollbar-thumb:#4a5059}

    .ds-scrollable::-webkit-scrollbar,#ds-chat-log::-webkit-scrollbar,#ds-highlight-log::-webkit-scrollbar,#ds-input::-webkit-scrollbar,.ds-cfg-textarea::-webkit-scrollbar,#ds-popup-left-content::-webkit-scrollbar,#ds-popup-right-content::-webkit-scrollbar,.ds-docked-scroll::-webkit-scrollbar{width:6px;height:6px}
    .ds-scrollable::-webkit-scrollbar-thumb,#ds-chat-log::-webkit-scrollbar-thumb,#ds-highlight-log::-webkit-scrollbar-thumb,#ds-input::-webkit-scrollbar-thumb,.ds-cfg-textarea::-webkit-scrollbar-thumb,#ds-popup-left-content::-webkit-scrollbar-thumb,#ds-popup-right-content::-webkit-scrollbar-thumb,.ds-docked-scroll::-webkit-scrollbar-thumb{background:var(--ds-scrollbar-thumb);border-radius:3px}
    .ds-scrollable::-webkit-scrollbar-track,#ds-chat-log::-webkit-scrollbar-track,#ds-highlight-log::-webkit-scrollbar-track,#ds-input::-webkit-scrollbar-track,.ds-cfg-textarea::-webkit-scrollbar-track,#ds-popup-left-content::-webkit-scrollbar-track,#ds-popup-right-content::-webkit-scrollbar-track,.ds-docked-scroll::-webkit-scrollbar-track{background:0 0}

    #ds-sidebar{position:fixed;top:0;width:${DS_CONFIG.settings.sidebarWidth}px;height:100vh;background:var(--ds-bg)!important;z-index:2147483647;transition:right .3s cubic-bezier(.4,0,.2,1),left .3s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:row;color:var(--ds-text)!important;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;box-sizing:border-box!important;padding:0!important;box-shadow:0 0 20px rgba(0,0,0,.4)}

    /* Toolbar Expansion */
    #ds-vertical-toolbar{width:36px;background:var(--ds-header-bg);border-left:1px solid var(--ds-border);border-right:1px solid var(--ds-border);display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:12px;z-index:10;flex-shrink:0;transition:width 0.2s ease;}
    #ds-vertical-toolbar.expanded { width: 120px; align-items: flex-start; }
    #ds-vertical-toolbar.expanded .ds-v-icon { justify-content: flex-start; padding-left: 12px; width: 100%; box-sizing: border-box; }
    .ds-v-icon{width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;cursor:pointer;color:var(--ds-text);opacity:0.7;font-size:16px;transition:all .2s;user-select:none}
    .ds-v-icon:hover{opacity:1;background:var(--ds-hover-bg);color:#fff}
    .ds-v-icon.active{color:var(--ds-accent);opacity:1}
    .ds-v-label { display: none; margin-left: 8px; font-size: 13px; white-space: nowrap; color: var(--ds-text); }
    #ds-vertical-toolbar.expanded .ds-v-label { display: inline-block; }

    /* 新增:侧边栏分区与编辑模式样式 */
    #ds-toolbar-active { display: flex; flex-direction: column; gap: 12px; width: 100%; align-items: center; padding-bottom: 10px; min-height: 50px; transition: all 0.2s; }

    #ds-toolbar-hidden {
        display: none; /* 默认隐藏 */
        flex-direction: column; gap: 12px; width: 100%; align-items: center;
        background: rgba(255,255,255,0.08); /* 稍微亮一点的背景 */
        border-top: 1px dashed #555;
        margin-top: auto; /* 关键:推到最底部 */
        flex-grow: 1; /* 占据剩余空间 */
        padding-top: 40px;
        padding-bottom: 15px;
        justify-content: flex-end;
        position: relative;
        box-sizing: border-box;
    }

    #ds-vertical-toolbar.ds-edit-mode #ds-toolbar-hidden { display: flex; animation: fadeIn 0.3s; }
    #ds-vertical-toolbar.ds-edit-mode { height: 100vh; overflow: hidden; } /* 确保容器占满高度 */

    .ds-hidden-text {
        position: absolute; top: 15px; left: 50%; transform: translateX(-50%);
        writing-mode: vertical-rl; text-orientation: mixed;
        font-size: 11px; color: #777; letter-spacing: 3px;
        pointer-events: none; white-space: nowrap; opacity: 0.8;
    }

    .ds-v-icon.dragging { opacity: 0.3; transform: scale(0.9); border: 1px dashed #fff; }

    /* 处于编辑模式时,活跃区即使为空也要有高度以便拖回 */
    #ds-vertical-toolbar.ds-edit-mode #ds-toolbar-active { min-height: 150px; padding-bottom: 20px; border-bottom: 1px solid transparent; }

    #ds-main-panel{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative}

    #ds-resizer{position:absolute;width:10px;height:100%;z-index:2147483648;background:transparent;cursor:ew-resize;transition:background .2s}#ds-resizer:hover{background:rgba(58,123,213,.1)}

    #ds-tab-header{height:42px;display:flex;align-items:center;justify-content:center;border-bottom:1px solid var(--ds-border);background:var(--ds-bg);padding:0 8px;flex-shrink:0}
    #ds-tabs-wrapper{display:flex;gap:4px;align-items:center;height:100%;z-index:1}
    /* Updated Tab Styling */
    .ds-tab{height:28px;cursor:pointer;font-size:15px;border-radius:6px;transition:all .2s;color:var(--ds-text);user-select:none;display:flex;align-items:center;justify-content:center;background:transparent!important;border:1px solid transparent!important;opacity:0.6;padding:0 8px;}
    .ds-tab:hover{color:#eee;background:var(--ds-hover-bg)!important;opacity:1}
    .ds-tab.active{background:var(--ds-tab-active-bg)!important;color:#fff!important;font-weight:700;border:1px solid #555!important;box-shadow:0 1px 2px rgba(0,0,0,.2);opacity:1}
    .ds-tab.dragging { opacity: 0.5; border: 1px dashed #666 !important; }

    .ds-tab-label { display: none; margin-left: 6px; font-size: 13px; }
    #ds-sidebar.expanded .ds-tab { width: auto; padding: 0 12px; }
    #ds-sidebar.expanded .ds-tab-label { display: inline; }

    #ds-tab-content{flex:1;overflow:hidden;display:flex;flex-direction:column;position:relative}.tab-panel{display:none;flex-direction:column;height:100%;width:100%;overflow:hidden}.tab-panel.active{display:flex}#ds-ai-content{flex:1}#ds-chat-log{flex:1;overflow-y:auto;padding:15px;display:flex;flex-direction:column;gap:15px;margin:0;scroll-behavior:smooth}.ds-msg{padding:12px 16px;border-radius:8px;font-size:14.5px;line-height:1.6;max-width:94%;word-wrap:break-word}.user-msg{align-self:flex-end;background:var(--ds-user-bg)!important;color:var(--ds-user-text)!important;border-top-right-radius:2px}.ai-msg{align-self:flex-start;background:var(--ds-msg-bg)!important;color:var(--ds-text)!important;border:1px solid var(--ds-border);border-top-left-radius:2px;white-space:pre-wrap}.ds-continue-text{display:block;margin-top:12px;color:var(--ds-accent);font-weight:700;cursor:pointer;text-decoration:none!important;transition:all .2s;font-size:14px;padding:4px 0;opacity:.9;letter-spacing:.5px}.ds-continue-text:hover{opacity:1;filter:brightness(1.3)}.ds-instruction-text{color:var(--ds-text);font-weight:700;font-size:13px;margin-bottom:5px}.ds-instruction-highlight{color:#FFD700!important;font-weight:700}.highlight-word{color:#1E90FF!important;font-weight:700!important;text-decoration:none!important;background:rgba(30,144,255,.1);padding:0 2px;border-radius:2px}#ds-fn-bar{padding:8px 10px;display:flex;gap:6px;flex-wrap:wrap;border-top:1px solid var(--ds-border);background:var(--ds-bg);flex-shrink:0;max-height:120px;overflow-y:auto}.fn-btn{flex:1;min-width:60px;padding:6px 8px;text-align:center;border-radius:4px;cursor:pointer;font-size:12px;color:var(--ds-text)!important;background:var(--ds-menu-active-bg);border:1px solid var(--ds-border);transition:all .2s;white-space:nowrap;display:flex;align-items:center;justify-content:center}.fn-btn:hover{background:var(--ds-hover-bg);border-color:#666}.fn-btn:active{transform:scale(.98)}.custom-prompt-btn{flex:0 1 auto!important}#ds-input-area{padding:10px 10px 15px;background:var(--ds-bg);flex-shrink:0;box-sizing:border-box!important;width:100%;border-top:1px solid var(--ds-border)}#ds-input-wrapper{display:flex;flex-direction:column;gap:8px;width:100%;box-sizing:border-box}#ds-input{width:100%;height:96px!important;border-radius:6px;border:1px solid var(--ds-border);padding:8px;outline:0;box-sizing:border-box;background:var(--ds-msg-bg)!important;color:rgba(255,255,255,0.08)!important;font-family:inherit;resize:none;font-size:14px;line-height:1.5;margin:0;overflow-y:auto;transition:color .2s ease,border-color .2s ease}#ds-input:focus{border-color:var(--ds-accent);color:var(--ds-text)!important}#ds-send-row{display:flex;justify-content:space-between;align-items:center;margin-top:4px}.ds-action-btn{width:80px;padding:6px 0;border:0;border-radius:12px;background:var(--ds-accent)!important;color:#fff!important;cursor:pointer;font-size:13px;font-weight:700;transition:opacity .2s ease,transform .1s;text-align:center}.ds-action-btn:hover{opacity:.9}.ds-action-btn:active{transform:scale(.96)}

    .ds-jump-wrapper{display:flex;gap:5px;align-items:center;position:relative;}
    .ds-jump-select-wrapper {position:relative;}
    .ds-jump-select-btn {background:var(--ds-msg-bg);color:var(--ds-text);border:1px solid var(--ds-border);border-radius:6px;padding:4px 8px;font-size:12px;min-width:85px;cursor:pointer;height:26px;display:flex;align-items:center;justify-content:space-between;}
    .ds-jump-select-btn:hover{border-color:#666}
    .ds-site-panel {
        position: absolute; bottom: 100%; left: 0; margin-bottom: 5px;
        background: var(--ds-msg-bg); border: 1px solid var(--ds-border);
        border-radius: 6px; padding: 8px; width: 220px;
        display: none; grid-template-columns: repeat(2, 1fr); gap: 6px;
        box-shadow: 0 4px 12px rgba(0,0,0,0.5); z-index: 100;
    }
    .ds-site-panel.active { display: grid; }
    .ds-site-item {
        padding: 6px; font-size: 12px; color: var(--ds-text);
        background: var(--ds-menu-active-bg); border: 1px solid transparent;
        border-radius: 4px; cursor: pointer; text-align: center;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .ds-site-item:hover { background: var(--ds-hover-bg); border-color: #666; }
    .ds-site-item.active { border-color: var(--ds-accent); color: var(--ds-accent); }

    #ds-config-panel,#ds-help-panel{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--ds-bg);z-index:1001;padding:20px;box-sizing:border-box;display:none;flex-direction:column;overflow-y:auto}.cfg-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;font-size:14px}#cfg-api-key{width:100%;margin-top:5px;padding:8px;border-radius:4px;border:1px solid var(--ds-border);background:var(--ds-msg-bg);color:var(--ds-text);font-size:13px}.ds-cfg-textarea{width:100%;height:120px;padding:8px;border-radius:4px;border:1px solid var(--ds-border);background:var(--ds-msg-bg);color:var(--ds-text);font-family:monospace;font-size:12px;resize:vertical;margin-top:5px;white-space:pre-wrap;overflow-x:hidden;word-wrap:break-word}.ds-panel-header{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--ds-border);padding-bottom:10px;margin-bottom:20px}.ds-panel-title{font-size:18px;font-weight:700;color:var(--ds-accent)}.ds-panel-top-btn{padding:4px 12px;background:var(--ds-accent);color:#fff;border-radius:4px;font-size:12px;cursor:pointer;border:none}.ds-panel-top-btn:hover{opacity:0.9}.ds-help-item{margin-bottom:15px;display:flex;flex-direction:column;gap:5px}.ds-help-key{font-weight:700;color:var(--ds-text);font-family:monospace;background:var(--ds-msg-bg);padding:2px 6px;border-radius:4px;display:inline-block;width:fit-content}.ds-help-desc{font-size:13px;color:var(--ds-text);opacity:.8;line-height:1.4;white-space:pre-wrap}.ds-primary-btn{width:100%;padding:8px;background:var(--ds-accent);color:#fff;border:0;border-radius:4px;cursor:pointer;font-size:14px;transition:opacity .2s;text-align:center}.ds-primary-btn:hover{opacity:.9}

    #ds-highlight-content{flex:1}#ds-highlight-log,#ds-history-log{flex:1;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:0;margin:0}.${DS_CONFIG.consts.HIGHLIGHT_CLASS}{background-color:var(--ds-highlight-bg)!important;color:var(--ds-highlight-text)!important;padding:0 2px!important;border-radius:2px;cursor:pointer;display:inline}.web-inline-trans{color:#1E90FF!important;font-size:14px!important;font-weight:400!important;display:block!important;margin-top:4px!important;padding:2px 0 6px!important;line-height:1.5!important;background:0 0!important;box-shadow:none!important;border:0!important}.web-inline-trans::before{content:""}.ds-inline-loading{animation:pulse 1.5s infinite}.ds-full-page-trans{color:#1E90FF!important;font-size:14px!important;font-weight:400!important;display:block!important;margin-top:4px!important;padding:2px 0 6px!important;line-height:1.5!important}.web-menu-item{display:flex!important;flex-direction:column!important;align-items:flex-start!important;padding:8px 12px!important;margin:0!important;background:var(--ds-menu-bg)!important;border-radius:0!important;cursor:default!important;transition:background-color .1s ease!important;border-bottom:1px solid rgba(255,255,255,.05)}.web-menu-item:hover{background:#353b45!important}.web-menu-header{display:flex;justify-content:space-between;width:100%;align-items:center;gap:8px;cursor:pointer}.web-menu-word{font-weight:700!important;color:#1E90FF!important;font-size:15px!important;}.web-menu-word:hover{text-decoration:none!important;color:var(--ds-accent)!important}.web-menu-jump{cursor:pointer;opacity:0.5;font-size:14px;padding:2px 6px}.web-menu-jump:hover{opacity:1;background:var(--ds-hover-bg);border-radius:4px}.web-menu-trans{display:none;margin-top:2px!important;color:#aaa!important;opacity:1;font-size:13px!important;line-height:1.4!important;white-space:pre-wrap!important;word-break:break-all!important;width:100%!important}#ds-confirm-modal{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);backdrop-filter:blur(2px);z-index:2000;display:none;align-items:center;justify-content:center;animation:fadeIn .2s ease}.ds-confirm-box{background:var(--ds-modal-bg);padding:25px 20px;border-radius:12px;width:75%;text-align:center;border:1px solid var(--ds-border);box-shadow:0 10px 30px rgba(0,0,0,.5);color:var(--ds-text)}.ds-confirm-text{font-size:15px;margin-bottom:20px;font-weight:500}.ds-confirm-btns{display:flex;gap:12px;justify-content:center}.ds-btn{padding:8px 20px;border-radius:6px;border:0;cursor:pointer;font-size:14px;font-weight:700;transition:transform .1s}.ds-btn:active{transform:scale(.95)}.ds-btn-yes{background:#ff3b30;color:#fff}.ds-btn-no{background:var(--ds-msg-bg);color:var(--ds-text);border:1px solid var(--ds-border)}@keyframes fadeIn{from{opacity:0}to{opacity:1}}#ds-popup{position:fixed;background:var(--ds-popup-bg);color:var(--ds-text);border:1px solid var(--ds-popup-border);border-radius:8px;overflow:hidden;box-shadow:0 10px 40px rgba(0,0,0,.6);z-index:2147483660;display:none;flex-direction:column;min-width:400px;min-height:250px;max-width:90vw;max-height:80vh}.ds-resize-handle{position:absolute;z-index:100;opacity:0}.ds-resize-handle:hover{background:rgba(30,144,255,.2);opacity:1}.ds-rh-n{top:0;left:10px;right:10px;height:5px;cursor:ns-resize}.ds-rh-s{bottom:0;left:10px;right:10px;height:5px;cursor:ns-resize}.ds-rh-w{left:0;top:10px;bottom:10px;width:5px;cursor:ew-resize}.ds-rh-e{right:0;top:10px;bottom:10px;width:5px;cursor:ew-resize}.ds-rh-nw{top:0;left:0;width:10px;height:10px;cursor:nwse-resize;z-index:101}.ds-rh-ne{top:0;right:0;width:10px;height:10px;cursor:nesw-resize;z-index:101}.ds-rh-sw{bottom:0;left:0;width:10px;height:10px;cursor:nesw-resize;z-index:101}.ds-rh-se{bottom:0;right:0;width:10px;height:10px;cursor:nwse-resize;z-index:101}#ds-popup-header-bar{height:36px;width:100%;cursor:move;flex-shrink:0;display:flex;align-items:center;justify-content:flex-end;padding-right:18px;gap:6px;background:var(--ds-header-bg);border-bottom:1px solid var(--ds-border)}.ds-popup-icon{cursor:pointer;font-size:15px;opacity:.6;transition:opacity .2s;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:4px;color:var(--ds-text)}.ds-popup-icon:hover{opacity:1;background:var(--ds-hover-bg)}#ds-popup-close-float{font-size:16px}#ds-popup-body{display:flex;flex:1;overflow:hidden;position:relative;padding:0;width:100%;height:100%;cursor:default}.ds-split-view{width:100%;height:100%;display:flex}.ds-split-left{flex:1;border-right:1px solid var(--ds-border);padding:16px;overflow-y:auto;background:var(--ds-popup-bg)}.ds-split-right{flex:1;padding:16px;overflow-y:auto;background:var(--ds-popup-bg)}#ds-docked-panel{flex-direction:column;background:var(--ds-bg)}.ds-docked-toolbar{padding:8px;border-bottom:1px solid var(--ds-border);display:flex;justify-content:center;align-items:center;background:#2f343c}.ds-docked-title{font-size:13px;font-weight:700;color:#aaa}#ds-undock-btn{padding:4px 12px;border:1px solid var(--ds-border);background:var(--ds-menu-bg);color:var(--ds-text);border-radius:4px;font-size:12px;cursor:pointer}#ds-undock-btn:hover{background:var(--ds-hover-bg);border-color:#666}.ds-docked-content{flex:1;overflow-y:auto;display:flex;flex-direction:column}.ds-docked-section{padding:15px;border-bottom:1px solid var(--ds-border)}.ds-docked-scroll{overflow-y:auto;max-height:50%}.ds-popup-title{font-size:14px;font-weight:700;margin-bottom:10px;color:var(--ds-accent);opacity:.9;letter-spacing:.5px;display:flex;align-items:center;gap:6px}.ds-popup-text{font-size:14px;line-height:1.6;white-space:pre-wrap;color:#ccc}.ds-popup-loading{color:#888;font-style:italic;animation:pulse 1.5s infinite}@keyframes pulse{0%{opacity:.5}50%{opacity:1}100%{opacity:.5}}.ds-target-italic{color:#1E90FF!important;font-weight:700;font-style:italic}.ds-head-row{display:flex;align-items:baseline;gap:10px;margin-bottom:8px;flex-wrap:wrap}.ds-headword{color:#1E90FF!important;font-weight:700;font-size:15px!important;display:inline-block}.ds-dict-grid{display:grid;grid-template-columns:45px 1fr;gap:4px 0;align-items:baseline}.ds-pos-label{text-align:right;color:#98c379;font-style:italic;font-weight:700;font-size:12px;user-select:none;white-space:nowrap;overflow:visible;padding-right:8px}.ds-def-split{cursor:pointer;border-bottom:1px dashed transparent;transition:all .1s}.ds-def-split:hover{color:var(--ds-accent)}.ds-is-streaming .ds-def-split{pointer-events:none!important;cursor:wait}.ds-is-streaming{cursor:wait}#ds-input::placeholder{color:rgba(255,255,255,0.15)!important;opacity:1}#ds-tab-docked{padding:0 8px!important}.ds-dock-lock{cursor:default}.ds-dock-restore{cursor:pointer;opacity:.6;transition:opacity .2s,background-color .2s;border-radius:4px;padding:0 4px;width:20px;text-align:center}.ds-dock-restore:hover{opacity:1;background:var(--ds-hover-bg)}.ds-clickable-ipa{color:#98c379;font-family:'Lucida Sans Unicode','Arial Unicode MS',sans-serif}

    #ds-config-panel input, #ds-config-panel textarea {font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif!important;color:var(--ds-text)!important;background:var(--ds-msg-bg)!important;border:1px solid var(--ds-border)!important;font-size:13px!important}
    .web-menu-item.active .ds-history-date { display: none; }
    .ds-undo-mode { background: transparent !important; cursor: default !important; justify-content: center; align-items: center; display: flex; }
    .ds-undo-mode:hover { background: transparent !important; }
    `;
    const switchCss = `.ds-switch{position:relative;display:inline-block;width:36px;height:20px}.ds-switch input{opacity:0;width:0;height:0}.ds-slider-btn{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:var(--ds-slider-off);transition:.4s;border-radius:34px}.ds-slider-btn:before{position:absolute;content:"";height:14px;width:14px;left:3px;bottom:3px;background-color:#fff;transition:.4s;border-radius:50%}input:checked+.ds-slider-btn{background-color:var(--ds-slider-on)}input:checked+.ds-slider-btn:before{transform:translateX(16px)}`;
    const readerCss = `
    #custom-reader-overlay {
        position: fixed; top: 0; left: 0; width: 100%; height: 100%;
        background-color: #0D262E !important; color: #939085 !important;
        z-index: 2147483649; overflow-y: auto;
        font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
        line-height: 1.6; scroll-behavior: smooth;
        -ms-overflow-style: none; scrollbar-width: none;
    }
    #custom-reader-overlay::-webkit-scrollbar { display: none; }
    /* Fix: Ensure highlighted words in reader mode stay red and white */
    #custom-reader-overlay mark, #custom-reader-overlay .custom-web-highlight-tag {
        background-color: var(--ds-highlight-bg) !important;
        color: var(--ds-highlight-text) !important;
    }
    /* Fix: Spacing between paragraphs */
    #reader-content-body p, #reader-content-body div, #reader-content-body li {
        margin-bottom: 24px !important;
    }

    #reader-ctrl-bar {
        position: fixed; top: 0 !important; margin: 0 !important;
        display: flex; flex-direction: column;
        background: #16323a;
        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
        z-index: 2147483651;
        opacity: 0.2;
        transition: opacity 0.3s;
        overflow: hidden;
        border: 1px solid rgba(147, 144, 133, 0.15);
        backface-visibility: hidden; transform: translateZ(0);
    }
    #reader-ctrl-bar:hover { opacity: 0.95; }
    .ctrl-btn {
        background: transparent; border: none;
        border-bottom: 1px solid rgba(147, 144, 133, 0.1);
        color: #939085;
        cursor: pointer; width: 38px; height: 38px;
        display: flex; align-items: center; justify-content: center;
        font-weight: 300; transition: all 0.2s;
        padding: 0; margin: 0;
        outline: none; box-sizing: border-box;
    }
    .ctrl-btn:last-child { border-bottom: none; }
    .ctrl-btn:hover { background: rgba(147, 144, 133, 0.15); color: #fff; }
    .btn-width { font-size: 14px; letter-spacing: -1px; }
    .btn-fs-plus { font-size: 18px; }
    .btn-fs-minus { font-size: 12px; }
    .exit-btn:hover { background: #EA4335 !important; color: white !important; }
    #reader-progress-v {
        position: fixed; top: 0; right: 0; width: 2px; height: 0%;
        background: #4285F4; opacity: 0.4; transition: height 0.1s; z-index: 2147483650;
    }
    #custom-reader-container { transition: width 0.3s, font-size 0.2s, line-height 0.2s; margin: 0 auto; }
    .reader-mode-active { overflow: hidden !important; }
    #reader-content-body img { max-width: 100%; height: auto; display: block; margin: 25px auto; border-radius: 4px; opacity: 0.8; }
    `;

    const fixCss = `
    #ds-highlight-content .ds-clickable-ipa { font-family: 'Lucida Sans Unicode', 'Arial Unicode MS', sans-serif; font-size: 13px; color: #98c379; }
    #ds-highlight-content .ds-head-row { margin-bottom: 6px; cursor: pointer; }
    #ds-highlight-content .ds-def-split { cursor: text !important; border-bottom: none !important; color: inherit !important; }
    #ds-highlight-content .ds-def-split:hover { color: inherit !important; background: transparent !important; }

    .web-menu-item { position: relative; transition: background-color 0.2s; }
    .web-menu-item.active { background: var(--ds-menu-active-bg); }
    .web-menu-item.active .web-menu-word { display: none !important; }
    .web-menu-item.active .web-menu-header { height: 0; padding: 0 !important; margin: 0 !important; border: none; display: block; overflow: visible; }
    .web-menu-item.active .web-menu-jump { position: absolute; right: 10px; top: 12px; z-index: 10; }
    .web-menu-item.active .web-menu-trans { margin-top: 0 !important; padding-top: 8px; display: block !important; }
    `;
    GM_addStyle(css + switchCss + readerCss + fixCss);
}

function updatePageLayout() {
    const sb = document.getElementById('ds-sidebar');
    const readerOverlay = document.getElementById('custom-reader-overlay');
    const width = parseInt(DS_CONFIG.settings.sidebarWidth);
    const side = DS_CONFIG.settings.sidebarSide;

    const resetReader = () => {
        if (readerOverlay) {
            readerOverlay.style.left = '0';
            readerOverlay.style.width = '100%';
            readerOverlay.style.transition = 'all 0.3s cubic-bezier(0.4,0,0.2,1)';
        }
    };

    // 如果侧边栏不可见,则重置所有
    if (!sb || !isSidebarVisible()) {
        document.body.style.marginLeft = '';
        document.body.style.marginRight = '';
        document.body.style.paddingLeft = '';
        document.body.style.paddingRight = '';
        document.body.style.width = '';
        document.body.style.minWidth = '';
        document.body.style.overflowX = '';
        document.body.style.boxSizing = '';
        document.body.style.transition = 'all 0.3s cubic-bezier(0.4,0,0.2,1)';

        resetReader();
        return;
    }

    // 默认开启挤压模式
    const gap = 0;
    const totalDistance = width + gap;

    document.body.style.transition = 'all 0.3s cubic-bezier(0.4,0,0.2,1)';
    document.body.style.boxSizing = 'border-box';

    document.body.style.marginLeft = '';
    document.body.style.marginRight = '';
    document.body.style.paddingLeft = '';
    document.body.style.paddingRight = '';
    document.body.style.width = '';
    document.body.style.minWidth = '';

    if (side === 'right') {
        document.body.style.width = `calc(100% - ${totalDistance}px)`;
        document.body.style.overflowX = 'auto';

        if (readerOverlay) {
            readerOverlay.style.transition = 'all 0.3s cubic-bezier(0.4,0,0.2,1)';
            readerOverlay.style.left = '0';
            readerOverlay.style.width = `calc(100% - ${totalDistance}px)`;
        }

    } else {
        document.body.style.paddingLeft = totalDistance + 'px';
        document.body.style.width = '100%';
        document.body.style.overflowX = 'hidden';

        if (readerOverlay) {
            readerOverlay.style.transition = 'all 0.3s cubic-bezier(0.4,0,0.2,1)';
            readerOverlay.style.left = totalDistance + 'px';
            readerOverlay.style.width = `calc(100% - ${totalDistance}px)`;
        }
    }
}

// Replaced toggleSidebarPushMode with toggleToolbarExpansion
function toggleToolbarExpansion() {
    const toolbar = document.getElementById('ds-vertical-toolbar');
    const sidebar = document.getElementById('ds-sidebar');
    if (toolbar) toolbar.classList.toggle('expanded');
    if (sidebar) sidebar.classList.toggle('expanded');
}

function updateSidebarPosition(animate = true) {
    const sb = document.getElementById('ds-sidebar');
    const resizer = document.getElementById('ds-resizer');
    const toggleBtn = document.getElementById('ds-side-toggle');
    const verticalToolbar = document.getElementById('ds-vertical-toolbar');
    const mainPanel = document.getElementById('ds-main-panel');

    // FIX: 之前使用 nextElementSibling 导致误选中了下一个图标(地球图标),并覆盖了其内容
    const label = toggleBtn ? toggleBtn.querySelector('.ds-v-label') : null;

    if (!sb || !resizer) return;
    if (!animate) { sb.style.transition = 'none'; } else { sb.style.transition = 'right 0.3s cubic-bezier(0.4,0,0.2,1), left 0.3s cubic-bezier(0.4,0,0.2,1)'; }

    sb.style.left = ''; sb.style.right = '';
    sb.style.borderLeft = ''; sb.style.borderRight = '';
    resizer.style.left = ''; resizer.style.right = '';

    // Helper to update text node without destroying span label
    const updateIconText = (btn, text) => {
        if (btn && btn.firstChild && btn.firstChild.nodeType === 3) {
            btn.firstChild.nodeValue = text;
        }
    };

    if (DS_CONFIG.settings.sidebarSide === 'right') {
        sb.style.right = isSidebarVisible() ? '0' : '-1200px';
        sb.style.borderLeft = '1px solid #3a3f47';
        resizer.style.left = '0';
        if (toggleBtn) { updateIconText(toggleBtn, '👈🏻'); toggleBtn.title = "切换至左侧"; }
        if (label) label.textContent = "切至左侧";

        if (verticalToolbar) { verticalToolbar.style.order = '2'; verticalToolbar.style.borderLeft = '1px solid var(--ds-border)'; verticalToolbar.style.borderRight = 'none'; }
        if (mainPanel) { mainPanel.style.order = '1'; }

    } else {
        sb.style.left = isSidebarVisible() ? '0' : '-1200px';
        sb.style.borderRight = '1px solid #3a3f47';
        resizer.style.right = '0';
        if (toggleBtn) { updateIconText(toggleBtn, '👉🏻'); toggleBtn.title = "切换至右侧"; }
        if (label) label.textContent = "切至右侧";

        if (verticalToolbar) { verticalToolbar.style.order = '0'; verticalToolbar.style.borderRight = '1px solid var(--ds-border)'; verticalToolbar.style.borderLeft = 'none'; }
        if (mainPanel) { mainPanel.style.order = '1'; }
    }
    updatePageLayout();
    if(DS_CONFIG.runtime.isReaderMode) updateReaderCtrlPosition();
}

function toggleSidebarSide() {
    const wasVisible = (DOM.sidebar.style.right === '0px' || DOM.sidebar.style.left === '0px');
    DS_CONFIG.settings.sidebarSide = DS_CONFIG.settings.sidebarSide === 'right' ? 'left' : 'right';
    GM_setValue('ds_sidebar_side', DS_CONFIG.settings.sidebarSide);
    updateSidebarPosition(true);
    if (wasVisible) showSidebar();
}

function setSidebarSide(side) {
    DS_CONFIG.settings.sidebarSide = side;
    GM_setValue('ds_sidebar_side', side);
    updateSidebarPosition(true);
}

// ==================== 3. 工具函数 ====================
function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
const isChinese = (text) => /[\u4e00-\u9fa5]/.test(text);

function getArticleContent() {
    if (location.hostname.includes('youtube.com')) {
        const desc = document.querySelector('#description-inline-expander .ytd-text-inline-expander') || document.querySelector('#description');
        if(desc) return desc.innerText.substring(0, 10000);
    }
    let articleEl = document.querySelector('article, main, #content, .content, .article-content, .post-content');
    const targetEl = articleEl || document.body;
    const validTags = ['h1','h2','h3','h4','p','blockquote','li','div'];
    const exclude = ['nav','header','footer','aside','.nav','.header','.footer','.ad','.advert','.banner','.sidebar','.comment','.menu'];
    let text = '';
    validTags.forEach(tag => {
        targetEl.querySelectorAll(tag).forEach(el => {
            if (exclude.some(es => el.closest(es))) return;
            const t = el.textContent.trim();
            if (t.length > 20) text += `${t}\n\n`;
        });
    });
    return text.substring(0,12000).trim() || '未识别到有效文章内容,请手动输入需要总结的文本。';
}

function togglePageTranslation() {
    if (DS_CONFIG.runtime.isPageTranslated) {
        if (DS_CONFIG.runtime.observer) {
            DS_CONFIG.runtime.observer.disconnect();
            DS_CONFIG.runtime.observer = null;
        }
        document.querySelectorAll('.ds-full-page-trans').forEach(el => el.remove());
        document.querySelectorAll('[data-ds-translated]').forEach(el => el.removeAttribute('data-ds-translated'));
        DS_CONFIG.runtime.isPageTranslated = false;
    } else {
        DS_CONFIG.runtime.isPageTranslated = true;
        translatePageContent();
        setupSPAObserver();
    }
}

function setupSPAObserver() {
    if (DS_CONFIG.runtime.observer) return;
    DS_CONFIG.runtime.observer = new MutationObserver((mutations) => {
        if (!DS_CONFIG.runtime.isPageTranslated) return;
        if (DS_CONFIG.runtime.observerTimeout) clearTimeout(DS_CONFIG.runtime.observerTimeout);
        DS_CONFIG.runtime.observerTimeout = setTimeout(() => {
            translatePageContent();
        }, 1000);
    });
    DS_CONFIG.runtime.observer.observe(document.body, { childList: true, subtree: true });
}

function translatePageContent() {
    if (DS_CONFIG.runtime.inlineAbortCtrl) {
    } else {
         DS_CONFIG.runtime.inlineAbortCtrl = new AbortController();
    }
    const signal = DS_CONFIG.runtime.inlineAbortCtrl.signal;

    let selectors = 'p, h1, h2, h3, h4, li, blockquote';
    if (location.hostname.includes('youtube.com')) {
        selectors += ', #content-text, #description-inline-expander, #video-title, .ytd-video-primary-info-renderer';
    }

    const targetEl = document.querySelector('article, main, #content, #columns') || document.body;
    const exclude = ['nav','header','footer','aside','.nav','.header','.footer','.ad','.banner','.sidebar','.menu', '#ds-sidebar', '#ds-popup'];

    targetEl.querySelectorAll(selectors).forEach(el => {
        if (el.dataset.dsTranslated === "true") return;
        if (el.closest('.ds-full-page-trans')) return;
        if (exclude.some(es => el.closest(es))) return;
        if (el.id === 'video-title' && el.closest('#dismissible')) { /* preview title */ }

        const text = el.innerText.trim();
        if (text.length > 0 && !isChinese(text)) {
            el.dataset.dsTranslated = "true";
            const transDiv = UI.el('div', { className: 'ds-full-page-trans ds-inline-loading' }, 'DeepSeek 思考中...');

            if (el.id === 'content-text') {
                  el.parentElement.appendChild(transDiv);
            } else if (el.tagName.startsWith('H')) {
                  el.appendChild(transDiv);
            } else {
                  el.appendChild(transDiv);
            }

            streamDeepSeekInline(text, transDiv, signal);
        }
    });
}

function clearAllInlineTranslations() {
    document.querySelectorAll('.web-inline-trans').forEach(el => el.remove());
    document.querySelectorAll('.web-trans-source-highlight').forEach(wrapper => {
        const parent = wrapper.parentNode;
        if (parent) {
            while (wrapper.firstChild) parent.insertBefore(wrapper.firstChild, wrapper);
            wrapper.remove();
        }
    });
    if (!DS_CONFIG.runtime.isPageTranslated) {
         document.querySelectorAll('.ds-full-page-trans').forEach(el => el.remove());
         document.querySelectorAll('[data-ds-translated]').forEach(el => el.removeAttribute('data-ds-translated'));
    }
}

function getPathTo(el) {
    if (!el || el === document.body) return 'BODY';
    if (el.id) return `id("${el.id}")`;
    let ix = 0, sibs = el.parentNode.childNodes;
    for (let i = 0; i < sibs.length; i++) {
        if (sibs[i] === el) return getPathTo(el.parentNode) + '/' + el.tagName + '[' + (ix + 1) + ']';
        if (sibs[i].nodeType === 1 && sibs[i].tagName === el.tagName) ix++;
    }
}

function getCurrentSentence() {
    let node, offset;
    if (document.caretRangeFromPoint) {
        const range = document.caretRangeFromPoint(DS_CONFIG.runtime.lastX, DS_CONFIG.runtime.lastY);
        if (!range) return null;
        node = range.startContainer; offset = range.startOffset;
    } else if (document.caretPositionFromPoint) {
        const pos = document.caretPositionFromPoint(DS_CONFIG.runtime.lastX, DS_CONFIG.runtime.lastY);
        if (!pos) return null;
        node = pos.offsetNode; offset = pos.offset;
    } else { return null; }
    if (node.nodeType !== 3) {
        if (node.childNodes.length === 1 && node.childNodes[0].nodeType === 3) { node = node.childNodes[0]; offset = 0; }
        else { return null; }
    }
    const text = node.textContent;
    let s = offset, e = offset;
    const re = /[\w\p{Unified_Ideograph}-]/u;
    if (e >= text.length) e = text.length - 1; if (s >= text.length) s = text.length - 1;
    if (!re.test(text[s])) { if (s > 0 && re.test(text[s - 1])) { s--; e--; } else return null; }
    while (s > 0 && re.test(text[s-1])) s--;
    while (e < text.length && re.test(text[e])) e++;
    const result = text.substring(s, e);
    return result.trim().length === 0 ? null : { text: result, node: node, s, e };
}

// ==================== 4. 核心 API 逻辑 ====================
async function requestAI({ messages, signal, onUpdate, onFinish, onError }) {
    if (!DS_CONFIG.settings.apiKey) {
        if (onError) onError(new Error("请配置 API Key"));
        return;
    }
    try {
        const res = await fetch(DS_CONFIG.consts.API_URL, {
            method: 'POST',
            headers: {'Content-Type':'application/json','Authorization':`Bearer ${DS_CONFIG.settings.apiKey}`},
            body: JSON.stringify({ model: DS_CONFIG.consts.MODEL_NAME, messages: messages, stream: true }),
            signal: signal
        });
        if (!res.ok) throw new Error(`HTTP Error: ${res.status}`);
        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let fullText = "";
        while (true) {
            const {done, value} = await reader.read();
            if (done) break;
            const chunk = decoder.decode(value);
            const lines = chunk.split('\n');
            for (const line of lines) {
                if (line.startsWith('data: ') && line !== 'data: [DONE]') {
                    try {
                        const data = JSON.parse(line.substring(6));
                        const delta = data.choices[0].delta.content || "";
                        fullText += delta;
                        if (onUpdate) onUpdate(delta, fullText);
                    } catch(e) {}
                }
            }
        }
        if (onFinish) onFinish(fullText);
    } catch (e) {
        if (onError) onError(e);
        else console.error("requestAI Error:", e);
    }
}

// Global Cache Helper
const manageGlobalCache = {
    get: (key) => {
        try {
            const cache = JSON.parse(GM_getValue(DS_CONFIG.consts.GLOBAL_DICT_CACHE_KEY, '{}'));
            return cache[key] ? cache[key].data : null;
        } catch(e) { return null; }
    },
    save: (key, data) => {
        try {
            let cache = JSON.parse(GM_getValue(DS_CONFIG.consts.GLOBAL_DICT_CACHE_KEY, '{}'));
            cache[key] = { data: data, time: Date.now() };

            const json = JSON.stringify(cache);
            // Limit to roughly 50MB
            if (json.length > 50 * 1024 * 1024) {
                 const entries = Object.entries(cache).sort((a,b) => a[1].time - b[1].time);
                 // Delete oldest 20%
                 const limit = Math.floor(entries.length * 0.2);
                 for(let i=0; i<limit; i++) {
                     delete cache[entries[i][0]];
                 }
            }
            GM_setValue(DS_CONFIG.consts.GLOBAL_DICT_CACHE_KEY, JSON.stringify(cache));
        } catch(e) {}
    }
};

function fetchVocabDefinition(word, container) {
    if (!DS_CONFIG.settings.apiKey) return;

    // Check Global Cache First
    const cachedData = manageGlobalCache.get(word);
    if (cachedData) {
        UI.renderDict(container, cachedData);
        // Sync to runtime cache
        DS_CONFIG.runtime.popupCache.dict[word] = cachedData;
        return;
    }

    if (DS_CONFIG.runtime.popupCache.dict[word]) {
        UI.renderDict(container, DS_CONFIG.runtime.popupCache.dict[word]);
        return;
    }

    UI.clear(container);
    container.appendChild(UI.el('span', { className: 'ds-popup-loading' }, 'DeepSeek 查询中...'));

    const prompt = isChinese(word) ? DS_CONFIG.consts.PROMPTS.DICT_CN : DS_CONFIG.consts.PROMPTS.DICT_EN;

    requestAI({
        messages: [{role:"system",content:prompt},{role:"user",content:word}],
        onUpdate: (delta, full) => {
            UI.renderDict(container, full);
        },
        onFinish: (full) => {
            DS_CONFIG.runtime.popupCache.dict[word] = full;
            manageGlobalCache.save(word, full); // Save to global
        },
        onError: (e) => {
            container.innerText = "查询失败: " + e.message;
        }
    });
}

async function streamDeepSeekInline(text, targetElement, signal = null) {
    if (DS_CONFIG.runtime.translationCache[text]) {
        targetElement.classList.remove('ds-inline-loading');
        targetElement.textContent = DS_CONFIG.runtime.translationCache[text];
        targetElement.style.color = "#1E90FF";
        return;
    }
    if (!DS_CONFIG.settings.apiKey) { targetElement.textContent = "请配置 API Key"; targetElement.classList.remove('ds-inline-loading'); return; }
    targetElement.textContent = "DeepSeek 思考中...";
    if (!targetElement.classList.contains('ds-inline-loading')) targetElement.classList.add('ds-inline-loading');
    let isFirstChunk = true;
    await requestAI({
        messages: [{role:"system", content:"你是一个翻译引擎。直接输出以下内容的中文翻译,不要任何解释或前缀。"},{role:"user", content: text}],
        signal: signal,
        onUpdate: (delta, fullText) => {
            if (isFirstChunk) { targetElement.textContent = ""; targetElement.classList.remove('ds-inline-loading'); isFirstChunk = false; }
            targetElement.textContent = fullText;
        },
        onFinish: (fullText) => {
            targetElement.classList.remove('ds-is-streaming');
            if (fullText) { DS_CONFIG.runtime.translationCache[text] = fullText; }
        },
        onError: (e) => {
            targetElement.classList.remove('ds-is-streaming');
            if(e.name !== 'AbortError') { targetElement.innerText = "Error: " + e.message; }
        }
    });
}

async function streamToElement(sysPrompt, userPrompt, targetElement, cacheCategory, cacheKey, highlightWord = null, mode = 'normal', signal = null) {
    // Check global cache for dict mode
    if (mode === 'dict') {
        const gCache = manageGlobalCache.get(highlightWord);
        if (gCache) {
            UI.renderDict(targetElement, gCache);
            DS_CONFIG.runtime.popupCache.dict[highlightWord] = gCache;
            return;
        }
    }

    if (cacheCategory && cacheKey && DS_CONFIG.runtime.popupCache[cacheCategory][cacheKey]) {
        if (mode === 'dict') UI.renderDict(targetElement, DS_CONFIG.runtime.popupCache[cacheCategory][cacheKey]);
        else UI.renderMarkdown(targetElement, DS_CONFIG.runtime.popupCache[cacheCategory][cacheKey], highlightWord);
        return;
    }
    if (!DS_CONFIG.settings.apiKey) { targetElement.innerText = "请配置 API Key"; return; }

    UI.clear(targetElement);
    targetElement.appendChild(UI.el('span', { className: 'ds-popup-loading' }, 'DeepSeek Thinking...'));
    targetElement.classList.add('ds-is-streaming');

    let isStreamFinished = false;

    await requestAI({
        messages: [{role:"system",content:sysPrompt},{role:"user",content:userPrompt}],
        signal: signal,
        onUpdate: (delta, fullText) => {
            if (mode === 'dict') {
                UI.renderDict(targetElement, fullText);
            } else {
                UI.renderMarkdown(targetElement, fullText, highlightWord);
            }
        },
        onFinish: (fullText) => {
            isStreamFinished = true;
            targetElement.classList.remove('ds-is-streaming');
            if (cacheCategory && cacheKey && fullText) { DS_CONFIG.runtime.popupCache[cacheCategory][cacheKey] = fullText; }
            if (mode === 'dict' && fullText) { manageGlobalCache.save(highlightWord, fullText); }
        },
        onError: (e) => {
            targetElement.classList.remove('ds-is-streaming');
            if(e.name !== 'AbortError') { targetElement.innerText = "Error: " + e.message; }
        }
    });
}

window.updateRightPanelExamples = function(defText, word) {
    if (DS_CONFIG.runtime.rightPanelAbortCtrl) { DS_CONFIG.runtime.rightPanelAbortCtrl.abort(); }

    const isDocked = DS_CONFIG.settings.isDocked;
    const rightContainerSelector = isDocked ? '#ds-docked-right-content' : '#ds-popup-right-content';
    const rightContainer = document.querySelector(rightContainerSelector);

    if (!rightContainer) return;

    const rightBody = rightContainer.querySelector('.ds-popup-text');
    const rightHeader = rightContainer.querySelector('.ds-popup-title');

    if (!rightBody) return;

    document.querySelectorAll('.ds-def-split').forEach(el => el.style.color = '');
    if (event && event.target && event.target.classList.contains('ds-def-split')) { event.target.style.color = '#3a7bd5'; }

    rightHeader.innerText = "📖 例句示范";
    const cacheKey = word + "_" + defText;
    if (DS_CONFIG.runtime.exampleCache[cacheKey]) { UI.renderMarkdown(rightBody, DS_CONFIG.runtime.exampleCache[cacheKey], word); return; }
    DS_CONFIG.runtime.rightPanelAbortCtrl = new AbortController();

    UI.clear(rightBody);
    rightBody.appendChild(UI.el('span', { className: 'ds-popup-loading' }, 'Generating 2 examples...'));

    let prompt = "";
    if (isChinese(word)) { prompt = `针对中文词汇 "${word}" 的特定含义:"${defText}",请生成 **2个** 包含该词的中文例句并附带英文翻译。要求:1. 必须提供2个不同场景的例句。2. 不要使用前缀标签。3. 中英文交替显示。`; }
    else { prompt = `针对单词 "${word}" 的特定含义:"${defText}",请生成 **2个** 地道的英文例句并附带中文翻译。要求:1. 必须提供2个不同场景的例句。2. **不要** 使用 "En:" 或 "Cn:" 等前缀。3. 第一行英文,第二行中文,依次排列。`; }
    prompt += `\n(Ref: ${Date.now()})`;
    requestAI({
        messages: [{role:"system",content:prompt},{role:"user",content:word}],
        signal: DS_CONFIG.runtime.rightPanelAbortCtrl.signal,
        onUpdate: (delta, fullText) => {
            UI.renderMarkdown(rightBody, fullText, word);
        },
        onFinish: (fullText) => { if (fullText) { DS_CONFIG.runtime.exampleCache[cacheKey] = fullText; } },
        onError: (e) => { if(e.name !== 'AbortError') rightBody.innerText = "Error: " + e.message; }
    });
};

function copyToClip(text) { if (!text) return; GM_setClipboard(text); }

async function askAI(query, targetWord = "", mode = "chat", customSystemPrompt = null) {
    if (!DS_CONFIG.settings.apiKey || DS_CONFIG.settings.apiKey.length < 10) {alert("请配置有效的 DeepSeek API Key");return;}
    if (!isSidebarVisible()) showSidebar();

    if (DS_CONFIG.runtime.activeTab !== 'docked') switchTab('docked');

    const rightContainer = document.querySelector('#ds-docked-right-content');
    const rightBody = rightContainer.querySelector('.ds-popup-text');
    const rightHeader = rightContainer.querySelector('.ds-popup-title');

    let title = "✨ 智能问答";
    if (mode === "summary") title = "🎯 全文概述";
    else if (mode === "explain") title = "🧠 文中解析";
    else if (mode === "custom") title = "✨ 智能问答";

    rightHeader.innerText = title;

    if (DS_CONFIG.runtime.popupAbortCtrl) { DS_CONFIG.runtime.popupAbortCtrl.abort(); }
    if (DS_CONFIG.runtime.abortCtrl) { DS_CONFIG.runtime.abortCtrl.abort(); }
    DS_CONFIG.runtime.abortCtrl = new AbortController();

    UI.clear(rightBody);
    rightBody.appendChild(UI.el('span', { className: 'ds-popup-loading' }, 'DeepSeek 思考中...'));

    let messages = [];
    let sysPrompt = DS_CONFIG.consts.PROMPTS.CHAT;
    if (mode==="summary") sysPrompt = DS_CONFIG.consts.PROMPTS.SUMMARY;
    else if (mode==="custom" && customSystemPrompt) sysPrompt = customSystemPrompt;
    messages = [{role:"system",content:sysPrompt},{role:"user",content:query}];

    DS_CONFIG.runtime.currentAiContext = { messages: messages, generatedText: "", element: rightBody };

    await requestAI({
        messages: messages, signal: DS_CONFIG.runtime.abortCtrl.signal,
        onUpdate: (delta, fullText) => {
            DS_CONFIG.runtime.currentAiContext.generatedText = fullText;
            UI.renderMarkdown(rightBody, fullText, targetWord);

            const scrollParent = rightContainer;
            if(scrollParent) scrollParent.scrollTop = scrollParent.scrollHeight;
        },
        onError: (e) => {
            if (e.name !== 'AbortError') { rightBody.appendChild(document.createTextNode("\n[请求失败: " + e.message + "]")); }
        }
    });
}
// Undo Helper
function softDelete(element, onConfirm) {
    if (!element) return;
    const header = element.querySelector('.web-menu-header');
    if (!header) return;

    // 临时禁用该行交互,防止误触查词,但允许点击取消按钮
    element.style.pointerEvents = 'none';

    // 隐藏跳转图标(如果存在)
    const jumpBtn = header.querySelector('.web-menu-jump');
    if (jumpBtn) jumpBtn.style.display = 'none';

    // 创建右侧取消按钮
    const undoBtn = UI.el('span', {
        style: { marginLeft: 'auto', marginRight: '4px', color: '#6db3f2', cursor: 'pointer', fontWeight: 'bold', fontSize: '12px', pointerEvents: 'auto', whiteSpace: 'nowrap' }}, '🔄 取消删除');

    header.appendChild(undoBtn);

    let isRestored = false;
    const timer = setTimeout(() => {
        if (!isRestored) {
            element.remove();
            if (onConfirm) onConfirm();
        }
    }, 3000);

    undoBtn.onclick = (e) => {
        e.stopPropagation();
        e.preventDefault();
        clearTimeout(timer);
        isRestored = true;

        // 恢复 UI 状态
        undoBtn.remove();
        if (jumpBtn) jumpBtn.style.display = '';
        element.style.pointerEvents = '';
    };
}

function saveHighlights() {
    DS_CONFIG.runtime.isRestoring = true; const h = [];
    document.querySelectorAll(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`).forEach(el => {
        const parent = el.parentElement; if (parent) {
            let rank = 0; const text = el.textContent; const regex = new RegExp(escapeRegExp(text), 'g');
            for (let i = 0; i < parent.childNodes.length; i++) {
                const child = parent.childNodes[i]; if (child === el) break;
                const childText = child.textContent; const matches = childText.match(regex); if (matches) rank += matches.length;
            }
            h.push({ path: getPathTo(parent), text: text, rank: rank });
        }
    });
    localStorage.setItem(DS_CONFIG.consts.STORAGE_KEY, JSON.stringify(h));
    if (DS_CONFIG.runtime.activeTab === 'history') renderHistoryMenu();
    setTimeout(() => { DS_CONFIG.runtime.isRestoring = false; }, 100);
}

function removeHighlight(el) {
    DS_CONFIG.runtime.isRestoring = true;

    const text = el.textContent.trim();
    if(text) deleteFromHistory(text);

    const p = el.parentNode;
    if (p) {
        while (el.firstChild) p.insertBefore(el.firstChild, p.contains(el) ? el : null);
        el.remove();
        saveHighlights();
    }
}

function deleteWord(word, elementRef = null) {
    const doDelete = () => {
        let removedFromDom = false;
        document.querySelectorAll(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`).forEach(el => {
            if (el.textContent.trim() === word) {
                const p = el.parentNode;
                while (el.firstChild) p.insertBefore(el.firstChild, el);
                el.remove();
                removedFromDom = true;
            }
        });
        if (removedFromDom) {
            saveHighlights();
        } else {
            const saved = JSON.parse(localStorage.getItem(DS_CONFIG.consts.STORAGE_KEY) || '[]');
            const newSaved = saved.filter(h => h.text !== word);
            localStorage.setItem(DS_CONFIG.consts.STORAGE_KEY, JSON.stringify(newSaved));
        }
    };

    if (elementRef) {
        softDelete(elementRef, doDelete);
    } else {
        doDelete();
    }
}

function applySavedHighlights() {
    if (DS_CONFIG.runtime.isRestoring) return; DS_CONFIG.runtime.isRestoring = true;
    try {
        const saved = JSON.parse(localStorage.getItem(DS_CONFIG.consts.STORAGE_KEY) || '[]');
        saved.forEach(item => {
            const parent = document.evaluate(item.path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            if (parent) {
                const walker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT, null, false);
                let node; let matchCount = 0; let finishedItem = false;
                while (node = walker.nextNode()) {
                    if (finishedItem) break; const nodeText = node.textContent; let searchPos = 0;
                    while (true) {
                        const idx = nodeText.indexOf(item.text, searchPos); if (idx === -1) break;
                        if (matchCount === (item.rank || 0)) {
                            if (node.parentElement.classList.contains(DS_CONFIG.consts.HIGHLIGHT_CLASS)) { finishedItem = true; break; }
                            const range = document.createRange(); range.setStart(node, idx); range.setEnd(node, idx + item.text.length);
                            const mark = document.createElement('mark'); mark.className = DS_CONFIG.consts.HIGHLIGHT_CLASS; mark.appendChild(range.extractContents()); range.insertNode(mark);
                            finishedItem = true; break;
                        }
                        matchCount++; searchPos = idx + 1;
                    }
                }
            }
        });
    } catch(e){}
    setTimeout(() => { DS_CONFIG.runtime.isRestoring = false; }, 200);
}

const isSidebarVisible = () => {
    if (!DOM.sidebar) return false;
    if (DS_CONFIG.settings.sidebarSide === 'right') return DOM.sidebar.style.right === '0px';
    return DOM.sidebar.style.left === '0px';
};

const showSidebar = () => {
    if (DOM.sidebar) {
        if (DS_CONFIG.settings.sidebarSide === 'right') { DOM.sidebar.style.right = '0'; DOM.sidebar.style.left = ''; }
        else { DOM.sidebar.style.left = '0'; DOM.sidebar.style.right = ''; }
    }
    updatePageLayout();
    if (!DS_CONFIG.settings.isDocked) toggleDockingMode(true);
};

const hideSidebar = () => {
    if (DOM.sidebar) {
        if (DS_CONFIG.settings.sidebarSide === 'right') { DOM.sidebar.style.right = '-1200px'; DOM.sidebar.style.left = ''; }
        else { DOM.sidebar.style.left = '-1200px'; DOM.sidebar.style.right = ''; }
    }
    if (DS_CONFIG.runtime.abortCtrl) DS_CONFIG.runtime.abortCtrl.abort();
    const cp = document.getElementById('ds-config-panel'); if (cp) cp.style.display = 'none';
    const hp = document.getElementById('ds-help-panel'); if (hp) hp.style.display = 'none';
    updatePageLayout();
    if (DS_CONFIG.settings.isDocked) toggleDockingMode(false);
};

const switchTab = (tabName) => {
    if (tabName !== 'docked' && tabName !== 'history') return;
    DS_CONFIG.runtime.activeTab = tabName;
    document.querySelectorAll('.ds-tab').forEach(tab => { tab.classList.remove('active'); if (tab.dataset.tab === tabName) tab.classList.add('active'); });
    document.querySelectorAll('.tab-panel').forEach(panel => { panel.classList.remove('active'); if (panel.dataset.panel === tabName) panel.classList.add('active'); });

    if (tabName === 'history') {
        DS_CONFIG.settings.historySort = 'time_desc';
        DS_CONFIG.runtime.historyLimit = 50; // 重置显示数量
        renderHistoryMenu();
    }
};

function toggleDockingMode(enable, isInit = false) {
    DS_CONFIG.settings.isDocked = enable;
    GM_setValue('ds_is_docked', enable);

    const dockedTab = document.getElementById('ds-tab-docked');
    const popup = document.getElementById('ds-popup');

   if (enable) {
        popup.style.display = 'none';
        if (!isInit) {
            switchTab('docked');
        }
    } else {
    }
}

// ==================== READER MODE LOGIC ====================
function toggleReaderMode() {
    if (DS_CONFIG.runtime.isReaderMode) exitReaderMode();
    else enterReaderMode();
}

function enterReaderMode() {
    if (DS_CONFIG.runtime.isReaderMode) return;
    if (typeof Readability === 'undefined') {
        alert("Readability library failed to load. Please check your connection.");
        return;
    }
    const documentClone = document.cloneNode(true);
    const reader = new Readability(documentClone);
    const article = reader.parse();
    if (!article) return alert("抱歉,未能识别正文内容。");

    DS_CONFIG.runtime.isReaderMode = true;
    document.documentElement.classList.add('reader-mode-active');
    document.body.classList.add('reader-mode-active');

    DOM.readerWrapper = document.createElement('div');
    DOM.readerWrapper.id = 'custom-reader-overlay';

    DOM.readerWrapper.innerHTML = `
        <div id="reader-progress-v"></div>
        <div id="reader-ctrl-bar">
            <button class="ctrl-btn btn-width" id="pw-minus" title="加宽">‹ ›</button>
            <button class="ctrl-btn btn-width" id="pw-plus" title="缩窄">› ‹</button>
            <button class="ctrl-btn btn-fs-plus" id="fs-plus" title="增大字号">A</button>
            <button class="ctrl-btn btn-fs-minus" id="fs-minus" title="减小字号">ᴀ</button>
            <button class="ctrl-btn" id="lh-plus" title="增大行高" style="font-size:16px">☰+</button>
            <button class="ctrl-btn" id="lh-minus" title="减小行高" style="font-size:12px">☰-</button>
            <button class="ctrl-btn exit-btn" id="reader-exit" title="退出">✕</button>
        </div>

        <div id="custom-reader-container" style="width: ${DS_CONFIG.runtime.readerPageWidth}px; font-size: ${DS_CONFIG.runtime.readerFontSize}px; line-height: ${DS_CONFIG.runtime.readerLineHeight}; padding: 60px 20px 100px 20px;">
            <h1 style="font-size: ${DS_CONFIG.runtime.readerFontSize + 12}px; color: #A8A495; margin: 0 0 40px 0; line-height: 1.3;">${article.title}</h1>
            <div id="reader-content-body">${article.content}</div>
        </div>
    `;
    document.body.appendChild(DOM.readerWrapper);

    if (DOM.popup && DOM.popup.parentNode) {
        document.body.appendChild(DOM.popup);
    }

    updateReaderCtrlPosition();
    updatePageLayout();

    DOM.readerWrapper.onscroll = () => {
        const height = DOM.readerWrapper.scrollHeight - DOM.readerWrapper.clientHeight;
        const scrolled = (DOM.readerWrapper.scrollTop / height) * 100 || 0;
        const prog = document.getElementById("reader-progress-v");
        if (prog) prog.style.height = scrolled + "%";
    };

    document.getElementById('fs-plus').onclick = () => updateReaderStyle('fs', 2);
    document.getElementById('fs-minus').onclick = () => updateReaderStyle('fs', -2);
    document.getElementById('pw-plus').onclick = () => updateReaderStyle('pw', -50);
    document.getElementById('pw-minus').onclick = () => updateReaderStyle('pw', 50);
    document.getElementById('lh-plus').onclick = () => updateReaderStyle('lh', 0.1);
    document.getElementById('lh-minus').onclick = () => updateReaderStyle('lh', -0.1);
    document.getElementById('reader-exit').onclick = exitReaderMode;
}

function updateReaderCtrlPosition() {
    const bar = document.getElementById('reader-ctrl-bar');
    if (!bar) return;

    bar.style.left = '';
    bar.style.right = '';
    bar.style.borderRadius = '';
    bar.style.borderWidth = '1px';
    bar.style.borderStyle = 'solid';
    bar.style.borderTop = 'none';

    if (DS_CONFIG.settings.sidebarSide === 'right') {
        bar.style.left = '0';
        bar.style.right = 'auto';
        bar.style.borderRadius = '0 0 4px 0';
        bar.style.borderLeft = 'none';
    } else {
        bar.style.right = '0';
        bar.style.left = 'auto';
        bar.style.borderRadius = '0 0 0 4px';
        bar.style.borderRight = 'none';
    }
}

function exitReaderMode() {
    if (!DS_CONFIG.runtime.isReaderMode) return;
    DS_CONFIG.runtime.isReaderMode = false;
    if (DOM.readerWrapper) DOM.readerWrapper.remove();
    document.documentElement.classList.remove('reader-mode-active');
    document.body.classList.remove('reader-mode-active');
    updatePageLayout();
}

function updateReaderStyle(type, delta) {
    const container = document.getElementById('custom-reader-container');
    if (!container) return;
    if (type === 'fs') {
        DS_CONFIG.runtime.readerFontSize += delta;
        container.style.fontSize = DS_CONFIG.runtime.readerFontSize + 'px';
        const title = container.querySelector('h1');
        if (title) title.style.fontSize = (DS_CONFIG.runtime.readerFontSize + 12) + 'px';
    } else if (type === 'pw') {
        DS_CONFIG.runtime.readerPageWidth += delta;
        const maxW = window.innerWidth - 150;
        if (DS_CONFIG.runtime.readerPageWidth < 400) DS_CONFIG.runtime.readerPageWidth = 400;
        if (DS_CONFIG.runtime.readerPageWidth > maxW) DS_CONFIG.runtime.readerPageWidth = maxW;
        container.style.width = DS_CONFIG.runtime.readerPageWidth + 'px';
    } else if (type === 'lh') {
        DS_CONFIG.runtime.readerLineHeight += delta;
        if (DS_CONFIG.runtime.readerLineHeight < 1.0) DS_CONFIG.runtime.readerLineHeight = 1.0;
        container.style.lineHeight = DS_CONFIG.runtime.readerLineHeight;
    }
}

function showSmartPopup(text, targetHighlight, context = "", isSelection = false) {
    const cp = document.getElementById('ds-config-panel');
    const hp = document.getElementById('ds-help-panel');
    if (cp) cp.style.display = 'none';
    if (hp) hp.style.display = 'none';

    DS_CONFIG.runtime.isSwitchingContext = true;
if (DS_CONFIG.runtime.popupAbortCtrl) DS_CONFIG.runtime.popupAbortCtrl.abort();
if (DS_CONFIG.runtime.abortCtrl) DS_CONFIG.runtime.abortCtrl.abort();
setTimeout(() => { DS_CONFIG.runtime.isSwitchingContext = false; }, 0);

DS_CONFIG.runtime.popupAbortCtrl = new AbortController();
    const signal = DS_CONFIG.runtime.popupAbortCtrl.signal;
    const dictPrompt = isChinese(text) ? DS_CONFIG.consts.PROMPTS.DICT_CN : DS_CONFIG.consts.PROMPTS.DICT_EN;
    const dictKey = text;
    let dictUserContent = text;
    if (context && context.trim().length > text.length) {
        dictUserContent = `Context: "${context}"\nTarget: "${text}"\nInstruction: Identify the lemma (prototype) of the Target word based on the Context (e.g., 'found' -> 'find'). Then define the prototype strictly following the dictionary format.`;
    }
    const contextKey = text + "_" + context.substring(0, 20);
    const contextPrompt = `你是一个语言专家。请分析"${text}"在以下句子中的用法:\n\n"${context}"\n\n请模仿以下风格进行解析:\n"在句子 '...' 中,'${text}' 是...词性...形式,与...构成...搭配,表示...。这里的固定搭配是...,意思是...。"`;

    let leftEl, rightEl;
    if (isSidebarVisible() || DS_CONFIG.settings.isDocked) {
        if (!isSidebarVisible()) showSidebar();
        switchTab('docked');
        leftEl = document.querySelector('#ds-docked-left-content .ds-popup-text');
        rightEl = document.querySelector('#ds-docked-right-content .ds-popup-text');
        const rightTitle = document.querySelector('#ds-docked-right-content .ds-popup-title');
        if (rightTitle) rightTitle.innerText = '🧠 文中解析';
    } else {
        if (!DOM.popup) return;
        let rect;
        if (isSelection) { try { rect = window.getSelection().getRangeAt(0).getBoundingClientRect(); } catch(e) { return; } }
        else if (targetHighlight) { rect = targetHighlight.getBoundingClientRect(); }
        else { rect = { top: DS_CONFIG.runtime.lastY - 10, bottom: DS_CONFIG.runtime.lastY + 10, left: DS_CONFIG.runtime.lastX - 10, width: 20, height: 20 }; }
        const pWidth = parseInt(DOM.popup.style.width || DS_CONFIG.settings.popupWidth) || 600;
        const pHeight = parseInt(DOM.popup.style.height || DS_CONFIG.settings.popupHeight) || 350;
        const viewportHeight = window.innerHeight; const viewportWidth = window.innerWidth;
        let top = rect.bottom + 10; let left = rect.left + (rect.width / 2) - (pWidth / 2);
        if (top + pHeight > viewportHeight) { top = rect.top - 10 - pHeight; if (top < 10) top = 10; }
        if (left < 10) left = 10; if (left + pWidth > viewportWidth - 10) left = viewportWidth - pWidth - 10;
        DOM.popup.style.top = top + 'px'; DOM.popup.style.left = left + 'px'; DOM.popup.style.transform = 'none';
        DOM.popup.style.display = 'flex';
        DS_CONFIG.runtime.currentPopupTrigger = targetHighlight;

        UI.clear(DOM.popup);
        ['n', 's', 'w', 'e', 'nw', 'ne', 'sw', 'se'].forEach(dir => {
            DOM.popup.appendChild(UI.el('div', { className: `ds-resize-handle ds-rh-${dir}`, 'data-dir': dir }));
        });

        const headerBar = UI.el('div', { id: 'ds-popup-header-bar' }, [
            UI.el('div', { id: 'ds-popup-lock', className: 'ds-popup-icon', title: '锁定并吸附到侧边栏' }, '🏠'),
            UI.el('div', { id: 'ds-popup-close-float', className: 'ds-popup-icon' }, '✖')
        ]);
        DOM.popup.appendChild(headerBar);

        // Update Titles in Popup
        const body = UI.el('div', { id: 'ds-popup-body' }, [
            UI.el('div', { className: 'ds-split-view' }, [
                UI.el('div', { className: 'ds-split-left', id: 'ds-popup-left-content' }, [
                      UI.el('div', { className: 'ds-popup-title' }, '🔍 词典解析'),
                      UI.el('div', { className: 'ds-popup-text' })
                ]),
                UI.el('div', { className: 'ds-split-right', id: 'ds-popup-right-content' }, [
                      UI.el('div', { className: 'ds-popup-title' }, '🧠 文中解析'),
                      UI.el('div', { className: 'ds-popup-text' })
                ])
            ])
        ]);
        DOM.popup.appendChild(body);

        bindPopupEvents(text);
        leftEl = DOM.popup.querySelector('#ds-popup-left-content .ds-popup-text');
        rightEl = DOM.popup.querySelector('#ds-popup-right-content .ds-popup-text');
    }

    if (!leftEl || !rightEl) return;
    DS_CONFIG.runtime.lastPopupParams.left = { sys: dictPrompt, user: dictUserContent, el: leftEl, cat: 'dict', key: dictKey, hw: text, mode: 'dict' };
    DS_CONFIG.runtime.lastPopupParams.right = { sys: contextPrompt, user: context, el: rightEl, cat: 'context', key: contextKey, hw: text, mode: 'normal' };

    streamToElement(dictPrompt, dictUserContent, leftEl, 'dict', dictKey, text, 'dict', signal);
    streamToElement(contextPrompt, context, rightEl, 'context', contextKey, text, 'normal', signal);
}

function bindPopupEvents(text) {
    const headerBar = document.getElementById('ds-popup-header-bar');
    if(headerBar) {
        headerBar.addEventListener('mousedown', (e) => {
            const icon = e.target.closest('.ds-popup-icon');
            if (icon) return;
            DS_CONFIG.runtime.isDraggingPopup = true; DS_CONFIG.runtime.dragStartX = e.clientX; DS_CONFIG.runtime.dragStartY = e.clientY; DS_CONFIG.runtime.popupStartX = DOM.popup.offsetLeft; DS_CONFIG.runtime.popupStartY = DOM.popup.offsetTop;
            document.body.classList.add('ds-global-cursor-move');
            document.documentElement.classList.add('ds-global-cursor-move');
        });
        headerBar.addEventListener('click', (e) => {
            const icon = e.target.closest('.ds-popup-icon');
            if (!icon) return;
            if (icon.id === 'ds-popup-close-float') { DOM.popup.style.display = 'none'; DS_CONFIG.runtime.currentPopupTrigger = null; }
            else if (icon.id === 'ds-popup-lock') { showSidebar(); }
        });
    }
    DOM.popup.querySelectorAll('.ds-resize-handle').forEach(el => {
        el.addEventListener('mousedown', (e) => {
            e.preventDefault(); e.stopPropagation();
            DS_CONFIG.runtime.isResizingPopup = true;
            DS_CONFIG.runtime.resizeDirection = el.dataset.dir;
            DS_CONFIG.runtime.dragStartX = e.clientX;
            DS_CONFIG.runtime.dragStartY = e.clientY;
            DS_CONFIG.runtime.resizeStartRect = DOM.popup.getBoundingClientRect();
        });
    });
}

function createSidebarIcon(id, icon, title, labelText, active = false) {
    const iconDiv = UI.el('div', { id: id, className: `ds-v-icon ${active ? 'active' : ''}`, title: title, draggable: true }, icon);
    const labelSpan = UI.el('span', { className: 'ds-v-label' }, labelText);
    iconDiv.appendChild(labelSpan);
    return iconDiv;
}

function buildUI() {
    const isTopWindow = (window.self === window.top);
    if (!isTopWindow) return;
    if (document.getElementById('ds-sidebar')) return;

    const container = UI.el('div', { id: 'ds-sidebar' });
    const promptString = DS_CONFIG.settings.customPrompts.map(p => `${p.name}=${p.template}`).join('\n');
    const sitesString = DS_CONFIG.settings.customSites.map(s => `${s.name}=${s.value}`).join('\n');
    const disabledSitesString = DS_CONFIG.settings.disabledSites.join('\n');

    const activeZone = UI.el('div', { id: 'ds-toolbar-active' });
    const hiddenZone = UI.el('div', { id: 'ds-toolbar-hidden' }, [
        UI.el('span', { className: 'ds-hidden-text' }, '可将图标拖动此处隐藏')
    ]);

    const verticalToolbar = UI.el('div', { id: 'ds-vertical-toolbar' }, [activeZone, hiddenZone]);

    // 定义所有图标数据
    const allIconsData = [
        { id: 'ds-close', icon: '✖', title: '关闭', label: '关闭' },
        { id: 'ds-side-toggle', icon: '👈🏻', title: '切换侧边栏方向', label: '切至左侧' },
        { id: 'ds-full-page-trans-btn', icon: '🌐', title: '全文翻译开关', label: '全文翻译' },
        { id: 'ds-expand-toolbar', icon: '↔️', title: '展开菜单', label: '展开菜单' },
        { id: 'ds-summary-btn', icon: '🎯', title: '全文总结', label: '全文总结' },
        { id: 'ds-reader-mode-btn', icon: '👓', title: '阅读模式 (简读)', label: '阅读模式' },
        { id: 'ds-help-btn', icon: '💡', title: '使用说明', label: '使用说明' },
        { id: 'ds-cfg-toggle', icon: '⚙️', title: '设置', label: '设置' }
    ];

    // 根据配置渲染图标到不同区域
    const hiddenSet = new Set(DS_CONFIG.settings.hiddenIcons);
    allIconsData.forEach(data => {
        const btn = createSidebarIcon(data.id, data.icon, data.title, data.label);
        if (data.id === 'ds-cfg-toggle' || data.id === 'ds-close') {
            activeZone.appendChild(btn);
        } else if (hiddenSet.has(data.id)) {
            hiddenZone.appendChild(btn); // 放入隐藏区
        } else {
            activeZone.appendChild(btn); // 放入活跃区
        }
    });

    // Drag and Drop Logic (Refined for 2 Zones)
    let draggedItem = null;

    const handleDragStart = (e) => {
        if (e.target.classList.contains('ds-v-icon')) {
            draggedItem = e.target;
            e.dataTransfer.effectAllowed = 'move';
            // 关键修正:添加 dragging 类名,确保 getDragAfterElement 计算准确
            setTimeout(() => e.target.classList.add('dragging'), 0);
        }
    };

    const handleDragEnd = (e) => {
        if (draggedItem) {
            draggedItem.classList.remove('dragging');
            draggedItem.style.opacity = '';

            // 保存隐藏状态
            const newHiddenList = [];
            hiddenZone.querySelectorAll('.ds-v-icon').forEach(el => newHiddenList.push(el.id));
            GM_setValue('ds_hidden_icons', newHiddenList);
            DS_CONFIG.settings.hiddenIcons = newHiddenList;

            draggedItem = null;
        }
    };

    // ... (前面是 handleDragStart 和 handleDragEnd) ...

    const handleDragOver = (e, container) => {
        e.preventDefault();

        const isEditMode = verticalToolbar.classList.contains('ds-edit-mode');

        if (!isEditMode && container.id === 'ds-toolbar-hidden') return;

        if (draggedItem && (draggedItem.id === 'ds-cfg-toggle' || draggedItem.id === 'ds-close') && container.id === 'ds-toolbar-hidden') {
            return;
        }

        const afterElement = getDragAfterElement(container, e.clientY);
        if (afterElement == null) {
            container.appendChild(draggedItem);
        } else {
            container.insertBefore(draggedItem, afterElement);
        }
    };

    function getDragAfterElement(container, y) {
        const draggableElements = [...container.querySelectorAll('.ds-v-icon:not(.dragging)')];
        return draggableElements.reduce((closest, child) => {
            const box = child.getBoundingClientRect();
            const offset = y - box.top - box.height / 2;
            if (offset < 0 && offset > closest.offset) {
                return { offset: offset, element: child };
            } else {
                return closest;
            }
        }, { offset: Number.NEGATIVE_INFINITY }).element;
    }


    // 绑定事件
    verticalToolbar.addEventListener('dragstart', handleDragStart);
    verticalToolbar.addEventListener('dragend', handleDragEnd);
    activeZone.addEventListener('dragover', (e) => handleDragOver(e, activeZone));
    hiddenZone.addEventListener('dragover', (e) => handleDragOver(e, hiddenZone));

    // ============ 2. Main Content Panel (New Wrapper) ============
    const mainPanel = UI.el('div', { id: 'ds-main-panel' });

    // Header for Tabs (Simplified)
    const createTab = (id, icon, label, title, active=false) => {
         return UI.el('div', { className: `ds-tab ${active?'active':''}`, 'data-tab': id, title: title, draggable: true }, [
             document.createTextNode(icon),
             UI.el('span', { className: 'ds-tab-label' }, label)
         ]);
    };

const tabHeader = UI.el('div', { id: 'ds-tab-header' }, [
        UI.el('div', { id: 'ds-tabs-wrapper' }, [
            createTab('history', '🕒', '历史', '历史与高亮', false),
            UI.el('div', { className: 'ds-tab active', id: 'ds-tab-docked', 'data-tab': 'docked', title: '智能助手', draggable: true }, [
                document.createTextNode('💬'),
                UI.el('span', { className: 'ds-tab-label' }, '助手')
            ])
        ])
    ]);

    // Tab Drag and Drop Logic
    let draggedTab = null;
    const tabsWrapper = tabHeader.querySelector('#ds-tabs-wrapper');

    tabsWrapper.addEventListener('dragstart', (e) => {
        const tab = e.target.closest('.ds-tab');
        if (tab) {
            draggedTab = tab;
            tab.classList.add('dragging');
            e.dataTransfer.effectAllowed = 'move';
        }
    });

    tabsWrapper.addEventListener('dragend', (e) => {
        const tab = e.target.closest('.ds-tab');
        if (tab) {
            tab.classList.remove('dragging');
            draggedTab = null;
        }
    });

    tabsWrapper.addEventListener('dragover', (e) => {
        e.preventDefault();
        const afterElement = getDragAfterTab(tabsWrapper, e.clientX);
        const dragging = tabsWrapper.querySelector('.dragging');
        if (dragging) {
            if (afterElement == null) {
                tabsWrapper.appendChild(dragging);
            } else {
                tabsWrapper.insertBefore(dragging, afterElement);
            }
        }
    });

    function getDragAfterTab(container, x) {
        const draggableElements = [...container.querySelectorAll('.ds-tab:not(.dragging)')];
        return draggableElements.reduce((closest, child) => {
            const box = child.getBoundingClientRect();
            const offset = x - box.left - box.width / 2;
            if (offset < 0 && offset > closest.offset) {
                return { offset: offset, element: child };
            } else {
                return closest;
            }
        }, { offset: Number.NEGATIVE_INFINITY }).element;
    }

    // Confirm Modal
    const confirmModal = UI.el('div', { id: 'ds-confirm-modal' }, [
        UI.el('div', { className: 'ds-confirm-box' }, [
            UI.el('div', { className: 'ds-confirm-text' }, '确定要清空所有生词和缓存吗?'),
            UI.el('div', { className: 'ds-confirm-btns' }, [
                UI.el('button', { id: 'ds-confirm-yes', className: 'ds-btn ds-btn-yes' }, '确定清空'),
                UI.el('button', { id: 'ds-confirm-no', className: 'ds-btn ds-btn-no' }, '取消')
            ])
        ])
    ]);

    // Config Panel
    const configPanel = UI.el('div', { id: 'ds-config-panel' }, [
        UI.el('div', { className: 'ds-panel-header' }, [
            UI.el('span', { className: 'ds-panel-title' }, '⚙️ 设置'),
            UI.el('button', { id: 'save-api-key', className: 'ds-panel-top-btn' }, '保存并退出')
        ]),
UI.el('div', { className: 'cfg-row' }, [
            UI.el('span', { style: { fontWeight: 'bold' } }, '查询/高亮/跳转时复制文本:'),
            UI.el('label', { className: 'ds-switch' }, [
                UI.el('input', { type: 'checkbox', id: 'cfg-auto-copy', checked: DS_CONFIG.settings.autoCopy }),
                UI.el('span', { className: 'ds-slider-btn' })
            ])
        ]),
        UI.el('div', { className: 'cfg-row', style: { flexDirection: 'column', alignItems: 'flex-start' } }, [
            UI.el('span', { style: { fontWeight: 'bold' } }, 'DeepSeek API Key:'),
            UI.el('input', { type: 'password', id: 'cfg-api-key', style: { width: '100%', marginTop: '5px', padding: '6px' }, value: DS_CONFIG.settings.apiKey })
        ]),
        UI.el('div', { className: 'cfg-row', style: { flexDirection: 'column', alignItems: 'flex-start' } }, [
            UI.el('span', { className: 'ds-instruction-text' }, '自定义Prompt格式(每行一个Prompt):'),
            UI.el('span', { className: 'ds-instruction-text', style: { fontWeight: 'normal', color: 'var(--ds-text)', opacity: 0.8 } }, '按钮名=prompt具体指令'),
            UI.el('textarea', { id: 'cfg-prompts', className: 'ds-cfg-textarea', placeholder: '按钮名称=具体指令内容\n每行一条...', value: promptString })
        ]),
        // New: Custom AI Sites Config
        UI.el('div', { className: 'cfg-row', style: { flexDirection: 'column', alignItems: 'flex-start' } }, [
            UI.el('span', { className: 'ds-instruction-text' }, '自定义求助网站格式(每行一个网站):'),
            UI.el('span', { className: 'ds-instruction-text', style: { fontWeight: 'normal', color: 'var(--ds-text)', opacity: 0.8 } }, '按钮名=网站具体网址'),
            UI.el('textarea', { id: 'cfg-ai-sites', className: 'ds-cfg-textarea', placeholder: 'DeepSeek=https://chat.deepseek.com/\n...', value: sitesString })
        ]),
        UI.el('div', { className: 'cfg-row', style: { flexDirection: 'column', alignItems: 'flex-start' } }, [
            UI.el('span', { className: 'ds-instruction-text' }, '禁用网站名单:'),
            UI.el('textarea', { id: 'cfg-disabled-sites', className: 'ds-cfg-textarea', placeholder: 'example.com\nyoutube.com/shorts\n...', value: disabledSitesString })
        ])
    ]);

    // Help Panel
    const createHelpItem = (key, desc) => UI.el('div', { className: 'ds-help-item' }, [
        UI.el('span', { className: 'ds-help-key' }, key),
        UI.el('span', { className: 'ds-help-desc' }, desc)
    ]);
    const helpPanel = UI.el('div', { id: 'ds-help-panel' }, [
        UI.el('div', { className: 'ds-panel-header' }, [
            UI.el('span', { className: 'ds-panel-title' }, '💡 使用说明'),
            UI.el('button', { id: 'ds-help-close', className: 'ds-panel-top-btn' }, '退出')
        ]),
        createHelpItem('Alt + Alt', '快速双击 (0.5s内),调出浮窗对鼠标所指文本查词。'),
        createHelpItem('Alt', '关闭浮窗。'),
        createHelpItem('Alt + 1', '对鼠标所指文本切换高亮状态。'),
        createHelpItem('Alt + 2', '开关侧边栏(开启时自动吸附查词)。'),
        createHelpItem('Alt + 3', '开关沉浸式阅读模式。'),
        createHelpItem('Alt + `', '切换侧边栏标签页。'),
        createHelpItem('Alt + 左键', '可对鼠标所指文本段落进行翻译。'),

    ]);

// Tab Content
    const tabContent = UI.el('div', { id: 'ds-tab-content' }, [
        UI.el('div', { className: 'tab-panel', 'data-panel': 'history', id: 'ds-history-content' }),
        UI.el('div', { className: 'tab-panel active', 'data-panel': 'docked', id: 'ds-docked-panel' }, [ UI.el('div', { className: 'ds-docked-content' }, [ UI.el('div', { className: 'ds-docked-section', id: 'ds-docked-left-content', style: { flex: '0 1 auto', maxHeight: '60%', overflowY: 'auto', borderBottom: '1px solid #444' } }, [ UI.el('div', { className: 'ds-popup-title' }, '🔍 词典解析'), UI.el('div', { className: 'ds-popup-text' }) ]), UI.el('div', { className: 'ds-docked-section', id: 'ds-docked-right-content', style: { flex: '1 1 auto', overflowY: 'auto' } }, [ UI.el('div', { className: 'ds-popup-title' }, '🧠 文中解析'), UI.el('div', { className: 'ds-popup-text' }) ]) ]) ]) ]);

    // NEW: Jump Button Panel Implementation (No more Select)
    const currentSiteName = DS_CONFIG.settings.customSites.find(s => s.value === DS_CONFIG.settings.lastAiSite)?.name || "DeepSeek";

    const jumpWrapper = UI.el('div', { className: 'ds-jump-wrapper' }, [
        UI.el('button', { id: 'ds-jump-btn', className: 'ds-action-btn', style: { marginRight: '5px' } }, '🔗 求助'),
        UI.el('div', { className: 'ds-jump-select-wrapper' }, [
            UI.el('button', { id: 'ds-cur-site-btn', className: 'ds-jump-select-btn', title: '点击切换跳转目标' }, [
                 document.createTextNode(currentSiteName),
                 UI.el('span', { style: {fontSize: '10px', opacity: 0.7} }, '▼')
            ]),
            UI.el('div', { id: 'ds-site-panel', className: 'ds-site-panel' })
        ])
    ]);

    // Populate Site Panel
    const updateSitePanel = () => {
        const panel = jumpWrapper.querySelector('#ds-site-panel');
        UI.clear(panel);
        DS_CONFIG.settings.customSites.forEach(s => {
            const btn = UI.el('div', {
                className: `ds-site-item ${s.value === DS_CONFIG.settings.lastAiSite ? 'active' : ''}`,
                title: s.value
            }, s.name);
            btn.addEventListener('click', () => {
                DS_CONFIG.settings.lastAiSite = s.value;
                GM_setValue('ds_last_ai_site', s.value);

                // Active class update
                panel.querySelectorAll('.ds-site-item').forEach(el => el.classList.remove('active'));
                btn.classList.add('active');

                const curBtn = document.getElementById('ds-cur-site-btn');
                if (curBtn) {
                     curBtn.firstChild.textContent = s.name;
                }
                panel.classList.remove('active');
            });
            panel.appendChild(btn);
        });
    };
    updateSitePanel();

    // Input Area
    const inputArea = UI.el('div', { id: 'ds-input-area' }, [
        UI.el('div', { id: 'ds-input-wrapper' }, [
            UI.el('textarea', { id: 'ds-input', placeholder: 'DeepSeek AI 等待您的指令...' }),
            UI.el('div', { id: 'ds-send-row' }, [
                jumpWrapper,
                UI.el('button', { id: 'ds-send', className: 'ds-action-btn' }, '🚀 发送')
            ])
        ])
    ]);

    mainPanel.appendChild(tabHeader);
    mainPanel.appendChild(confirmModal);
    mainPanel.appendChild(configPanel);
    mainPanel.appendChild(helpPanel);
    mainPanel.appendChild(tabContent);
    mainPanel.appendChild(UI.el('div', { id: 'ds-fn-bar' }));
    mainPanel.appendChild(inputArea);

    container.appendChild(UI.el('div', { id: 'ds-resizer' }));
    container.appendChild(verticalToolbar);
    container.appendChild(mainPanel);

    const popupEl = UI.el('div', { id: 'ds-popup', style: { width: DS_CONFIG.settings.popupWidth, height: DS_CONFIG.settings.popupHeight } });
    popupEl.addEventListener('mouseup', () => {
        GM_setValue('ds_popup_width', popupEl.style.width);
        GM_setValue('ds_popup_height', popupEl.style.height);
    });

    document.body.appendChild(container);
    document.body.appendChild(popupEl);

    DOM.sidebar = container;
    DOM.popup = popupEl;

const autoCopySwitch = document.getElementById('cfg-auto-copy');
    if (autoCopySwitch) {
        autoCopySwitch.addEventListener('change', (e) => {
             const isChecked = e.target.checked;
             DS_CONFIG.settings.autoCopy = isChecked;
             GM_setValue('ds_auto_copy', isChecked);
             GM_setValue('ds_jump_copy', isChecked);
             DS_CONFIG.settings.jumpCopy = isChecked;
        });
    }

    renderCustomButtons();
    injectStyles();
    updateSidebarPosition(false);

    if (DS_CONFIG.settings.isDocked) {
        toggleDockingMode(true, true);
    }
}

function renderCustomButtons() {
    const bar = document.getElementById('ds-fn-bar'); if (!bar) return;
    UI.clear(bar);

    DS_CONFIG.settings.customPrompts.forEach(item => {
        if (!item.name || !item.template) return;
        const btn = UI.el('div', {
            className: 'fn-btn custom-prompt-btn',
            title: item.template,
            onclick: () => {
                const input = document.getElementById('ds-input');
                if (input) {
                    const val = input.value.trim();
                    if (!val) {
                         input.placeholder = "请输入内容后点击...";
                         setTimeout(() => input.placeholder = "DeepSeek AI 等待您的指令...", 2000);
                         return;
                    }
                    const combinedQuery = `【${val}】\n\n${item.template}`;
                    askAI(combinedQuery, "", "custom");
                }
            }
        }, item.name);
        bar.appendChild(btn);
    });
}

function stopAllStreams() {
    if (DS_CONFIG.runtime.abortCtrl) { DS_CONFIG.runtime.abortCtrl.abort(); DS_CONFIG.runtime.abortCtrl = null; }
    if (DS_CONFIG.runtime.rightPanelAbortCtrl) { DS_CONFIG.runtime.rightPanelAbortCtrl.abort(); DS_CONFIG.runtime.rightPanelAbortCtrl = null; }
}

// ==================== 5. 事件绑定 ====================
function bindEvents() {
    document.addEventListener('click', (e) => {
             if (e.target.classList.contains('ds-headword')) {
                 const word = e.target.textContent.trim();
                 const container = e.target.closest('.web-menu-trans') || e.target.closest('.ds-popup-text');
                 if (word && container) {
                     e.stopPropagation();
                     try { let gCache = JSON.parse(GM_getValue(DS_CONFIG.consts.GLOBAL_DICT_CACHE_KEY, '{}')); if (gCache[word]) { delete gCache[word]; GM_setValue(DS_CONFIG.consts.GLOBAL_DICT_CACHE_KEY, JSON.stringify(gCache)); } } catch(err) {}
                     if (DS_CONFIG.runtime.popupCache.dict[word]) delete DS_CONFIG.runtime.popupCache.dict[word];
                     UI.clear(container);
                     container.appendChild(UI.el('span', { className: 'ds-popup-loading' }, 'DeepSeek 强制刷新中...'));
                     if (container.classList.contains('web-menu-trans')) { fetchVocabDefinition(word, container); }
                     else { const dictPrompt = isChinese(word) ? DS_CONFIG.consts.PROMPTS.DICT_CN : DS_CONFIG.consts.PROMPTS.DICT_EN; streamToElement(dictPrompt, word, container, 'dict', word, word, 'dict'); }
                 }
                 return;
             }

         if (e.target && e.target.classList.contains('ds-def-split')) {
             if (e.target.closest('#ds-highlight-content')) return;
             const defText = e.target.dataset.def;
             const word = DS_CONFIG.runtime.lastPopupParams.left?.hw || "";
             if(word) window.updateRightPanelExamples(defText, word);
         }
         // Close site panel if clicking outside
         const sitePanel = document.getElementById('ds-site-panel');
         const curSiteBtn = document.getElementById('ds-cur-site-btn');
         if (sitePanel && sitePanel.classList.contains('active')) {
             if (!sitePanel.contains(e.target) && !curSiteBtn.contains(e.target)) {
                 sitePanel.classList.remove('active');
             }
         }
    });

    document.addEventListener('click', (e) => {
         const targetHighlight = e.target.closest(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`);
         if (targetHighlight && !e.altKey) {
             e.preventDefault();
             e.stopPropagation();
         }
    }, true);

    document.addEventListener('contextmenu', (e) => {
        const inSidebar = DOM.sidebar && DOM.sidebar.contains(e.target);
        const inPopup = DOM.popup && DOM.popup.contains(e.target);
        if (inSidebar || inPopup) return;

        const targetHighlight = e.target.closest(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`);
        if (targetHighlight) {
            e.preventDefault();
            e.stopPropagation();
            removeHighlight(targetHighlight);
            DS_CONFIG.runtime.preventContextMenuOnce = true;
            setTimeout(() => { DS_CONFIG.runtime.preventContextMenuOnce = false; }, 300);
            stopAllStreams();
            return;
        }

        const hasInlineTrans = document.querySelector('.web-inline-trans');
        const hasPageTrans = document.querySelector('.ds-full-page-trans');
        const hasSourceHighlight = document.querySelector('.web-trans-source-highlight');

        if (hasInlineTrans || hasPageTrans || hasSourceHighlight) {
            e.preventDefault();
            e.stopPropagation();
            clearAllInlineTranslations();
            return;
        }
    });
    // FIX END

    document.addEventListener('mousemove', e => {
        DS_CONFIG.runtime.lastX = e.clientX; DS_CONFIG.runtime.lastY = e.clientY;
        const isTopWindow = (window.self === window.top);
        if (isTopWindow) {
            if (DS_CONFIG.runtime.isResizingPopup && DOM.popup) {
                e.preventDefault();
                const dx = e.clientX - DS_CONFIG.runtime.dragStartX; const dy = e.clientY - DS_CONFIG.runtime.dragStartY; const startRect = DS_CONFIG.runtime.resizeStartRect;
                if (DS_CONFIG.runtime.resizeDirection.includes('e')) { DOM.popup.style.width = (startRect.width + dx) + 'px'; }
                if (DS_CONFIG.runtime.resizeDirection.includes('w')) { DOM.popup.style.width = (startRect.width - dx) + 'px'; DOM.popup.style.left = (startRect.left + dx) + 'px'; }
                if (DS_CONFIG.runtime.resizeDirection.includes('s')) { DOM.popup.style.height = (startRect.height + dy) + 'px'; }
                if (DS_CONFIG.runtime.resizeDirection.includes('n')) { DOM.popup.style.height = (startRect.height - dy) + 'px'; DOM.popup.style.top = (startRect.top + dy) + 'px'; }
                return;
            }
            const isResizing = document.getElementById('ds-resizer')?.dataset.resizing === 'true';
            if (isResizing) {
                e.preventDefault();
                if (DOM.sidebar) {
                    let width;
                    if (DS_CONFIG.settings.sidebarSide === 'right') { width = window.innerWidth - e.clientX; }
                    else { width = e.clientX; }
                    if (width > 300 && width < window.innerWidth * 0.9) {
                        DOM.sidebar.style.width = width + 'px';
                        GM_setValue('sidebar_width', width);
                        DS_CONFIG.settings.sidebarWidth = width;
                        updatePageLayout();
                    }
                }
            }
            if (DS_CONFIG.runtime.isDraggingPopup && DOM.popup) {
                const dx = e.clientX - DS_CONFIG.runtime.dragStartX; const dy = e.clientY - DS_CONFIG.runtime.dragStartY;
                DOM.popup.style.left = (DS_CONFIG.runtime.popupStartX + dx) + 'px'; DOM.popup.style.top = (DS_CONFIG.runtime.popupStartY + dy) + 'px';
            }
        }
    }, {passive: false});

    document.addEventListener('mouseup', (e) => {
        const resizer = document.getElementById('ds-resizer'); if (resizer) resizer.dataset.resizing = 'false';
        document.body.classList.remove('ds-global-cursor-ew', 'ds-global-cursor-ns', 'ds-global-cursor-nwse', 'ds-global-cursor-nesw', 'ds-global-cursor-move');
        document.documentElement.classList.remove('ds-global-cursor-ew', 'ds-global-cursor-ns', 'ds-global-cursor-nwse', 'ds-global-cursor-nesw', 'ds-global-cursor-move');

        DS_CONFIG.runtime.isDraggingPopup = false; DS_CONFIG.runtime.isResizingPopup = false;
    });

    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape' && DS_CONFIG.runtime.isReaderMode) {
            exitReaderMode();
        }

        if (e.key !== 'Alt') { DS_CONFIG.runtime.lastAltUpTime = 0; }

        if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) || document.activeElement.isContentEditable) return;
        if (e.altKey && (e.key === '`' || e.code === 'Backquote')) {
            e.preventDefault();
            DS_CONFIG.runtime.sidebarLockUntil = Date.now() + 600;

            if (!isSidebarVisible()) {
                showSidebar(); // 如果没打开,先打开
            } else {
                const tabs = ['history', 'docked'];
                const currentTab = DS_CONFIG.runtime.activeTab;
                let currentIndex = tabs.indexOf(currentTab);
                if (currentIndex === -1) currentIndex = 1; // 默认视为 docked

                const nextIndex = (currentIndex + 1) % tabs.length;
                switchTab(tabs[nextIndex]);
            }
            return;
        }
        // Alt + 3: Toggle Reader Mode
        if (e.altKey && (e.key === '3' || e.code === 'Digit3')) {
            e.preventDefault();
            DS_CONFIG.runtime.sidebarLockUntil = Date.now() + 600;
            toggleReaderMode();
            return;
        }

        if (e.altKey && (e.key === '1' || e.code === 'Digit1')) {
            e.preventDefault();
            DS_CONFIG.runtime.sidebarLockUntil = Date.now() + 600;

            const el = document.elementFromPoint(DS_CONFIG.runtime.lastX, DS_CONFIG.runtime.lastY);
            const existingHighlight = el ? el.closest(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`) : null;
            const sidebarItem = el ? el.closest('.web-menu-item') : null;

            if (sidebarItem) {
                 const word = sidebarItem.dataset.word;
                 if(word) deleteWord(word, sidebarItem);
                 return;
            }

            if (existingHighlight) {
                removeHighlight(existingHighlight);
            } else {
                const sel = window.getSelection(); let range = null;
                if (sel.rangeCount && sel.toString().trim()) { range = sel.getRangeAt(0); }
                else { const wordObj = getCurrentSentence(); if (wordObj) { range = document.createRange(); range.setStart(wordObj.node, wordObj.s); range.setEnd(wordObj.node, wordObj.e); } }

                if (range) {
                    const text = range.toString().trim();
                    if (!range.commonAncestorContainer.parentElement.classList.contains(DS_CONFIG.consts.HIGHLIGHT_CLASS)) {
                        if (DS_CONFIG.settings.autoCopy) {
                            copyToClip(text);
                        }
                        const mark = document.createElement('mark'); mark.className = DS_CONFIG.consts.HIGHLIGHT_CLASS; mark.appendChild(range.extractContents()); range.insertNode(mark);
                        saveHighlights();
                        addToGlobalHistory(text, range.commonAncestorContainer.textContent);
                        sel.removeAllRanges();

                        if (window.self === window.top) {
                             const input = document.getElementById('ds-input');
                             if(input) input.value = text;
                        }
                    }
                }
            }
        }
        if (e.altKey && (e.key === '2' || e.code === 'Digit2')) {
            e.preventDefault();
            DS_CONFIG.runtime.sidebarLockUntil = Date.now() + 600;
            if (isSidebarVisible()) hideSidebar(); else showSidebar();
        }
    }, true);

    const isTopWindow = (window.self === window.top);
    if (isTopWindow) {
        document.addEventListener('keyup', (e) => {
            if (e.key === 'Alt') {
                if (DOM.popup.style.display !== 'none' && !DS_CONFIG.settings.isDocked) {
                    DOM.popup.style.display = 'none';
                    DS_CONFIG.runtime.currentPopupTrigger = null;
                    return;
                }

                const now = Date.now();
                if (now < DS_CONFIG.runtime.sidebarLockUntil) { DS_CONFIG.runtime.lastAltUpTime = 0; return; }

                if (now - DS_CONFIG.runtime.lastAltUpTime < 500) {
                    const selText = window.getSelection().toString().trim();
                    if (selText.length > 0) {
                        if (DS_CONFIG.settings.autoCopy) {
                            copyToClip(selText);
                        }
                        let context = ""; try { context = window.getSelection().getRangeAt(0).commonAncestorContainer.parentElement.innerText; } catch(e){}

                        if (isTopWindow) {
                            const input = document.getElementById('ds-input');
                            if(input) input.value = selText;
                        }

                        showSmartPopup(selText, null, context, true);
                    }
                    else {
                        const wordObj = getCurrentSentence();
                        if (wordObj && wordObj.text) {
                            if (DS_CONFIG.settings.autoCopy) {
                                copyToClip(wordObj.text);
                            }
                            const context = wordObj.node.parentElement ? wordObj.node.parentElement.innerText : wordObj.text;

                            if (isTopWindow) {
                                const input = document.getElementById('ds-input');
                                if(input) input.value = wordObj.text;
                            }

                            showSmartPopup(wordObj.text, null, context, false);
                        }
                    }
                    DS_CONFIG.runtime.lastAltUpTime = 0;
                }
                else {
                    DS_CONFIG.runtime.lastAltUpTime = now;
                }
            }
        }, true);

        const inputEl = document.getElementById('ds-input');
        inputEl?.addEventListener('focus', function(e) {
             setTimeout(() => {
               this.selectionStart = this.selectionEnd = this.value.length;
               this.scrollTop = this.scrollHeight;
             }, 0);
        });
    }

    document.addEventListener('click', e => { if (e.altKey) { e.preventDefault(); e.stopImmediatePropagation(); } }, true);

    if (isTopWindow) {
        if (DOM.sidebar) {
            DOM.sidebar.addEventListener('contextmenu', (e) => {
                 const item = e.target.closest('.web-menu-item');
                 if (item) {
                     e.preventDefault();
                     const word = item.dataset.word;
                     if (DS_CONFIG.runtime.activeTab === 'history') {
                         deleteFromHistory(word, item);
                     } else {
                         if(word) deleteWord(word, item);
                     }
                 }
            });
        }

        DOM.sidebar.addEventListener('click', (e) => {
            const menuJump = e.target.closest('.web-menu-jump');
            if (menuJump) {
                const item = menuJump.closest('.web-menu-item');
                const word = item.dataset.word;

                const highlights = document.querySelectorAll(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`);
                for (let el of highlights) {
                    if (el.textContent.trim() === word) {
                        el.scrollIntoView({ behavior: 'auto', block: 'center' });
                        el.style.transition = 'backgroundColor 0.2s';
                        el.style.backgroundColor = '#FFD700';
                        setTimeout(() => { el.style.backgroundColor = ''; }, 600);
                        break;
                    }
                }
                return;
            }

            if (e.target.classList.contains('ds-def-split')) {
                return;
            }

            const item = e.target.closest('.web-menu-item');
            if (item) {
                if (window.getSelection().toString().length > 0) return;

                const word = item.dataset.word;
                const trans = item.querySelector('.web-menu-trans');
                const isActive = item.classList.contains('active');

                // Clear other active items
                const contentId = DS_CONFIG.runtime.activeTab === 'history' ? 'ds-history-content' : 'ds-highlight-content';
                document.querySelectorAll(`#${contentId} .web-menu-item.active`).forEach(activeItem => {
                    activeItem.classList.remove('active');
                    const t = activeItem.querySelector('.web-menu-trans');
                    if (t) t.style.display = 'none';
                });

                if (!isActive) {
                    const inputEl = document.getElementById('ds-input');
                    if(inputEl) inputEl.value = word;

                    item.classList.add('active');
                    if (trans) {
                        trans.style.display = 'block';
                        if (!trans.hasChildNodes()) {
                            fetchVocabDefinition(word, trans);
                        }
                    }
                }
                return;
            }

            const dockRestore = e.target.closest('.ds-dock-restore');
            if (dockRestore) {
                e.stopPropagation();
                hideSidebar();
                return;
            }

            const tab = e.target.closest('.ds-tab');
            if (tab) { switchTab(tab.dataset.tab); return; }

            const targetId = e.target.id || e.target.closest('.ds-v-icon')?.id || e.target.closest('.header-action')?.id || e.target.closest('button')?.id;
            if (!targetId) return;

            if (targetId === 'ds-help-btn') {
                const hp = document.getElementById('ds-help-panel');
                const cp = document.getElementById('ds-config-panel');
                if (cp) cp.style.display = 'none';
                if (hp) hp.style.display = hp.style.display === 'flex' ? 'none' : 'flex';
            }
            else if (targetId === 'ds-help-close') { document.getElementById('ds-help-panel').style.display = 'none'; }
            else if (targetId === 'ds-full-page-trans-btn') { togglePageTranslation(); }
            else if (targetId === 'ds-clear-cache') { document.getElementById('ds-confirm-modal').style.display = 'flex'; }
            else if (targetId === 'ds-cfg-toggle') {
                const cp = document.getElementById('ds-config-panel');
                const hp = document.getElementById('ds-help-panel');
                const tb = document.getElementById('ds-vertical-toolbar');

                if (hp) hp.style.display = 'none';

                const isOpening = cp.style.display !== 'flex';
                cp.style.display = isOpening ? 'flex' : 'none';

                // 切换编辑模式样式
                if (isOpening) {
                    tb.classList.add('ds-edit-mode');
                } else {
                    tb.classList.remove('ds-edit-mode');
                }
            }
            else if (targetId === 'ds-close') { hideSidebar(); }
            else if (targetId === 'ds-side-toggle') { toggleSidebarSide(); }
            else if (targetId === 'ds-expand-toolbar') { toggleToolbarExpansion(); } // New Handler
            else if (targetId === 'ds-reader-mode-btn') { toggleReaderMode(); }
            else if (targetId === 'ds-cur-site-btn') { // New Site Panel Toggle
                const panel = document.getElementById('ds-site-panel');
                if (panel) panel.classList.toggle('active');
            }
            else if (targetId === 'save-api-key') {
                const cfgApiKey = document.getElementById('cfg-api-key');
                const cfgPrompts = document.getElementById('cfg-prompts');
                const cfgSites = document.getElementById('cfg-ai-sites');
                const cfgDisabled = document.getElementById('cfg-disabled-sites');

                if (!cfgApiKey) return;
                DS_CONFIG.settings.apiKey = cfgApiKey.value;

                // Save Custom Prompts
                const rawLines = cfgPrompts.value.split('\n');
                DS_CONFIG.settings.customPrompts = [];
                rawLines.forEach(line => {
                    if (line.includes('=')) { const parts = line.split('='); if (parts.length >= 2) { const name = parts[0].trim(); const template = line.substring(line.indexOf('=') + 1).trim(); if(name && template) DS_CONFIG.settings.customPrompts.push({name, template}); } }
                });

                // Save Custom Sites
                if (cfgSites) {
                    const rawSites = cfgSites.value.split('\n');
                    DS_CONFIG.settings.customSites = parseConfigLines(rawSites);
                    GM_setValue('ds_custom_sites', DS_CONFIG.settings.customSites);

                    // Refresh Site Panel (Explicit Rebuild)
                    const panel = document.getElementById('ds-site-panel');
                    if (panel) {
                         UI.clear(panel);
                         DS_CONFIG.settings.customSites.forEach(s => {
                            const btn = UI.el('div', {
                                className: `ds-site-item ${s.value === DS_CONFIG.settings.lastAiSite ? 'active' : ''}`,
                                title: s.value
                            }, s.name);
                            btn.addEventListener('click', () => {
                                DS_CONFIG.settings.lastAiSite = s.value;
                                GM_setValue('ds_last_ai_site', s.value);
                                panel.querySelectorAll('.ds-site-item').forEach(el => el.classList.remove('active'));
                                btn.classList.add('active');
                                const curBtn = document.getElementById('ds-cur-site-btn');
                                if (curBtn) curBtn.firstChild.textContent = s.name;
                                panel.classList.remove('active');
                            });
                            panel.appendChild(btn);
                        });
                    }
                }

                if (cfgDisabled) {
                    const sites = cfgDisabled.value.split('\n').map(s => s.trim()).filter(s => s !== '');
                    DS_CONFIG.settings.disabledSites = sites;
                    GM_setValue('ds_disabled_sites', sites);
                }

                GM_setValue('ds_api_key', DS_CONFIG.settings.apiKey); GM_setValue('ds_custom_prompts', DS_CONFIG.settings.customPrompts);
                renderCustomButtons();
                document.getElementById('ds-config-panel').style.display = 'none';
                document.getElementById('ds-vertical-toolbar').classList.remove('ds-edit-mode'); // 退出编辑模式
            }
            else if (targetId === 'ds-send') {
                const el = document.getElementById('ds-input'); if (!el) return; const val = el.value.trim();
                if (val) { if (DS_CONFIG.runtime.activeTab !== 'docked') switchTab('docked'); askAI(val,"","chat"); el.value = ""; }
            }
            else if (targetId === 'ds-jump-btn') {
                // New Jump Logic: No Auto-Fill Transfer
                const el = document.getElementById('ds-input');
                const text = el ? el.value.trim() : "";

                // Copy to clipboard if enabled (使用统一的 autoCopy 设置)
                if (DS_CONFIG.settings.autoCopy && text) {
                    copyToClip(text);
                }

                // Simply open the tab
                window.open(DS_CONFIG.settings.lastAiSite, '_blank');
            }
            else if (targetId === 'ds-summary-btn') { // New Handler Location (legacy check, though we have explicit onclick now)
                  const content = getArticleContent();
                  askAI(`请对以下文章内容进行结构化总结:\n\n${content}`, "", "summary");
            }
            else if (targetId === 'ds-confirm-yes') {
                // Clear Local Storage (Current Page Highlights)
                Object.keys(localStorage).forEach(k => {
                    if(k.startsWith(DS_CONFIG.consts.STORAGE_PREFIX) || k === DS_CONFIG.consts.VOCAB_CACHE_KEY) localStorage.removeItem(k);
                });
                // Clear Global Storage (Cross-Page History & Cache)
                GM_setValue(DS_CONFIG.consts.GLOBAL_STORAGE_KEY, '[]');
                GM_setValue(DS_CONFIG.consts.GLOBAL_DICT_CACHE_KEY, '{}');
                location.reload();
            }
            else if (targetId === 'ds-confirm-no') { document.getElementById('ds-confirm-modal').style.display = 'none'; }
        });

        document.getElementById('ds-chat-log')?.addEventListener('contextmenu', (e) => { e.preventDefault(); if (DS_CONFIG.runtime.abortCtrl) { DS_CONFIG.runtime.abortCtrl.abort(); } });
        document.getElementById('ds-input')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') { if (!e.shiftKey) { e.preventDefault(); document.getElementById('ds-send').click(); } } });
        document.getElementById('ds-resizer')?.addEventListener('mousedown', (e) => {
            e.preventDefault(); e.stopPropagation();
            const resizer = document.getElementById('ds-resizer');
            if (resizer) resizer.dataset.resizing = 'true';
        });
        document.addEventListener('selectionchange', () => { if (!DS_CONFIG.settings.autoImport) return; const sel = window.getSelection().toString().trim(); const el = document.getElementById('ds-input'); if (sel && sel.length < 500 && el) {
            if (el.value !== sel) {
                  el.value = sel;
                  el.selectionStart = el.selectionEnd = el.value.length;
                  el.scrollTop = el.scrollHeight;
            }
            DS_CONFIG.runtime.lastSelection.word = sel; try { DS_CONFIG.runtime.lastSelection.context = window.getSelection().getRangeAt(0).commonAncestorContainer.parentElement.innerText; } catch(e) {DS_CONFIG.runtime.lastSelection.context = "";} } });
    }

    document.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return;


        const inSidebar = DOM.sidebar && DOM.sidebar.contains(e.target); const inPopup = DOM.popup && DOM.popup.style.display !== 'none' && DOM.popup.contains(e.target);
        if (!inSidebar && !inPopup) { if (DOM.popup && DOM.popup.style.display !== 'none' && isTopWindow) { DOM.popup.style.display = 'none'; DS_CONFIG.runtime.currentPopupTrigger = null; clearAllInlineTranslations(); } }
        if (e.altKey) { DS_CONFIG.runtime.sidebarLockUntil = Date.now() + 600; }
    });

    document.addEventListener('mousedown', e => {
        const targetHighlight = e.target.closest(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`);
        if (e.altKey && e.button === 0 && !targetHighlight) {
            e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); clearAllInlineTranslations();
            let sel = window.getSelection().toString().trim(); let container = e.target;
            const renderTrans = (nodeToInsertAfter) => {
                const transSpan = UI.el('div', { className: 'web-inline-trans' }, 'DeepSeek 思考中...');
                nodeToInsertAfter.after(transSpan);
                return transSpan;
            };
            if (sel.length > 0) {
                  const selection = window.getSelection(); if (!selection.rangeCount) return;
                  const range = selection.getRangeAt(0); const sourceSpan = document.createElement('span'); sourceSpan.className = 'web-trans-source-highlight'; sourceSpan.appendChild(range.extractContents()); range.insertNode(sourceSpan); selection.removeAllRanges(); const transSpan = renderTrans(sourceSpan);
                  if (DS_CONFIG.runtime.inlineAbortCtrl) DS_CONFIG.runtime.inlineAbortCtrl.abort();
                  DS_CONFIG.runtime.inlineAbortCtrl = new AbortController();
                  streamDeepSeekInline(sel, transSpan, DS_CONFIG.runtime.inlineAbortCtrl.signal);
            }
            else {
                while (container && container !== document.body && window.getComputedStyle(container).display === 'inline') container = container.parentElement; const text = container.textContent.trim();
                if (text.length > 2) {
                    const tempSpan = UI.el('div', { className: 'web-inline-trans' }, 'DeepSeek 思考中...');
                    container.appendChild(tempSpan);
                    if (DS_CONFIG.runtime.inlineAbortCtrl) DS_CONFIG.runtime.inlineAbortCtrl.abort();
                    DS_CONFIG.runtime.inlineAbortCtrl = new AbortController();
                    streamDeepSeekInline(text, tempSpan, DS_CONFIG.runtime.inlineAbortCtrl.signal);
                }
            }
            return;
        }
        if (targetHighlight && e.button === 0 && !e.altKey) {
            e.preventDefault(); e.stopPropagation();
            const text = targetHighlight.textContent.trim(); const parentBlock = targetHighlight.closest('p, div, li, h1, h2, h3') || targetHighlight.parentElement; const context = parentBlock ? parentBlock.innerText : text;
            if (DS_CONFIG.settings.autoCopy) {
                copyToClip(text);
            }
            if (isSidebarVisible() && isTopWindow) { const input = document.getElementById('ds-input'); if(input) { input.value = text; } }
            const isWord = (text.split(/\s+/).length <= 3 && text.length < 30);
            if (isWord) {
                if (isTopWindow) {
                    if (DS_CONFIG.settings.isDocked) {
                        showSmartPopup(text, targetHighlight, context);
                        return;
                    }
                    if (DOM.popup.style.display === 'flex' && DS_CONFIG.runtime.currentPopupTrigger === targetHighlight) { DOM.popup.style.display = 'none'; DS_CONFIG.runtime.currentPopupTrigger = null; return; }
                    showSmartPopup(text, targetHighlight, context);
                }
            }
            else {
                  clearAllInlineTranslations();
                  const transSpan = UI.el('div', { className: 'web-inline-trans' }, 'DeepSeek 思考中...');
                  if (targetHighlight.nextSibling) targetHighlight.parentNode.insertBefore(transSpan, targetHighlight.nextSibling); else targetHighlight.parentNode.appendChild(transSpan);
                  if (DS_CONFIG.runtime.inlineAbortCtrl) DS_CONFIG.runtime.inlineAbortCtrl.abort();
                  DS_CONFIG.runtime.inlineAbortCtrl = new AbortController();
                  streamDeepSeekInline(text, transSpan, DS_CONFIG.runtime.inlineAbortCtrl.signal);
            }
        }
    }, true);
}

function addToGlobalHistory(word, context) {
    if (!word) return;
    let history = [];
    try {
        history = JSON.parse(GM_getValue(DS_CONFIG.consts.GLOBAL_STORAGE_KEY, '[]'));
    } catch(e) { history = []; }

    const existingIndex = history.findIndex(i => i.word === word);
    if (existingIndex > -1) {
        history.splice(existingIndex, 1);
    }
    history.push({ word, context: "", date: Date.now() });
   GM_setValue(DS_CONFIG.consts.GLOBAL_STORAGE_KEY, JSON.stringify(history));
    if (DS_CONFIG.runtime.activeTab === 'history') renderHistoryMenu();
}

function deleteFromHistory(word, elementRef = null) {
    const doDelete = () => {
        // 1. 移除全局历史记录
        let history = [];
        try { history = JSON.parse(GM_getValue(DS_CONFIG.consts.GLOBAL_STORAGE_KEY, '[]')); } catch(e) { history = []; }
        const newHistory = history.filter(i => i.word !== word);
        GM_setValue(DS_CONFIG.consts.GLOBAL_STORAGE_KEY, JSON.stringify(newHistory));

        // 2. 实时移除当前网页上的高亮 (新增逻辑)
        let removedFromDom = false;
        document.querySelectorAll(`.${DS_CONFIG.consts.HIGHLIGHT_CLASS}`).forEach(el => {
            if (el.textContent.trim() === word) {
                const p = el.parentNode;
                while (el.firstChild) p.insertBefore(el.firstChild, el);
                el.remove();
                removedFromDom = true;
            }
        });

        // 3. 更新当前页面的本地存储缓存
        if (removedFromDom) {
            saveHighlights();
        } else {
            // 即使当前DOM没找到(可能不在当前视口或页面),也要确保从Local Storage移除
            const saved = JSON.parse(localStorage.getItem(DS_CONFIG.consts.STORAGE_KEY) || '[]');
            const newSaved = saved.filter(h => h.text !== word);
            localStorage.setItem(DS_CONFIG.consts.STORAGE_KEY, JSON.stringify(newSaved));
        }

        renderHistoryMenu();
    };

    if (elementRef) {
        softDelete(elementRef, doDelete);
    } else {
        doDelete();
    }
}

// Added export functionality
function exportHistoryToTxt() {
    let history = [];
    try {
        history = JSON.parse(GM_getValue(DS_CONFIG.consts.GLOBAL_STORAGE_KEY, '[]'));
    } catch(e) { history = []; }

    if (history.length === 0) {
        alert('没有历史记录可导出');
        return;
    }

    history.sort((a, b) => {
        if (DS_CONFIG.settings.historySort === 'time_desc') return b.date - a.date;
        if (DS_CONFIG.settings.historySort === 'time_asc') return a.date - b.date;
        if (DS_CONFIG.settings.historySort === 'alpha_asc') return a.word.localeCompare(b.word);
        if (DS_CONFIG.settings.historySort === 'alpha_desc') return b.word.localeCompare(a.word);
        return 0;
    });

    const content = history.map(h => h.word).join('\n');

    const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `DeepSeek_Words_${new Date().toISOString().slice(0,10)}.txt`;
    a.click();
    URL.revokeObjectURL(url);
}

function renderHistoryMenu() {
    const container = document.getElementById('ds-history-content');
    if (!container) return;

    const currentScroll = document.getElementById('ds-history-log')?.scrollTop || 0;

    UI.clear(container);

    const controls = UI.el('div', { style: { padding: '10px', borderBottom: '1px solid var(--ds-border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: 'var(--ds-menu-bg)' } }, [
        // Left side: Sort icons
        UI.el('div', { style: { display: 'flex', gap: '10px' } }, [
            UI.el('span', {
                className: 'ds-sort-btn ' + (DS_CONFIG.settings.historySort.includes('time') ? 'active' : ''),
                style: { cursor: 'pointer', opacity: DS_CONFIG.settings.historySort.includes('time') ? 1 : 0.5 },
                onclick: () => {
                    DS_CONFIG.settings.historySort = DS_CONFIG.settings.historySort === 'time_desc' ? 'time_asc' : 'time_desc';
                    GM_setValue('ds_history_sort', DS_CONFIG.settings.historySort);
                    DS_CONFIG.runtime.historyLimit = 50; // 切换排序时重置显示数量
                    renderHistoryMenu();
                }
            }, DS_CONFIG.settings.historySort === 'time_asc' ? '📅 ⬆️' : '📅 ⬇️'),
            UI.el('span', {
                className: 'ds-sort-btn ' + (DS_CONFIG.settings.historySort.includes('alpha') ? 'active' : ''),
                style: { cursor: 'pointer', opacity: DS_CONFIG.settings.historySort.includes('alpha') ? 1 : 0.5 },
                onclick: () => {
                    DS_CONFIG.settings.historySort = DS_CONFIG.settings.historySort === 'alpha_asc' ? 'alpha_desc' : 'alpha_asc';
                    GM_setValue('ds_history_sort', DS_CONFIG.settings.historySort);
                    DS_CONFIG.runtime.historyLimit = 50; // 切换排序时重置显示数量
                    renderHistoryMenu();
                }
            }, DS_CONFIG.settings.historySort === 'alpha_desc' ? '🔤 ⬇️' : '🔤 ⬆️')
        ]),
        // Right side: Export and Delete
        UI.el('div', { style: { display: 'flex', gap: '10px' } }, [
             UI.el('span', {
                className: 'ds-sort-btn',
                style: { cursor: 'pointer', opacity: 0.7 },
                title: '导出为TXT',
                onclick: exportHistoryToTxt
            }, '📤'),
            UI.el('span', {
                className: 'ds-sort-btn',
                style: { cursor: 'pointer', opacity: 0.7 },
                title: '清空所有缓存',
                onclick: () => { document.getElementById('ds-confirm-modal').style.display = 'flex'; }
            }, '🗑️')
        ])
    ]);
    container.appendChild(controls);

    const listContainer = UI.el('div', { id: 'ds-history-log' });

    // 从 GM_getValue 读取全局数据
    let history = [];
    try {
        history = JSON.parse(GM_getValue(DS_CONFIG.consts.GLOBAL_STORAGE_KEY, '[]'));
    } catch(e) { history = []; }

    // 获取当前页面所有高亮的词,用于判断是否显示跳转按钮
    const localHighlights = new Set(JSON.parse(localStorage.getItem(DS_CONFIG.consts.STORAGE_KEY) || '[]').map(h => h.text));

    history.sort((a, b) => {
        if (DS_CONFIG.settings.historySort === 'time_desc') return b.date - a.date;
        if (DS_CONFIG.settings.historySort === 'time_asc') return a.date - b.date;
        if (DS_CONFIG.settings.historySort === 'alpha_asc') return a.word.localeCompare(b.word);
        if (DS_CONFIG.settings.historySort === 'alpha_desc') return b.word.localeCompare(a.word);
        return 0;
    });

    // === 分页逻辑开始 ===
    if (!DS_CONFIG.runtime.historyLimit) DS_CONFIG.runtime.historyLimit = 50;
    const totalCount = history.length;
    const visibleHistory = history.slice(0, DS_CONFIG.runtime.historyLimit);
    // === 分页逻辑结束 ===

    if (visibleHistory.length === 0) {
        listContainer.appendChild(UI.el('div', { style: { textAlign: 'center', color: '#666', marginTop: '20px', fontSize: '13px' } }, "暂无历史记录"));
    } else {
        visibleHistory.forEach(item => {
             // 检查该词是否在当前页面高亮中
             const isLocal = localHighlights.has(item.word);
             const children = [
                UI.el('span', { className: 'web-menu-word' }, item.word)
             ];

             // 如果是当前页面的词,添加跳转图标
             if (isLocal) {
                 children.push(UI.el('span', {
                     className: 'web-menu-jump',
                     title: '跳转到文中位置',
                     style: { marginLeft: 'auto', marginRight: '4px' } // 靠右对齐
                 }, '📍'));
             }

             const div = UI.el('div', { className: 'web-menu-item', 'data-word': item.word }, [
                UI.el('div', { className: 'web-menu-header' }, children),
                UI.el('div', { className: 'web-menu-trans', style: { display: 'none' } })
             ]);
             listContainer.appendChild(div);
        });

        // === 载入更多按钮 ===
        if (totalCount > DS_CONFIG.runtime.historyLimit) {
            const remaining = totalCount - DS_CONFIG.runtime.historyLimit;
            const loadMoreBtn = UI.el('div', {
                style: {
                    textAlign: 'center', padding: '12px', cursor: 'pointer',
                    color: '#3a7bd5', fontSize: '13px', fontWeight: 'bold',
                    borderTop: '1px solid var(--ds-border)', marginTop: '5px',
                    transition: 'background 0.2s', borderRadius: '4px'
                },
                className: 'ds-load-more-btn',
                onclick: function() {
                    this.textContent = "载入中...";
                    DS_CONFIG.runtime.historyLimit += 50; // 增加50个
                    renderHistoryMenu();
                }
            }, `⬇️ 载入更多 (剩余 ${remaining} 个)`);

            // 鼠标悬停效果
            loadMoreBtn.onmouseover = () => loadMoreBtn.style.background = 'var(--ds-hover-bg)';
            loadMoreBtn.onmouseout = () => loadMoreBtn.style.background = 'transparent';

            listContainer.appendChild(loadMoreBtn);
        }
    }

    container.appendChild(listContainer);

    // 恢复滚动位置
    if (currentScroll > 0) {
        setTimeout(() => {
            if (listContainer) listContainer.scrollTop = currentScroll;
        }, 0);
    }
}

function initTimedTasks() { setInterval(() => { if (!DS_CONFIG.runtime.isRestoring && isSidebarVisible()) { applySavedHighlights(); } }, 2000); }
async function init() {
    const currentUrl = window.location.href;
    const currentHost = window.location.hostname;
    const isDisabled = DS_CONFIG.settings.disabledSites.some(site => {
        if (!site) return false;
        return currentUrl.toLowerCase().includes(site.toLowerCase()) || currentHost.toLowerCase().includes(site.toLowerCase());
    });

    if (isDisabled) {
        console.log('[AI语言学习专家] 当前网站在黑名单中,脚本已停止运行。');
        return;
    }

    buildUI();
    bindEvents();
    initTimedTasks();

}
init();
})();