тШ░

ЁЯОпЁЯОи Chess.com Custom Arrows

тЬи Personalize suas setas com controle total sobre cores, gradientes, espessura, opacidade e tamanho da ponta. тЩЯя╕ПЁЯФе

рд╣реА рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ Tampermonkey, Greasemonkey рдХрд┐рдВрд╡рд╛ Violentmonkey рдпрд╛рд╕рд╛рд░рдЦреЗ рдПрдХреНрд╕реНрдЯреЗрдВрд╢рди рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

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

рд╣реА рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ Tampermonkey рдХрд┐рдВрд╡рд╛ Violentmonkey рдпрд╛рд╕рд╛рд░рдЦреЗ рдПрдХреНрд╕реНрдЯреЗрдВрд╢рди рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓..

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

рд╣реА рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ Tampermonkey рдпрд╛рд╕рд╛рд░рдЦреЗ рдПрдХреНрд╕реНрдЯреЗрдВрд╢рди рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓..

рд╣реА рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ рдПрдХ рдпреБрдЭрд░ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдПрдХреНрд╕реНрдЯреЗрдВрд╢рди рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

(рдорд╛рдЭреНрдпрд╛рдХрдбреЗ рдЖрдзреАрдЪ рдпреБрдЭрд░ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдЖрд╣реЗ, рдорд▓рд╛ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░реВ рджреНрдпрд╛!)

рд╣реА рд╕реНрдЯрд╛рдИрд▓ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ Stylus рд╕рд╛рд░рдЦреЗ рдПрдХреНрд╕реНрдЯреЗрдВрд╢рди рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

рд╣реА рд╕реНрдЯрд╛рдИрд▓ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ Stylus рд╕рд╛рд░рдЦреЗ рдПрдХреНрд╕реНрдЯреЗрдВрд╢рди рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

рд╣реА рд╕реНрдЯрд╛рдИрд▓ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ Stylus рд╕рд╛рд░рдЦреЗ рдПрдХреНрд╕реНрдЯреЗрдВрд╢рди рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

рд╣реА рд╕реНрдЯрд╛рдИрд▓ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ рдПрдХ рдпреБрдЭрд░ рд╕реНрдЯрд╛рдИрд▓ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

рд╣реА рд╕реНрдЯрд╛рдИрд▓ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ рдПрдХ рдпреБрдЭрд░ рд╕реНрдЯрд╛рдИрд▓ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

рд╣реА рд╕реНрдЯрд╛рдИрд▓ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ рдПрдХ рдпреБрдЭрд░ рд╕реНрдЯрд╛рдИрд▓ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓.

(рдорд╛рдЭреНрдпрд╛рдХрдбреЗ рдЖрдзреАрдЪ рдпреБрдЭрд░ рд╕реНрдЯрд╛рдИрд▓ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдЖрд╣реЗ, рдорд▓рд╛ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░реВ рджреНрдпрд╛!)

// ==UserScript==
// @name         ЁЯОпЁЯОи Chess.com Custom Arrows
// @namespace    http://tampermonkey.net/
// @version      7.2
// @description  тЬи Personalize suas setas com controle total sobre cores, gradientes, espessura, opacidade e tamanho da ponta. тЩЯя╕ПЁЯФе
// @author       aerus15
// @match        *://*.chess.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // ==========================================
    // 1. ESTADO GLOBAL E SALVAMENTO (LOCALSTORAGE)
    // ==========================================
    const savedGrad = localStorage.getItem('vfx-arrow-use-grad');

    const STATE = {
        thickness: parseFloat(localStorage.getItem('vfx-arrow-thick')) || 15,
        useGradient: savedGrad !== null ? savedGrad === 'true' : true,
        color1: localStorage.getItem('vfx-arrow-c1') || '#ff0000',
        color2: localStorage.getItem('vfx-arrow-c2') || '#ffff00',
        color3: localStorage.getItem('vfx-arrow-c3') || '#00ff00',
        solidColor: localStorage.getItem('vfx-arrow-solid') || '#ffaa00',
        opacity: parseFloat(localStorage.getItem('vfx-arrow-opacity')) || 0.8,

        headSize: parseFloat(localStorage.getItem('vfx-arrow-hsize')) || 1.0,
        headWidth: parseFloat(localStorage.getItem('vfx-arrow-hwidth')) || 1.0,
        headLength: parseFloat(localStorage.getItem('vfx-arrow-hlength')) || 1.0,

        drawnShapes: [],
        currentShape: null,
        isDragging: false,
        boardElement: null,
        svgOverlay: null,
        squareSize: 0,
        isFlipped: false
    };

    // ==========================================
    // 2. CSS PARA ANULAR SETAS NATIVAS E BLINDAR CONTAINER
    // ==========================================
    const cssOverride = `
        /* Oculta APENAS caminhos e pol├нgonos das setas nativas (preserva os c├нrculos e quadrados de outras extens├╡es) */
        wc-chess-board .arrows path,
        wc-chess-board .arrows line,
        wc-chess-board .arrows polyline,
        wc-chess-board .arrows polygon,
        [data-cy="arrows"] path,
        [data-cy="arrows"] line,
        [data-cy="arrows"] polyline,
        [data-cy="arrows"] polygon,
        svg.arrows path,
        svg.arrows line,
        svg.arrows polyline,
        svg.arrows polygon {
            display: none !important;
            opacity: 0 !important;
            visibility: hidden !important;
            pointer-events: none !important;
        }

        #vfx-custom-arrows-container {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            /* Z-index extremo para garantir que fique em cima de TODAS as pe├зas e marca├з├╡es do outro script */
            z-index: 2147483650 !important;
        }

        #vfx-master-panel {
            position: fixed;
            right: 15px;
            bottom: 55px; /* Movido ~1cm para cima */
            background: rgba(15, 15, 20, 0.95);
            border: 1px solid #00e5ff;
            border-radius: 8px;
            padding: 15px;
            z-index: 2147483647;
            font-family: system-ui, sans-serif;
            color: white;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 15px rgba(0, 229, 255, 0.2);
            backdrop-filter: blur(10px);
            display: flex;
            flex-direction: column;
            gap: 12px;
            width: 320px;
            max-height: 85vh;
            overflow-y: auto;
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            transform-origin: bottom right;
        }

        #vfx-master-panel::-webkit-scrollbar { width: 6px; }
        #vfx-master-panel::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); }
        #vfx-master-panel::-webkit-scrollbar-thumb { background: #00e5ff; border-radius: 3px; }

        #vfx-master-panel.minimized {
            transform: scale(0);
            pointer-events: none;
        }

        #vfx-close-btn {
            position: absolute;
            top: 10px;
            right: 12px;
            color: #ff4444;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            user-select: none;
            line-height: 1;
            transition: 0.2s;
        }
        #vfx-close-btn:hover {
            color: #ff0000;
            transform: scale(1.2);
        }

        .vfx-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .vfx-label {
            font-size: 11px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            color: #ccc;
            flex: 1;
        }
        .vfx-input {
            background: #222;
            border: 1px solid #444;
            color: white;
            border-radius: 4px;
            padding: 4px;
            outline: none;
        }
        input[type="color"].vfx-input {
            height: 28px;
            width: 40px;
            padding: 0;
            cursor: pointer;
        }
        input[type="range"].vfx-slider {
            width: 110px;
            accent-color: #00e5ff;
            cursor: pointer;
            margin-right: 8px;
        }
        .vfx-val-display {
            width: 30px;
            text-align: right;
            font-size: 11px;
            font-weight: bold;
            color: #00e5ff;
            font-family: monospace;
        }
        .vfx-toggle {
            cursor: pointer;
            background: #333;
            border: 1px solid #555;
            padding: 6px 10px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: bold;
            transition: 0.2s;
            text-align: center;
            width: 100%;
            margin-top: 5px;
            user-select: none;
        }
        .vfx-toggle.active {
            background: #00e5ff;
            color: #000;
            border-color: #00e5ff;
        }
        .vfx-divider {
            height: 1px;
            background: rgba(255,255,255,0.1);
            margin: 5px 0;
        }
        #vfx-floating-btn {
            position: fixed;
            right: 15px;
            bottom: 55px; /* Movido ~1cm para cima */
            width: 45px;
            height: 45px;
            background: #111;
            border: 2px solid #00e5ff;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 20px;
            cursor: pointer;
            z-index: 2147483648;
            box-shadow: 0 0 15px rgba(0,229,255,0.4);
            user-select: none;
            transition: all 0.2s ease;
        }
        #vfx-floating-btn:hover {
            transform: scale(1.1);
            background: #222;
        }
    `;

    const styleEl = document.createElement('style');
    styleEl.textContent = cssOverride;
    (document.head || document.documentElement).appendChild(styleEl);

    // ==========================================
    // 3. MATEM├БTICA DO TABULEIRO
    // ==========================================
    const FILES = ['a','b','c','d','e','f','g','h'];

    function getBoardState() {
        if (!STATE.boardElement) return;
        const rect = STATE.boardElement.getBoundingClientRect();
        STATE.squareSize = rect.width / 8;
        STATE.isFlipped = STATE.boardElement.classList.contains('flipped');
    }

    function pointToSquare(x, y) {
        if (!STATE.boardElement || STATE.squareSize === 0) return null;
        let fileIdx = Math.floor(x / STATE.squareSize);
        let rankIdx = Math.floor(y / STATE.squareSize);

        if (fileIdx < 0) fileIdx = 0;
        if (fileIdx > 7) fileIdx = 7;
        if (rankIdx < 0) rankIdx = 0;
        if (rankIdx > 7) rankIdx = 7;

        if (STATE.isFlipped) {
            fileIdx = 7 - fileIdx;
        } else {
            rankIdx = 7 - rankIdx;
        }
        return FILES[fileIdx] + (rankIdx + 1);
    }

    function squareToPoint(sq) {
        if (!STATE.boardElement || STATE.squareSize === 0 || !sq) return {x: 0, y: 0};
        const fileIdx = FILES.indexOf(sq[0]);
        const rankIdx = parseInt(sq[1]) - 1;

        let visualFile, visualRank;

        if (STATE.isFlipped) {
            visualFile = 7 - fileIdx;
            visualRank = rankIdx;
        } else {
            visualFile = fileIdx;
            visualRank = 7 - rankIdx;
        }

        return {
            x: (visualFile + 0.5) * STATE.squareSize,
            y: (visualRank + 0.5) * STATE.squareSize
        };
    }

    // ==========================================
    // 4. MOTOR DE DESENHO (SVG)
    // ==========================================
    function setupSVG() {
        if (!STATE.boardElement) return;

        let container = STATE.boardElement.querySelector('#vfx-custom-arrows-container');
        if (!container) {
            container = document.createElement('div');
            container.id = 'vfx-custom-arrows-container';
            STATE.boardElement.appendChild(container);
        }

        // SVG Container - A opacidade ├й aplicada DIRETAMENTE AQUI
        container.innerHTML = `
            <svg id="vfx-main-svg" width="100%" height="100%" style="overflow: visible; opacity: ${STATE.opacity};">
                <defs>
                    <linearGradient id="vfx-triple-grad" x1="0%" y1="0%" x2="100%" y2="100%">
                        <stop id="vfx-stop-1" offset="0%" stop-color="${STATE.color1}" />
                        <stop id="vfx-stop-2" offset="50%" stop-color="${STATE.color2}" />
                        <stop id="vfx-stop-3" offset="100%" stop-color="${STATE.color3}" />
                    </linearGradient>
                </defs>
                <g id="vfx-shapes-layer"></g>
            </svg>
        `;
        STATE.svgOverlay = container.querySelector('#vfx-shapes-layer');
    }

    function drawAll() {
        if (!STATE.svgOverlay) return;
        getBoardState();
        STATE.svgOverlay.innerHTML = '';
        STATE.drawnShapes.forEach(shape => renderShape(shape));
    }

    function renderShape(shape) {
        // Ignora cliques simples na mesma casa
        if (shape.start === shape.end) return;

        const p1 = squareToPoint(shape.start);
        const p2 = squareToPoint(shape.end);
        const strokeColor = STATE.useGradient ? 'url(#vfx-triple-grad)' : STATE.solidColor;
        const T = parseFloat(STATE.thickness);

        const file1 = FILES.indexOf(shape.start[0]);
        const rank1 = parseInt(shape.start[1]);
        const file2 = FILES.indexOf(shape.end[0]);
        const rank2 = parseInt(shape.end[1]);

        const dx = Math.abs(file2 - file1);
        const dy = Math.abs(rank2 - rank1);
        const isKnight = (dx === 1 && dy === 2) || (dx === 2 && dy === 1);

        const baseHeadSize = Math.max(15, T * 2.5) * STATE.headSize;
        const headLength = baseHeadSize * STATE.headLength;
        const headWidth = baseHeadSize * STATE.headWidth;

        let pathD = '';
        let arrowAngle = 0;
        let finalP2 = { ...p2 };

        if (isKnight) {
            let cornerSq = (dy > dx) ? (shape.start[0] + shape.end[1]) : (shape.end[0] + shape.start[1]);
            const pc = squareToPoint(cornerSq);

            arrowAngle = Math.atan2(p2.y - pc.y, p2.x - pc.x);

            const retraction = headLength - Math.min(T/2, headLength * 0.2);
            finalP2.x = p2.x - retraction * Math.cos(arrowAngle);
            finalP2.y = p2.y - retraction * Math.sin(arrowAngle);

            pathD = `M ${p1.x} ${p1.y} L ${pc.x} ${pc.y} L ${finalP2.x} ${finalP2.y}`;
        } else {
            arrowAngle = Math.atan2(p2.y - p1.y, p2.x - p1.x);

            const retraction = headLength - Math.min(T/2, headLength * 0.2);
            finalP2.x = p2.x - retraction * Math.cos(arrowAngle);
            finalP2.y = p2.y - retraction * Math.sin(arrowAngle);

            pathD = `M ${p1.x} ${p1.y} L ${finalP2.x} ${finalP2.y}`;
        }

        const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path.setAttribute('d', pathD);
        path.setAttribute('class', 'vfx-shaft');

        // BLINDAGEM: Aplicando propriedades inline com !important para vencer o outro script
        path.style.setProperty('fill', 'none', 'important');
        path.style.setProperty('stroke', strokeColor, 'important');
        path.style.setProperty('stroke-width', T + 'px', 'important');
        path.style.setProperty('stroke-linecap', 'round', 'important');
        path.style.setProperty('stroke-linejoin', 'round', 'important');

        const tipX = p2.x;
        const tipY = p2.y;
        const baseX = p2.x - headLength * Math.cos(arrowAngle);
        const baseY = p2.y - headLength * Math.sin(arrowAngle);

        const leftX = baseX - (headWidth/2) * Math.sin(arrowAngle);
        const leftY = baseY + (headWidth/2) * Math.cos(arrowAngle);
        const rightX = baseX + (headWidth/2) * Math.sin(arrowAngle);
        const rightY = baseY - (headWidth/2) * Math.cos(arrowAngle);

        const head = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
        head.setAttribute('points', `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`);
        head.setAttribute('class', 'vfx-head');

        // BLINDAGEM PARA A CABE├ЗA DA SETA
        head.style.setProperty('fill', strokeColor, 'important');
        head.style.setProperty('stroke', strokeColor, 'important');
        head.style.setProperty('stroke-width', (T * 0.1) + 'px', 'important');
        head.style.setProperty('stroke-linejoin', 'round', 'important');

        STATE.svgOverlay.appendChild(path);
        STATE.svgOverlay.appendChild(head);
    }

    // ==========================================
    // 5. EVENTOS (SEM PREVIEW EM TEMPO REAL)
    // ==========================================
    function attachBoardEvents() {
        if (!STATE.boardElement) return;

        STATE.boardElement.addEventListener('contextmenu', e => e.preventDefault(), { capture: true });

        STATE.boardElement.addEventListener('pointerdown', (e) => {
            if (e.button === 2) {
                e.preventDefault();
                getBoardState();
                const rect = STATE.boardElement.getBoundingClientRect();
                const startSq = pointToSquare(e.clientX - rect.left, e.clientY - rect.top);

                if (startSq) {
                    STATE.isDragging = true;
                    STATE.currentShape = { start: startSq, end: startSq };
                }
            } else if (e.button === 0) {
                if (STATE.drawnShapes.length > 0) {
                    STATE.drawnShapes = [];
                    drawAll();
                }
            }
        }, { capture: true });

        window.addEventListener('pointermove', (e) => {
            if (!STATE.isDragging || !STATE.currentShape || !STATE.boardElement) return;

            const rect = STATE.boardElement.getBoundingClientRect();
            const currSq = pointToSquare(e.clientX - rect.left, e.clientY - rect.top);

            if (currSq && currSq !== STATE.currentShape.end) {
                STATE.currentShape.end = currSq;
            }
        }, { capture: true });

        window.addEventListener('pointerup', (e) => {
            if (e.button === 2 && STATE.isDragging) {
                STATE.isDragging = false;

                if (STATE.currentShape && STATE.currentShape.start !== STATE.currentShape.end) {
                    const existsIdx = STATE.drawnShapes.findIndex(
                        s => s.start === STATE.currentShape.start && s.end === STATE.currentShape.end
                    );

                    if (existsIdx > -1) {
                        STATE.drawnShapes.splice(existsIdx, 1);
                    } else {
                        STATE.drawnShapes.push(STATE.currentShape);
                    }
                }
                STATE.currentShape = null;
                drawAll(); // S├У AGORA DESENHAMOS A SETA
            }
        }, { capture: true });
    }

    // ==========================================
    // 6. UI MASTER COM SAVE E LOAD E BOT├ГO FECHAR
    // ==========================================
    function initUI() {
        if (document.getElementById('vfx-master-panel')) return;

        const container = document.createElement('div');
        container.innerHTML = `
            <div id="vfx-floating-btn">ЁЯев</div>
            <div id="vfx-master-panel" class="minimized">
                <div id="vfx-close-btn">тЬХ</div>
                <div style="text-align: center; color: #00e5ff; font-weight: 800; font-size: 14px; margin-bottom: 5px;">VFX ARROWS 7.2</div>

                <div class="vfx-divider"></div>

                <div class="vfx-row">
                    <span class="vfx-label">Espessura</span>
                    <input type="range" id="vfx-thick-slider" class="vfx-slider" min="0.5" max="40" step="0.5" value="${STATE.thickness}">
                    <span id="vfx-thick-val" class="vfx-val-display">${STATE.thickness}</span>
                </div>

                <div class="vfx-row">
                    <span class="vfx-label">Opacidade</span>
                    <input type="range" id="vfx-op-slider" class="vfx-slider" min="0.1" max="1" step="0.05" value="${STATE.opacity}">
                    <span id="vfx-op-val" class="vfx-val-display">${STATE.opacity}</span>
                </div>

                <div class="vfx-divider"></div>
                <div style="font-size: 10px; color: #00e5ff; font-weight: bold; margin-bottom: 5px; text-transform: uppercase;">Cabe├зa da Seta</div>

                <div class="vfx-row">
                    <span class="vfx-label">Tamanho Global</span>
                    <input type="range" id="vfx-hsize-slider" class="vfx-slider" min="0.2" max="3" step="0.1" value="${STATE.headSize}">
                    <span id="vfx-hsize-val" class="vfx-val-display">${STATE.headSize}</span>
                </div>

                <div class="vfx-row">
                    <span class="vfx-label">Largura (Gorda)</span>
                    <input type="range" id="vfx-hwidth-slider" class="vfx-slider" min="0.2" max="3" step="0.1" value="${STATE.headWidth}">
                    <span id="vfx-hwidth-val" class="vfx-val-display">${STATE.headWidth}</span>
                </div>

                <div class="vfx-row">
                    <span class="vfx-label">Comprimento (Pontuda)</span>
                    <input type="range" id="vfx-hlength-slider" class="vfx-slider" min="0.2" max="3" step="0.1" value="${STATE.headLength}">
                    <span id="vfx-hlength-val" class="vfx-val-display">${STATE.headLength}</span>
                </div>

                <div class="vfx-divider"></div>

                <div id="vfx-mode-btn" class="vfx-toggle ${STATE.useGradient ? 'active' : ''}">${STATE.useGradient ? 'MODO: GRADIENTE' : 'MODO: COR ├ЪNICA'}</div>

                <div id="vfx-solid-config" class="vfx-row" style="display: ${STATE.useGradient ? 'none' : 'flex'}; margin-top: 10px;">
                    <span class="vfx-label">Cor Principal</span>
                    <input type="color" id="vfx-c-solid" class="vfx-input" value="${STATE.solidColor}">
                </div>

                <div id="vfx-grad-config" style="display: ${STATE.useGradient ? 'flex' : 'none'}; flex-direction: column; gap: 8px; margin-top: 10px;">
                    <div class="vfx-row">
                        <span class="vfx-label">Cor In├нcio</span>
                        <input type="color" id="vfx-c1" class="vfx-input" value="${STATE.color1}">
                    </div>
                    <div class="vfx-row">
                        <span class="vfx-label">Cor Meio</span>
                        <input type="color" id="vfx-c2" class="vfx-input" value="${STATE.color2}">
                    </div>
                    <div class="vfx-row">
                        <span class="vfx-label">Cor Fim</span>
                        <input type="color" id="vfx-c3" class="vfx-input" value="${STATE.color3}">
                    </div>
                </div>
            </div>
        `;
        document.body.appendChild(container);

        const bindSlider = (id, stateKey, storeKey) => {
            const slider = document.getElementById(id);
            const valEl = document.getElementById(id.replace('-slider', '-val'));
            slider.oninput = (e) => {
                STATE[stateKey] = parseFloat(e.target.value);
                valEl.innerText = e.target.value;
                localStorage.setItem(storeKey, e.target.value);

                if (stateKey === 'opacity') {
                    const svgMain = document.getElementById('vfx-main-svg');
                    if (svgMain) svgMain.style.opacity = STATE.opacity;
                } else {
                    drawAll();
                }
            };
        };

        bindSlider('vfx-thick-slider', 'thickness', 'vfx-arrow-thick');
        bindSlider('vfx-op-slider', 'opacity', 'vfx-arrow-opacity');
        bindSlider('vfx-hsize-slider', 'headSize', 'vfx-arrow-hsize');
        bindSlider('vfx-hwidth-slider', 'headWidth', 'vfx-arrow-hwidth');
        bindSlider('vfx-hlength-slider', 'headLength', 'vfx-arrow-hlength');

        const btn = document.getElementById('vfx-floating-btn');
        const panel = document.getElementById('vfx-master-panel');
        const modeBtn = document.getElementById('vfx-mode-btn');
        const solidConf = document.getElementById('vfx-solid-config');
        const gradConf = document.getElementById('vfx-grad-config');
        const closeBtn = document.getElementById('vfx-close-btn');

        let panelOpen = false;

        btn.onclick = () => {
            panelOpen = !panelOpen;
            panel.classList.toggle('minimized', !panelOpen);
            btn.style.transform = panelOpen ? 'rotate(90deg)' : 'rotate(0deg)';
        };

        closeBtn.onclick = () => {
            panelOpen = false;
            panel.classList.add('minimized');
            btn.style.transform = 'rotate(0deg)';
        };

        modeBtn.onclick = () => {
            STATE.useGradient = !STATE.useGradient;
            modeBtn.classList.toggle('active', STATE.useGradient);
            modeBtn.innerText = STATE.useGradient ? 'MODO: GRADIENTE' : 'MODO: COR ├ЪNICA';
            solidConf.style.display = STATE.useGradient ? 'none' : 'flex';
            gradConf.style.display = STATE.useGradient ? 'flex' : 'none';
            localStorage.setItem('vfx-arrow-use-grad', STATE.useGradient);
            drawAll();
        };

        const updateGradientDef = () => {
            const s1 = document.getElementById('vfx-stop-1');
            const s2 = document.getElementById('vfx-stop-2');
            const s3 = document.getElementById('vfx-stop-3');
            if (s1 && s2 && s3) {
                s1.setAttribute('stop-color', STATE.color1);
                s2.setAttribute('stop-color', STATE.color2);
                s3.setAttribute('stop-color', STATE.color3);
            }
        };

        document.getElementById('vfx-c-solid').oninput = (e) => {
            STATE.solidColor = e.target.value;
            localStorage.setItem('vfx-arrow-solid', STATE.solidColor);
            drawAll();
        };

        ['c1', 'c2', 'c3'].forEach((id, idx) => {
            document.getElementById(`vfx-${id}`).oninput = (e) => {
                STATE[`color${idx+1}`] = e.target.value;
                localStorage.setItem(`vfx-arrow-${id}`, e.target.value);
                updateGradientDef();
            };
        });
    }

    // ==========================================
    // 7. INICIALIZA├З├ГO
    // ==========================================
    function initSystem() {
        const board = document.querySelector('wc-chess-board') || document.querySelector('.cg-board');
        if (board && !STATE.boardElement) {
            STATE.boardElement = board;
            setupSVG();
            attachBoardEvents();
            initUI();

            const resizeObserver = new ResizeObserver(() => requestAnimationFrame(drawAll));
            resizeObserver.observe(board);

            const classObserver = new MutationObserver(() => drawAll());
            classObserver.observe(board, { attributes: true, attributeFilter: ['class'] });
        }
    }

    const bootInterval = setInterval(() => {
        initSystem();
        if (STATE.boardElement) clearInterval(bootInterval);
    }, 500);

    document.addEventListener('DOMContentLoaded', initSystem);

})();