BIMBO BOX💋 beta

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

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

// ==UserScript==
// @name         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');
})();