BIMBO BOX💋 beta

像素级适配 JanitorAI、Artworks 与 Pornworks!采用原生属性描述符劫持技术,彻底击穿任何前端框架的 Token 拦截与表单死锁!

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

// ==UserScript==
// @name         BIMBO BOX💋 beta
// @namespace    https://greasyfork.org/zh-CN/users/1593463-nander
// @version      2.0.5
// @description  像素级适配 JanitorAI、Artworks 与 Pornworks!采用原生属性描述符劫持技术,彻底击穿任何前端框架的 Token 拦截与表单死锁!
// @author       Nander
// @license      CC BY-NC-SA 4.0
// @match        *://*.janitorai.com/*
// @match        *://localhost/*
// @match        *://*.artworks.ai/*
// @match        *://*.pornworks.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    let isVisible = GM_getValue("panel_visible", true);
    let savedLeft = GM_getValue("float_left", null);
    let savedTop = GM_getValue("float_top", null);

    let lastActiveField = null;

    // --- 🎨 UI 维持 v185 经典官方暗夜舱 + 催眠粉形态 ---
    const style = document.createElement('style');
    style.innerHTML = `
        .cha-helper-float {
            position: fixed; width: 186px; background: #383331; border: 1.5px solid #ff4fa7;
            border-radius: 10px; padding: 9px;
            box-shadow: 0 6px 24px rgba(255, 79, 167, 0.15), inset 0 1px 1px rgba(255, 255, 255, 0.1);
            font-family: -apple-system, BlinkMacSystemFont, sans-serif;
            display: flex; flex-direction: column; gap: 7px; box-sizing: border-box; user-select: none;
            z-index: 999999; height: auto !important; will-change: top, left;
        }
        .gme-mini-head { display: flex; justify-content: space-between; align-items: center; font-size: 9.5px; color: #ff4fa7; font-weight: 900; line-height: 1; cursor: move; letter-spacing: 0.3px; padding: 0 1px; }
        .gme-close { cursor: pointer; font-size: 12px; color: #ff4fa7; }
        .gme-close:hover { color: #ffffff; }
        .gme-mini-body { display: flex; justify-content: space-between; width: 168px; box-sizing: border-box; font-size: 9px; color: #bdafa8; font-family: monospace; font-weight: bold; background: #1f1c1b; padding: 3px 6px; border-radius: 4px; box-shadow: inset 0 1.5px 4px rgba(0, 0, 0, 0.6), 0 0.5px 0px rgba(255, 79, 167, 0.15); }
        .gme-kiss-box { position: relative; width: 168px; height: 244px; }
        .gme-kiss-area { width: 168px; height: 244px; background: #1f1c1b; border: none; border-radius: 5px; color: #ff99cc; font-size: 11px; padding: 7px; outline: none; box-sizing: border-box; resize: none; line-height: 1.45; position: relative; z-index: 2; box-shadow: inset 0 2.5px 6px rgba(0, 0, 0, 0.65), 0 0.5px 0px rgba(255, 79, 167, 0.15); }
        .gme-kiss-area:focus { box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.7), 0 0 6px rgba(255, 79, 167, 0.3); }
        .gme-kiss-placeholder { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; color: #bd538f; font-weight: 800; font-size: 10.5px; text-align: center; line-height: 1.6; pointer-events: none; z-index: 1; box-sizing: border-box; padding: 20px; }
        .gme-kiss-btn { width: 168px; height: 22px; background: linear-gradient(180deg, #ff4fa7 0%, #cc297b 100%); border: none; border-radius: 4px; color: #fff; font-size: 11px; font-weight: 900; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 1px 2px rgba(0,0,0,0.2); gap: 4px; box-sizing: border-box; }
        .gme-kiss-btn:hover { background: linear-gradient(180deg, #ff75bd 0%, #ff4fa7 100%); }
        .gme-kiss-btn:active { transform: scale(0.97); }
        .cha-helper-body { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4.5px; width: 168px; box-sizing: border-box; }
        .cha-mini-btn { color: #ffffff; border: none; padding: 4px 0; border-radius: 4px; cursor: pointer; font-size: 10px; font-weight: 800; text-align: center; outline: none; position: relative; text-transform: uppercase; box-shadow: 0 1px 2px rgba(0,0,0,0.2); transition: all 0.12s cubic-bezier(0.2, 0.8, 0.2, 1); }
        .btn-write { background: linear-gradient(180deg, #ff4fa7 0%, #cc297b 100%); color: #ffffff; grid-column: span 2; padding: 5.5px 0; font-size: 11px; font-weight: 900; }
        .btn-write:hover { background: linear-gradient(180deg, #ff75bd 0%, #ff4fa7 100%); box-shadow: 0 0 7px rgba(255, 79, 167, 0.3); }
        .btn-suck { background: linear-gradient(180deg, #00bfff 0%, #0088cc 100%); color: #ffffff; }
        .btn-suck:hover { box-shadow: 0 0 6px rgba(0, 191, 255, 0.4); }
        .btn-down { background: linear-gradient(180deg, #9955ff 0%, #7722ff 100%); color: #ffffff; }
        .btn-down:hover { box-shadow: 0 0 6px rgba(153, 85, 255, 0.4); }
        .btn-toggle-panel { grid-column: span 2; background: #262120; color: #ff6ea7; padding: 3.5px 0; font-size: 8.5px; font-weight: bold; border: 1px solid rgba(255, 79, 167, 0.2); }
        .btn-toggle-panel:hover { background: #ff4fa7; color: #ffffff; }
        .btn-toggle-panel.active { background: #ff4fa7; color: #ffffff; }
        .btn-clear { background: #522126; grid-column: span 2; font-size: 8.5px; opacity: 0.7; }
        .btn-clear:hover { opacity: 1; background: #6b2d34; }
        .cha-mini-btn:active { transform: scale(0.96) translateY(0.5px); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3); }
        .cha-big-panel { position: fixed; z-index: 1000000; background: #383331; border: 1.5px solid #ff4fa7; border-radius: 10px; display: none; flex-direction: column; padding: 12px; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.85); box-sizing: border-box; width: 650px; height: 500px; }
        .cha-big-header { display: flex; justify-content: space-between; margin-bottom: 8px; color: #ff4fa7; font-weight: 900; font-size: 12px; letter-spacing: 0.5px; }
        .cha-nfo-area { flex: 1; background: #1f1c1b; color: #ff99cc; border: none; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-size: 13px; line-height: 1.5; padding: 10px; resize: none; border-radius: 6px; outline: none; box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.7); }
        #cha-toast { position: fixed; top: 40%; left: 50%; transform: translate(-50%, -50%); background: #383331; color: #ff75bd; border: 1.2px solid #ff4fa7; padding: 5px 14px; border-radius: 20px; z-index: 10000001; font-size: 11px; font-weight: bold; opacity: 0; transition: opacity 0.12s ease; pointer-events: none; }
    `;
    document.head.appendChild(style);

    const toast = document.createElement('div');
    toast.id = 'cha-toast';
    document.body.appendChild(toast);

    function showToast(message) {
        toast.innerText = message; toast.style.opacity = '1';
        setTimeout(() => { toast.style.opacity = '0'; }, 1100);
    }

    // --- 默认面板配置桩 ---
    const defaultNfo = `[CHARACTER INFO]
--------------------------------------------------
NAME     :
BOT_NAME :
TAGS     :
--------------------------------------------------
[BIO]


[PERSONALITY]


[SCENARIO]


[FIRST_MSG]


[DIALOGS]


--------------------------------------------------
[POSITIVE_PROMPT]

[NEGATIVE_PROMPT]
--------------------------------------------------`;

    // --- 构建控制中心 ---
    const floatWin = document.createElement('div');
    floatWin.className = 'cha-helper-float';

    if (savedLeft !== null && savedTop !== null) {
        floatWin.style.left = savedLeft; floatWin.style.top = savedTop;
    } else {
        floatWin.style.left = `${window.innerWidth - 202}px`; floatWin.style.top = `${window.innerHeight - 450}px`;
    }
    floatWin.style.display = isVisible ? 'flex' : 'none';

    floatWin.innerHTML = `
        <div class="gme-mini-head" id="chaDragHdr">
            <span>BIMBO BOX v2.0.5 💋</span>
            <span class="gme-close" id="chaCloseBtn">×</span>
        </div>
        <div class="gme-mini-body">
            <span id="chaStatsCount">0字</span>
            <span id="chaStatsSize">0K</span>
        </div>
        <div class="gme-kiss-box">
            <div class="gme-kiss-placeholder" id="kissPlace">💋<br>(Ctrl+V)<br>注入灵魂...</div>
            <textarea class="gme-kiss-area" id="kissInput"></textarea>
        </div>
        <button class="gme-kiss-btn" id="kissBtn">💋 QUICK INJECT</button>
        <div class="cha-helper-body">
            <button class="cha-mini-btn btn-write" id="btnWrite">Inject ✨</button>
            <button class="cha-mini-btn btn-suck" id="btnSuck">Suck 🐾</button>
            <button class="cha-mini-btn btn-down" id="btnDown">Save 💾</button>
            <button class="cha-mini-btn btn-toggle-panel" id="btnPanel">OPEN EDITOR</button>
            <button class="cha-mini-btn btn-clear" id="btnClear">Reset Data</button>
        </div>
    `;
    document.body.appendChild(floatWin);

    const bigPanel = document.createElement('div');
    bigPanel.className = 'cha-big-panel';
    bigPanel.innerHTML = `
        <div class="cha-big-header">
            <span>💖 BIMBO MASTER CONSOLE</span>
            <span style="cursor:pointer;color:#ff4fa7" id="chaBigClose">[CLOSE]</span>
        </div>
        <textarea class="cha-nfo-area" id="nfoTextArea"></textarea>
    `;
    document.body.appendChild(bigPanel);

    function repositionBigPanel() {
        bigPanel.style.left = `${(window.innerWidth - 650) / 2}px`; bigPanel.style.top = `${(window.innerHeight - 500) / 2}px`;
    }
    repositionBigPanel();
    window.addEventListener('resize', repositionBigPanel);

    const nfoArea = document.getElementById('nfoTextArea');
    const kissInput = document.getElementById('kissInput');
    const kissPlace = document.getElementById('kissPlace');

    if(!nfoArea.value) nfoArea.value = defaultNfo;

    function getTimestamp() {
        const now = new Date(); const pad = (n) => String(n).padStart(2, '0');
        return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
    }

    document.addEventListener('focusin', (e) => {
        const el = e.target;
        if (el && el.id !== 'kissInput' && el.id !== 'nfoTextArea') {
            if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.getAttribute('contenteditable') === 'true' || el.className.includes('raw')) {
                lastActiveField = el;
            }
        }
    });

    function syncDataFlow(source) {
        if (source === 'big') {
            kissInput.value = nfoArea.value;
        } else if (source === 'mini') {
            nfoArea.value = kissInput.value;
        }

        if (kissInput.value && kissInput.value !== defaultNfo) {
            kissPlace.style.display = 'none';
        } else {
            kissPlace.style.display = 'flex';
        }

        const text = nfoArea.value;
        let sizeStr = (new Blob([text]).size / 1024).toFixed(1) + "K";
        document.getElementById('chaStatsCount').innerText = text.length + "字";
        document.getElementById('chaStatsSize').innerText = sizeStr;
    }

    nfoArea.addEventListener('input', () => syncDataFlow('big'));
    kissInput.addEventListener('input', () => syncDataFlow('mini'));

    function getOfficialElements() {
        return {
            titleInput: document.getElementById('name'),
            chatNameInput: document.getElementById('chat_name'),
            bioEditor: document.querySelector('.tiptap.ProseMirror'),
            personalityTextarea: document.getElementById('personality'),
            scenarioTextarea: document.getElementById('scenario'),
            firstMsgTextarea: document.getElementById('first_messages.0') || document.querySelector('[id^="first_messages."]'),
            dialogsTextarea: document.getElementById('example_dialogs'),
            artworksPrompt: document.querySelector('textarea[formcontrolname="prompt"]'),
            artworksNegativePrompt: document.querySelector('textarea[formcontrolname="negativePrompt"]')
        };
    }

    // --- 🔥 核心黑魔法:降维打击级别原生属性劫持函数 ---
    function forceActivateElement(el, val) {
        if (!el) return;

        try {
            // 获取原生输入框的 value 属性 setter
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
                el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
                'value'
            ).set;

            // 强行把新值塞给底层的 DOM 节点,绕过前端框架对普通 .value = xxx 的拦截
            nativeInputValueSetter.call(el, val);
        } catch (e) {
            // 后备方案
            el.value = val;
        }

        el.focus();

        // 🚀 第一波:标准 DOM 事件,冒泡全开
        el.dispatchEvent(new Event('focus', { bubbles: true }));
        el.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));

        // 🚀 第二波:模拟一个虚拟键盘敲击,彻底骗过 Angular/React 框架的状态追踪
        el.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'a', code: 'KeyA' }));
        el.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: 'a', code: 'KeyA' }));

        // 🚀 第三波:失去焦点,强迫底层的 Token 计算器和校验引擎同步数据
        el.dispatchEvent(new Event('blur', { bubbles: true }));
    }

    // --- 💋 QUICK INJECT 快捷注入 ---
    document.getElementById('kissBtn').addEventListener('click', () => {
        const val = kissInput.value;
        if(!val || val === defaultNfo) { showToast('💋 NOTHING TO INJECT'); return; }

        const els = getOfficialElements();
        let targetEl = lastActiveField || document.activeElement;

        if (els.artworksPrompt) targetEl = els.artworksPrompt;

        if (!targetEl || targetEl.id === 'kissInput' || targetEl.id === 'nfoTextArea') {
            showToast('🚨 CLICK A FIELD FIRST'); return;
        }

        let injectVal = val;

        if (val.includes('[POSITIVE_PROMPT]')) {
            const posBlock = val.match(/\[POSITIVE_PROMPT\]([\s\S]*?)(\[NEGATIVE_PROMPT\]|---|$)/);
            if (posBlock) injectVal = posBlock[1].trim();
        } else if (val.includes('[CHARACTER INFO]')) {
            const nameMatch = val.match(/NAME\s*:\s*(.*)/);
            injectVal = nameMatch ? nameMatch[1].trim() : val;
        }

        if (targetEl.getAttribute('contenteditable') === 'true' || targetEl.classList.contains('ProseMirror')) {
            targetEl.focus();
            document.execCommand('insertText', false, injectVal);
            targetEl.dispatchEvent(new Event('input', { bubbles: true }));
            showToast('💋 KISSED INTO BIO');
        } else {
            forceActivateElement(targetEl, injectVal);
            showToast('💋 PROMPT KISSED & ACTIVATED');
        }
    });

    // --- 大面板精准注入 (Inject) ---
    function writeFormData() {
        const text = nfoArea.value;
        const els = getOfficialElements();

        if (els.artworksPrompt || els.artworksNegativePrompt) {
            let hasInjected = false;

            const posBlock = text.match(/\[POSITIVE_PROMPT\]([\s\S]*?)(\[NEGATIVE_PROMPT\]|---|$)/);
            if (posBlock && posBlock[1].trim() && els.artworksPrompt) {
                forceActivateElement(els.artworksPrompt, posBlock[1].trim());
                hasInjected = true;
            }

            const negBlock = text.match(/\[NEGATIVE_PROMPT\]([\s\S]*?)(---|#|$)/);
            if (negBlock && negBlock[1].trim() && els.artworksNegativePrompt) {
                forceActivateElement(els.artworksNegativePrompt, negBlock[1].trim());
                hasInjected = true;
            }

            if (!hasInjected && els.artworksPrompt) {
                forceActivateElement(els.artworksPrompt, text.trim());
            }

            showToast('🎨 MATRIX INJECTED & WOKEN');
            return;
        }

        const nameMatch = text.match(/NAME\s*:\s*(.*)/);
        const botNameMatch = text.match(/BOT_NAME\s*:\s*(.*)/);
        const bioBlock = text.match(/\[BIO\]([\s\S]*?)(\[PERSONALITY\]|\[SCENARIO\]|\[FIRST_MSG\]|\[DIALOGS\]|---|$)/);
        const personalityBlock = text.match(/\[PERSONALITY\]([\s\S]*?)(\[BIO\]|\[SCENARIO\]|\[FIRST_MSG\]|\[DIALOGS\]|---|$)/);
        const scenarioBlock = text.match(/\[SCENARIO\]([\s\S]*?)(\[BIO\]|\[PERSONALITY\]|\[FIRST_MSG\]|\[DIALOGS\]|---|$)/);
        const firstMsgBlock = text.match(/\[FIRST_MSG\]([\s\S]*?)(\[BIO\]|\[PERSONALITY\]|\[SCENARIO\]|\[DIALOGS\]|---|$)/);
        const dialogsBlock = text.match(/\[DIALOGS\]([\s\S]*?)(\[BIO\]|\[PERSONALITY\]|\[SCENARIO\]|\[FIRST_MSG\]|---|$)/);

        if (nameMatch) forceActivateElement(els.titleInput, nameMatch[1].trim());
        if (botNameMatch) forceActivateElement(els.chatNameInput, botNameMatch[1].trim());

        if (els.bioEditor && bioBlock && bioBlock[1].trim()) {
            els.bioEditor.focus();
            els.bioEditor.innerHTML = '';
            document.execCommand('insertText', false, bioBlock[1].trim());
            els.bioEditor.dispatchEvent(new Event('input', { bubbles: true }));
            els.bioEditor.blur();
        }

        if (personalityBlock) forceActivateElement(els.personalityTextarea, personalityBlock[1].trim());
        if (scenarioBlock) forceActivateElement(els.scenarioTextarea, scenarioBlock[1].trim());
        if (firstMsgBlock) forceActivateElement(els.firstMsgTextarea, firstMsgBlock[1].trim());
        if (dialogsBlock) forceActivateElement(els.dialogsTextarea, dialogsBlock[1].trim());

        showToast('✨ ALL FIELDS INJECTED & WOKEN');
    }

    // --- 表单数据吸取 (Suck) ---
    function suckFormData() {
        const els = getOfficialElements();

        if (els.artworksPrompt || els.artworksNegativePrompt) {
            let currentTxt = nfoArea.value;
            let pVal = els.artworksPrompt ? els.artworksPrompt.value : '';
            let nVal = els.artworksNegativePrompt ? els.artworksNegativePrompt.value : '';

            let updatedTxt = currentTxt
                .replace(/\[POSITIVE_PROMPT\][\s\S]*?(\[NEGATIVE_PROMPT\]|---|$)/, `[POSITIVE_PROMPT]\n${pVal}\n\n$1`)
                .replace(/\[NEGATIVE_PROMPT\][\s\S]*?(---|#|$)/, `[NEGATIVE_PROMPT]\n${nVal}\n\n$1`);

            nfoArea.value = updatedTxt;
            syncDataFlow('big');
            showToast('🐾 MATRIX ABSORBED');
            return;
        }

        const tagElements = Array.from(document.querySelectorAll('div[class*="react-select__multi-value__label"]'));
        const tags = tagElements.map(el => el.textContent.trim()).join(', ');
        let bioContent = els.bioEditor ? els.bioEditor.innerText.trim() : '';

        let generatedPrompts = `masterpiece, best quality, 1girl, solo, ${tags ? tags + ', ' : ''}detailed facial features`;

        let nfoText = `[CHARACTER INFO]\n--------------------------------------------------\n`;
        nfoText += `NAME     : ${els.titleInput ? els.titleInput.value : ''}\n`;
        nfoText += `BOT_NAME : ${els.chatNameInput ? els.chatNameInput.value : ''}\n`;
        nfoText += `TAGS     : ${tags}\n`;
        nfoText += `--------------------------------------------------\n`;
        nfoText += `[BIO]\n${bioContent}\n\n`;
        nfoText += `[PERSONALITY]\n${els.personalityTextarea ? els.personalityTextarea.value : ''}\n\n`;
        nfoText += `[SCENARIO]\n${els.scenarioTextarea ? els.scenarioTextarea.value : ''}\n\n`;
        nfoText += `[FIRST_MSG]\n${els.firstMsgTextarea ? els.firstMsgTextarea.value : ''}\n\n`;
        nfoText += `[DIALOGS]\n${els.dialogsTextarea ? els.dialogsTextarea.value : ''}\n\n`;
        nfoText += `--------------------------------------------------\n`;
        nfoText += `[POSITIVE_PROMPT]\n${generatedPrompts}\n\n`;
        nfoText += `[NEGATIVE_PROMPT]\nlowres, bad anatomy, bad hands, text, error, worst quality, low quality\n`;
        nfoText += `--------------------------------------------------`;

        nfoArea.value = nfoText;
        syncDataFlow('big');
        showToast('💋 DATA ABSORBED');
    }

    // --- 事件绑定与面板控制 ---
    document.getElementById('btnWrite').addEventListener('click', writeFormData);
    document.getElementById('btnSuck').addEventListener('click', suckFormData);
    document.getElementById('btnDown').addEventListener('click', () => {
        const blob = new Blob([nfoArea.value], { type: 'text/markdown;charset=utf-8' });
        const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url;
        const nameMatch = nfoArea.value.match(/NAME\s*:\s*(.*)/);
        const charName = (nameMatch && nameMatch[1].trim()) ? nameMatch[1].trim() : 'character';
        a.download = `${charName}-${getTimestamp()}.md`; a.click(); URL.revokeObjectURL(url);
        showToast('💝 SWEET BACKUP SAVED');
    });

    document.getElementById('btnClear').addEventListener('click', () => {
        if(confirm('🚨 ERASE EVERYTHING?')) { nfoArea.value = defaultNfo; syncDataFlow('big'); showToast('🗑️ WIPED'); }
    });

    const btnPanel = document.getElementById('btnPanel');
    function toggleBigPanel() {
        if(bigPanel.style.display === 'none' || bigPanel.style.display === '') {
            bigPanel.style.display = 'flex'; btnPanel.classList.add('active'); syncDataFlow('big');
        } else {
            bigPanel.style.display = 'none'; btnPanel.classList.remove('active');
        }
    }
    btnPanel.addEventListener('click', toggleBigPanel);
    document.getElementById('chaBigClose').addEventListener('click', toggleBigPanel);

    document.getElementById('chaCloseBtn').addEventListener('click', () => {
        floatWin.style.display = 'none'; isVisible = false; GM_setValue("panel_visible", false);
    });

    function centerFloatWindow() {
        const left = (window.innerWidth - floatWin.offsetWidth) / 2; const top = (window.innerHeight - floatWin.offsetHeight) / 2;
        floatWin.style.left = `${left}px`; floatWin.style.top = `${top}px`;
        GM_setValue("float_left", `${left}px`); GM_setValue("float_top", `${top}px`);
    }

    GM_registerMenuCommand("显示/隐藏 助手小窗", () => { isVisible = !isVisible; floatWin.style.display = isVisible ? 'flex' : 'none'; GM_setValue("panel_visible", isVisible); });
    GM_registerMenuCommand("🔄 恢复默认位置并居中", () => { isVisible = true; floatWin.style.display = 'flex'; GM_setValue("panel_visible", true); centerFloatWindow(); showToast('RESET CENTERED'); });

    // --- 拖拽引擎 ---
    const dragHdr = document.getElementById('chaDragHdr');
    let isDragging = false; let offsetX = 0, offsetY = 0;
    dragHdr.addEventListener('mousedown', (e) => {
        isDragging = true; const rect = floatWin.getBoundingClientRect();
        offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top;
        document.body.style.userSelect = 'none'; e.preventDefault();
    });
    window.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        let left = e.clientX - offsetX; let top = e.clientY - offsetY;
        const maxLeft = window.innerWidth - floatWin.offsetWidth; const maxTop = window.innerHeight - floatWin.offsetHeight;
        left = Math.max(0, Math.min(left, maxLeft)); top = Math.max(0, Math.min(top, maxTop));
        floatWin.style.left = `${left}px`; floatWin.style.top = `${top}px`;
    });
    window.addEventListener('mouseup', () => {
        if (isDragging) { isDragging = false; document.body.style.userSelect = ''; GM_setValue("float_left", floatWin.style.left); GM_setValue("float_top", floatWin.style.top); }
    });

    syncDataFlow('big');
})();