🎲🧊 Chess.com 3D Board

🔄🎲 Transforme o tabuleiro do Chess.com em 3D! 🎨 Personalize o verso do Tabuleiro, dê zoom, rotação e diversos efeitos visuais. 🚀🌟 (funciona melhor em firefox)

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         🎲🧊 Chess.com 3D Board
// @namespace    http://tampermonkey.net/
// @version      13.6
// @description  🔄🎲 Transforme o tabuleiro do Chess.com em 3D! 🎨 Personalize o verso do Tabuleiro, dê zoom, rotação e diversos efeitos visuais. 🚀🌟  (funciona melhor em firefox) 
// @author       aerus15
// @match        *://*.chess.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    window.addEventListener('error', function(e) {
        if (e.message && (
            e.message.toLowerCase().includes('piece') ||
            e.message.toLowerCase().includes('board') ||
            e.message.toLowerCase().includes('bounds') ||
            e.message.toLowerCase().includes('node') ||
            e.message.toLowerCase().includes('style')
        )) {
            e.preventDefault();
            e.stopPropagation();
        }
    }, true);

    const loadSavedState = () => {
        try {
            const saved = localStorage.getItem('chess3dState_v13');
            return saved ? JSON.parse(saved) : {};
        } catch (e) {
            return {};
        }
    };

    const savedState = loadSavedState();

    let state = {
        bgMode: savedState.bgMode || 'solid',
        colorSolid: savedState.colorSolid || '#333333',
        g1: savedState.g1 || '#ff0055',
        g2: savedState.g2 || '#5500ff',
        g3: savedState.g3 || '#00aaff',
        imgBase64: savedState.imgBase64 || '',
        reflect: savedState.reflect !== undefined ? savedState.reflect : true,
        allowInteraction: savedState.allowInteraction !== undefined ? savedState.allowInteraction : false,
        centerBoard: savedState.centerBoard !== undefined ? savedState.centerBoard : false,
        scale: savedState.scale !== undefined ? savedState.scale : 1,
        thorEnabled: savedState.thorEnabled !== undefined ? savedState.thorEnabled : false,
        thorDuration: savedState.thorDuration || 3.0,
        thorDistance: savedState.thorDistance || 5000,
        thorSpin: savedState.thorSpin || 5,
        rx: 0,
        ry: 0,
        tx: 0,
        ty: 0,
        locked: false
    };

    let saveTimeout = null;
    const saveSettings = () => {
        if (saveTimeout) clearTimeout(saveTimeout);
        saveTimeout = setTimeout(() => {
            const stateToSave = {
                bgMode: state.bgMode,
                colorSolid: state.colorSolid,
                g1: state.g1,
                g2: state.g2,
                g3: state.g3,
                imgBase64: state.imgBase64,
                reflect: state.reflect,
                allowInteraction: state.allowInteraction,
                centerBoard: state.centerBoard,
                scale: state.scale,
                thorEnabled: state.thorEnabled,
                thorDuration: state.thorDuration,
                thorDistance: state.thorDistance,
                thorSpin: state.thorSpin
            };
            localStorage.setItem('chess3dState_v13', JSON.stringify(stateToSave));
        }, 300);
    };

    const style = document.createElement('style');
    style.innerHTML = `
        #chess-3d-toggle-btn {
            position: fixed; bottom: 20px; left: 20px; background: rgba(15, 15, 15, 0.7); border: 1px solid #444; color: #fff;
            width: 38px; height: 38px; border-radius: 50%; cursor: pointer; z-index: 9999999; font-size: 18px;
            display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
            transition: transform 0.2s ease, background 0.2s ease; backdrop-filter: blur(5px); padding: 0;
            will-change: transform;
        }
        #chess-3d-toggle-btn:hover { background: rgba(45, 45, 45, 0.95); transform: scale(1.05); }
        #chess-3d-toggle-btn:active { transform: scale(0.95); }

        #chess-3d-gui {
            position: fixed; bottom: 65px; left: 20px; width: 260px; background: rgba(15, 15, 15, 0.95);
            border: 1px solid #333; border-radius: 10px; padding: 12px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.95);
            z-index: 9999999; color: #eaeaea; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            backdrop-filter: blur(10px); user-select: none; display: none; flex-direction: column; gap: 10px;
            box-sizing: border-box; transition: opacity 0.2s ease; max-height: 80vh; overflow-y: auto;
            will-change: opacity, transform;
        }
        #chess-3d-gui.show { display: flex; animation: fadeIn 0.2s ease-out; }
        #chess-3d-gui::-webkit-scrollbar { width: 6px; }
        #chess-3d-gui::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
        @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }

        .chess-3d-control { display: flex; flex-direction: column; gap: 6px; }
        .chess-3d-control label { font-size: 11px; color: #ccc; display: flex; justify-content: space-between; align-items: center; cursor: pointer; font-weight: 500; }
        .chess-3d-control select, .chess-3d-control input[type="file"], .chess-3d-control input[type="range"] {
            background: #1a1a1a; color: #ffffff; border: 1px solid #555; border-radius: 4px; padding: 4px 6px; font-size: 11px; width: 100%; margin-top: 3px; outline: none; box-sizing: border-box;
        }
        .color-row { display: flex; gap: 6px; margin-top: 3px; }
        .color-row input[type="color"] { flex: 1; height: 24px; border: 1px solid #444; background: #1a1a1a; cursor: pointer; padding: 1px; border-radius: 3px; }
        input[type=checkbox] { accent-color: #739b53; width: 14px; height: 14px; cursor: pointer; }
        .status-indicator { font-size: 10px; text-align: center; color: #ff4444; font-weight: bold; min-height: 12px; letter-spacing: 0.5px; margin-top: 4px; }

        .board-layout-bg, .layout-board-bg, .bg-shadow, div[class*="board-layout-bg"], #board-layout-main::before, #board-layout-main::after,
        .board-layout-main::before, .board-layout-main::after, body::before, body::after, .hover-square {
            display: none !important; background-image: none !important; background-color: transparent !important;
            opacity: 0 !important; visibility: hidden !important; box-shadow: none !important; backdrop-filter: none !important;
        }

        .coordinates, .coordinate, .ranks, .rank, .files, .file {
            visibility: visible !important;
            opacity: 1 !important;
            z-index: 99995 !important;
            pointer-events: none !important;
            background-color: transparent !important;
            background-image: none !important;
            box-shadow: none !important;
            border: none !important;
        }

        .piece, .highlight, .hover-square {
            -webkit-backface-visibility: hidden !important;
            backface-visibility: hidden !important;
            outline: none !important;
            box-shadow: none !important;
            border: none !important;
        }

        .square {
            -webkit-backface-visibility: hidden !important;
            backface-visibility: hidden !important;
            outline: none !important;
            box-shadow: none !important;
            border: none !important;
            pointer-events: none !important;
        }

        .board-layout-chessboard, #board-layout-chessboard, .board-layout-center, .layout-board, .board-layout-main {
            background-color: transparent !important; background-image: none !important; box-shadow: none !important; border: none !important;
        }
        #board-layout-chessboard::before, #board-layout-chessboard::after, .board-layout-chessboard::before, .board-layout-chessboard::after,
        chess-board::before, chess-board::after {
            display: none !important; background-image: none !important; content: none !important; opacity: 0 !important;
        }
        .board-background-image, .board-background, .board-bg, svg.board-background { display: none !important; opacity: 0 !important; }

        chess-board {
            pointer-events: auto !important; touch-action: none !important; overflow: visible !important;
            transform: translateZ(0) !important;
            -webkit-font-smoothing: subpixel-antialiased !important;
            outline: none !important;
        }

        .piece { pointer-events: auto !important; z-index: 100000 !important; }
        svg { pointer-events: none !important; }
        .chess-3d-overlay.blocking { pointer-events: none !important; display: none !important; }

        .chess-board-3d-wrapper {
            perspective: 2500px !important; transform-style: preserve-3d !important;
            background: transparent !important; box-shadow: none !important;
        }
        .chess-board-3d-target {
            transform-style: preserve-3d !important; will-change: transform;
            transform-origin: center center !important; position: relative !important; z-index: 100 !important; transition: none !important;
        }

        .chess-3d-back {
            position: absolute !important; top: 0 !important; transform: translateZ(-0.5px) rotateY(180deg) !important;
            pointer-events: none !important; background-size: cover !important; background-position: center !important;
            background-repeat: no-repeat !important; box-shadow: inset 0 0 60px rgba(0,0,0,0.85) !important;
            border-radius: inherit !important; margin: 0 !important; padding: 0 !important; z-index: -1 !important;
            backface-visibility: hidden !important; box-sizing: border-box !important; overflow: hidden !important;
            will-change: background;
        }
        .chess-3d-back::after {
            content: '' !important; position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important;
            background: radial-gradient(circle at var(--bg-x, 50%) var(--bg-y, 50%), rgba(255,255,255,0.9), transparent 50%),
                        conic-gradient(from var(--bg-angle, 0deg) at 50% 50%, #ff007f, #7f00ff, #00ffff, #00ff7f, #ffeb3b, #ff007f) !important;
            mix-blend-mode: color-dodge !important; opacity: var(--reflect-opacity, 0) !important; pointer-events: none !important;
            border-radius: inherit !important; transition: opacity 0.2s ease !important; box-sizing: border-box !important;
            will-change: opacity;
        }
        .chess-3d-overlay { position: absolute !important; inset: 0 !important; z-index: 1000 !important; pointer-events: none !important; }

        body.chess-3d-centered .analysis-tools, body.chess-3d-centered .layout-right, body.chess-3d-centered [data-cy="analysis-panel"],
        body.chess-3d-centered .sidebar, body.chess-3d-centered .game-overview, body.chess-3d-centered .vertical-move-list,
        body.chess-3d-centered #board-layout-sidebar {
            opacity: 0 !important; pointer-events: none !important; z-index: -999 !important; position: absolute !important;
            visibility: hidden !important; display: none !important; width: 0 !important; height: 0 !important;
        }

        /* CORREÇÃO DO SUB-PIXEL (TELA CHEIA): Removido o translate(-50%, -50%) e substituído por inset:0 + margin:auto */
        body.chess-3d-centered .board-layout-main {
            display: flex !important; flex-direction: column !important; justify-content: center !important; align-items: center !important;
            width: max-content !important; height: max-content !important; position: fixed !important;
            inset: 0 !important; margin: auto !important; /* Alinhamento nativo do navegador para forçar pixels inteiros */
            z-index: 9998 !important; background: transparent !important; padding: 0 !important;
            will-change: transform;
        }
        body.chess-3d-centered .board-layout-player-top, body.chess-3d-centered .board-layout-player-bottom {
            width: 100% !important; max-width: 100% !important; display: flex !important; justify-content: space-between !important;
            align-items: center !important; box-sizing: border-box !important; margin: 0 !important; padding: 0 !important;
        }
        body.chess-3d-centered #board-layout-chessboard { margin: 0 !important; }
    `;
    document.head.appendChild(style);

    const dynamicStyle = document.createElement('style');
    dynamicStyle.id = 'chess-3d-dynamic-style';
    document.head.appendChild(dynamicStyle);

    const toggleBtn = document.createElement('button');
    toggleBtn.id = 'chess-3d-toggle-btn';
    toggleBtn.innerHTML = '⚙️';
    toggleBtn.title = "Configurações 3D & Efeitos";
    document.body.appendChild(toggleBtn);

    const gui = document.createElement('div');
    gui.id = 'chess-3d-gui';
    gui.innerHTML = `
        <div class="chess-3d-control">
            <label>Tipo do Fundo (Verso)</label>
            <select id="bg-type">
                <option value="solid">Cor Sólida</option>
                <option value="gradient">Gradiente (3 Cores)</option>
                <option value="image">Imagem Personalizada</option>
            </select>
        </div>
        <div class="chess-3d-control" id="ctrl-solid">
            <div class="color-row"><input type="color" id="color-solid"></div>
        </div>
        <div class="chess-3d-control" id="ctrl-gradient" style="display:none;">
            <div class="color-row"><input type="color" id="color-g1"><input type="color" id="color-g2"><input type="color" id="color-g3"></div>
        </div>
        <div class="chess-3d-control" id="ctrl-image" style="display:none;">
            <input type="file" id="img-upload" accept="image/*">
        </div>
        <div class="chess-3d-control" style="border-top: 1px solid #333; padding-top: 6px; margin-top: 3px;">
            <label><span>Reflexos RTX (Verso)</span><input type="checkbox" id="check-reflect"></label>
        </div>
        <div class="chess-3d-control">
            <label><span>Mover Peças Girado</span><input type="checkbox" id="check-interact"></label>
        </div>
        <div class="chess-3d-control" style="border-top: 1px solid #333; padding-top: 6px; margin-top: 3px;">
            <label><span>Centralizar Tabuleiro</span><input type="checkbox" id="check-center"></label>
        </div>
        <div class="chess-3d-control" style="border-top: 1px solid #333; padding-top: 6px; margin-top: 3px;">
            <label><span style="color: #00ffaa; font-weight:bold;">Animação Thor (Início)</span><input type="checkbox" id="check-thor"></label>
            <div id="thor-settings" style="display:none; flex-direction:column; gap:8px; margin-top:6px; background:#111; padding:6px; border-radius:4px;">
                <label>Duração (Segundos): <span id="val-dur" style="color:#aaa; font-size:9px;"></span></label>
                <input type="range" id="thor-duration" min="0.5" max="10" step="0.5">
                <label>Distância de Origem: <span id="val-dist" style="color:#aaa; font-size:9px;"></span></label>
                <input type="range" id="thor-distance" min="1000" max="25000" step="1000">
                <label>Intensidade do Giro: <span id="val-spin" style="color:#aaa; font-size:9px;"></span></label>
                <input type="range" id="thor-spin" min="1" max="15" step="1">
                <div style="font-size:10px; color:#888; text-align:center;">Dica: Esconde o tabuleiro ao buscar partida. Pressione 'O' para invocar.</div>
            </div>
        </div>
        <div style="font-size:10px; color:#aaa; text-align:center; margin-top:2px;">Scroll do mouse altera tamanho (Zoom)</div>
        <div class="status-indicator" id="lock-status"></div>
    `;
    document.body.appendChild(gui);

    const elements = {
        bgType: document.getElementById('bg-type'),
        ctrlSolid: document.getElementById('ctrl-solid'),
        ctrlGradient: document.getElementById('ctrl-gradient'),
        ctrlImage: document.getElementById('ctrl-image'),
        colorSolid: document.getElementById('color-solid'),
        colorG1: document.getElementById('color-g1'),
        colorG2: document.getElementById('color-g2'),
        colorG3: document.getElementById('color-g3'),
        imgUpload: document.getElementById('img-upload'),
        checkReflect: document.getElementById('check-reflect'),
        checkInteract: document.getElementById('check-interact'),
        checkCenter: document.getElementById('check-center'),
        checkThor: document.getElementById('check-thor'),
        thorSettings: document.getElementById('thor-settings'),
        thorDuration: document.getElementById('thor-duration'),
        thorDistance: document.getElementById('thor-distance'),
        thorSpin: document.getElementById('thor-spin'),
        lockStatus: document.getElementById('lock-status'),
        valDur: document.getElementById('val-dur'),
        valDist: document.getElementById('val-dist'),
        valSpin: document.getElementById('val-spin')
    };

    elements.bgType.value = state.bgMode;
    elements.colorSolid.value = state.colorSolid;
    elements.colorG1.value = state.g1;
    elements.colorG2.value = state.g2;
    elements.colorG3.value = state.g3;
    elements.checkReflect.checked = state.reflect;
    elements.checkInteract.checked = state.allowInteraction;
    elements.checkCenter.checked = state.centerBoard;
    elements.checkThor.checked = state.thorEnabled;

    elements.thorDuration.value = state.thorDuration;
    elements.thorDistance.value = state.thorDistance;
    elements.thorSpin.value = state.thorSpin;
    elements.valDur.innerText = state.thorDuration + 's';
    elements.valDist.innerText = state.thorDistance;
    elements.valSpin.innerText = state.thorSpin;

    if (state.centerBoard) document.body.classList.add('chess-3d-centered');

    let boardEl = null;
    let backEl = null;
    let overlayEl = null;
    let cachedMainLayout = null;
    let cachedBoardParent = null;

    let isRotating = false;
    let isPanning = false;
    let lastX = 0; let lastY = 0;
    let vx = 0; let vy = 0;
    let inertiaFrame = null;

    const rotSensitivity = 0.45;
    const panSensitivity = 1.2;
    const friction = 0.93;

    let isAnimatingThor = false;
    let thorFrameId = null;
    let lastPiecesCount = -1;
    let rAF_pending = false;

    const updateControlsVisibility = () => {
        elements.ctrlSolid.style.display = state.bgMode === 'solid' ? 'block' : 'none';
        elements.ctrlGradient.style.display = state.bgMode === 'gradient' ? 'block' : 'none';
        elements.ctrlImage.style.display = state.bgMode === 'image' ? 'block' : 'none';
        elements.thorSettings.style.display = state.thorEnabled ? 'flex' : 'none';
    };

    const updateBackBackground = () => {
        if (!backEl) return;
        if (state.bgMode === 'solid') {
            backEl.style.setProperty('background', state.colorSolid, 'important');
        } else if (state.bgMode === 'gradient') {
            backEl.style.setProperty('background', `linear-gradient(45deg, ${state.g1}, ${state.g2}, ${state.g3})`, 'important');
        } else if (state.bgMode === 'image') {
            backEl.style.setProperty('background', state.imgBase64 ? `url(${state.imgBase64}) center/cover no-repeat` : '#111', 'important');
        }
    };

    const syncBackSize = () => {
        if (boardEl && backEl) {
            const corteLateral = 20;
            const width = boardEl.offsetWidth - (corteLateral * 2);
            const height = boardEl.offsetHeight;
            if (width > 0 && height > 0) {
                backEl.style.setProperty('width', `${width}px`, 'important');
                backEl.style.setProperty('height', `${height}px`, 'important');
                backEl.style.setProperty('left', `${corteLateral}px`, 'important');
            }
        }
    };

    const killGhostBoards = () => {
        if (!boardEl || !cachedBoardParent) return;
        cachedBoardParent.style.setProperty('background-image', 'none', 'important');
        cachedBoardParent.style.setProperty('background-color', 'transparent', 'important');

        const children = cachedBoardParent.children;
        for (let i = 0; i < children.length; i++) {
            const child = children[i];
            if (child !== boardEl &&
                child.tagName !== 'STYLE' && child.tagName !== 'SCRIPT' && child.tagName !== 'LINK' &&
                !child.classList.contains('chess-3d-overlay') && !child.classList.contains('chess-3d-back') &&
                !child.classList.contains('board-layout-player-top') && !child.classList.contains('board-layout-player-bottom')) {

                const zIndex = window.getComputedStyle(child).zIndex;
                if (zIndex === '-1' || zIndex === '0' || child.tagName === 'IMG' || child.tagName === 'SVG') {
                    if (!child.classList.contains('coordinates') && !child.classList.contains('piece')) {
                        child.style.setProperty('display', 'none', 'important');
                        child.style.setProperty('opacity', '0', 'important');
                    }
                }
            }
        }
    };

    const applyStaticLayoutStyles = () => {
        const centerWrapperRules = state.centerBoard ? `z-index: 9999 !important; position: relative !important; margin: 0 auto !important;` : '';
        const forceTargetInteraction = state.centerBoard ? 'z-index: 10000 !important; position: relative !important; pointer-events: auto !important;' : '';
        dynamicStyle.innerHTML = `
            .chess-board-3d-wrapper { ${centerWrapperRules} }
            .chess-board-3d-target { ${forceTargetInteraction} }
        `;
    };

    const applyTransform = () => {
        if (!boardEl || isAnimatingThor) return;

        if (cachedMainLayout) {
            // Removido o translate(-50%, -50%) que causava o sub-pixel
            const wrapperTransform = `translate3d(${state.tx}px, ${state.ty}px, 0) scale(${state.scale})`;
            cachedMainLayout.style.setProperty('transform', wrapperTransform, 'important');
            cachedMainLayout.style.setProperty('transform-origin', 'center center', 'important');
        }

        const boardTransform = `translate3d(0px, 0px, 0px) rotateX(${state.rx}deg) rotateY(${state.ry}deg)`;
        boardEl.style.setProperty('transform', boardTransform, 'important');

        if (backEl) {
            boardEl.style.setProperty('--reflect-opacity', state.reflect ? '1' : '0');
            const bgAngle = (state.rx + state.ry) * 2.5;
            const bgX = 50 + (state.ry / 1.2);
            const bgY = 50 + (state.rx / 1.2);
            boardEl.style.setProperty('--bg-angle', `${bgAngle}deg`);
            boardEl.style.setProperty('--bg-x', `${bgX}%`);
            boardEl.style.setProperty('--bg-y', `${bgY}%`);
        }

        if (overlayEl) {
            const isShiftedFromOrigin = Math.abs(state.rx) > 0.5 || Math.abs(state.ry) > 0.5 || Math.abs(state.tx) > 0.5 || Math.abs(state.ty) > 0.5;
            const isBlocking = overlayEl.classList.contains('blocking');
            if (isShiftedFromOrigin && !state.allowInteraction) {
                if (!isBlocking) overlayEl.classList.add('blocking');
            } else {
                if (isBlocking) overlayEl.classList.remove('blocking');
            }
        }
    };

    const triggerThorAnimation = () => {
        if (!boardEl || isAnimatingThor) return;
        isAnimatingThor = true;

        if (inertiaFrame) cancelAnimationFrame(inertiaFrame);
        boardEl.style.setProperty('opacity', '1', 'important');

        let startTime = null;
        const durationMs = state.thorDuration * 1000;
        const spinForce = parseFloat(state.thorSpin);
        const startZ = -state.thorDistance;

        const startRx = (Math.random() > 0.5 ? 1 : -1) * (360 + Math.random() * 720) * spinForce;
        const startRy = (Math.random() > 0.5 ? 1 : -1) * (360 + Math.random() * 720) * spinForce;
        const targetScale = state.scale;

        const easeOut = t => 1 - Math.pow(1 - t, 3);

        const animate = (timestamp) => {
            if (!startTime) startTime = timestamp;
            let progress = (timestamp - startTime) / durationMs;
            if (progress >= 1) progress = 1;

            const e = easeOut(progress);
            const currentRx = startRx * (1 - e);
            const currentRy = startRy * (1 - e);
            const currentTx = state.tx * (1 - e);
            const currentTy = state.ty * (1 - e);
            const currentZ = startZ * (1 - e);

            if (cachedMainLayout) {
                 // Removido o translate(-50%, -50%)
                 cachedMainLayout.style.setProperty('transform', `translate3d(${currentTx}px, ${currentTy}px, 0) scale(${targetScale})`, 'important');
            }
            boardEl.style.setProperty('transform', `translate3d(0, 0, ${currentZ}px) rotateX(${currentRx}deg) rotateY(${currentRy}deg)`, 'important');

            if (progress < 1) {
                thorFrameId = requestAnimationFrame(animate);
            } else {
                isAnimatingThor = false;
                resetTransform();
            }
        };
        requestAnimationFrame(animate);
    };

    const resetTransform = () => {
        if (state.locked) return;
        state.rx = 0; state.ry = 0; state.tx = 0; state.ty = 0; state.scale = 1;
        vx = 0; vy = 0;
        if (inertiaFrame) cancelAnimationFrame(inertiaFrame);
        if (thorFrameId && !isAnimatingThor) cancelAnimationFrame(thorFrameId);
        applyTransform();
        saveSettings();
    };

    const startInertia = () => {
        if (inertiaFrame) cancelAnimationFrame(inertiaFrame);
        const loop = () => {
            if (state.locked || isRotating || isPanning || isAnimatingThor) return;
            if (Math.abs(vx) > 0.05 || Math.abs(vy) > 0.05) {
                state.rx -= vy;
                state.ry += vx;
                vx *= friction;
                vy *= friction;
                applyTransform();
                inertiaFrame = requestAnimationFrame(loop);
            } else { vx = 0; vy = 0; }
        };
        loop();
    };

    const findBoard = () => {
        if (!boardEl) {
            boardEl = document.querySelector('chess-board') || document.querySelector('#board-layout-chessboard') || document.querySelector('.board');
            if (boardEl) {
                cachedBoardParent = boardEl.parentElement;
                cachedMainLayout = document.querySelector('.board-layout-main') || boardEl.closest('.board-layout-main');

                boardEl.classList.add('chess-board-3d-target');
                if (cachedBoardParent) cachedBoardParent.classList.add('chess-board-3d-wrapper');

                if (!backEl) { backEl = document.createElement('div'); backEl.className = 'chess-3d-back'; boardEl.appendChild(backEl); }
                if (!overlayEl) { overlayEl = document.createElement('div'); overlayEl.className = 'chess-3d-overlay'; boardEl.appendChild(overlayEl); }

                if (!boardEl._antiResetObserver) {
                    boardEl._antiResetObserver = new MutationObserver(() => {
                        if (isAnimatingThor || isRotating || isPanning) return;
                        const expectedTransform = `translate3d(0px, 0px, 0px) rotateX(${state.rx}deg) rotateY(${state.ry}deg)`;
                        if (boardEl.style.getPropertyValue('transform') !== expectedTransform) {
                            boardEl.style.setProperty('transform', expectedTransform, 'important');
                        }
                    });
                    boardEl._antiResetObserver.observe(boardEl, { attributes: true, attributeFilter: ['style'] });
                }

                if (cachedMainLayout && !cachedMainLayout._antiResetObserver) {
                    cachedMainLayout._antiResetObserver = new MutationObserver(() => {
                        if (isAnimatingThor || isRotating || isPanning) return;
                        // Removido o translate(-50%, -50%)
                        const expectedTransform = `translate3d(${state.tx}px, ${state.ty}px, 0) scale(${state.scale})`;
                        if (cachedMainLayout.style.getPropertyValue('transform') !== expectedTransform) {
                            cachedMainLayout.style.setProperty('transform', expectedTransform, 'important');
                        }
                    });
                    cachedMainLayout._antiResetObserver.observe(cachedMainLayout, { attributes: true, attributeFilter: ['style'] });
                }
                updateControlsVisibility();
                updateBackBackground();
                applyStaticLayoutStyles();
                syncBackSize();
                killGhostBoards();
                applyTransform();
            }
        }
    };

    toggleBtn.addEventListener('click', () => { gui.classList.toggle('show'); });
    elements.bgType.addEventListener('change', (e) => { state.bgMode = e.target.value; updateControlsVisibility(); updateBackBackground(); saveSettings(); });
    elements.colorSolid.addEventListener('input', (e) => { state.colorSolid = e.target.value; updateBackBackground(); saveSettings(); });
    elements.colorG1.addEventListener('input', (e) => { state.g1 = e.target.value; updateBackBackground(); saveSettings(); });
    elements.colorG2.addEventListener('input', (e) => { state.g2 = e.target.value; updateBackBackground(); saveSettings(); });
    elements.colorG3.addEventListener('input', (e) => { state.g3 = e.target.value; updateBackBackground(); saveSettings(); });

    elements.imgUpload.addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (file) {
            const reader = new FileReader();
            reader.onload = (event) => { state.imgBase64 = event.target.result; updateBackBackground(); saveSettings(); };
            reader.readAsDataURL(file);
        }
    });

    elements.checkReflect.addEventListener('change', (e) => { state.reflect = e.target.checked; applyTransform(); saveSettings(); });
    elements.checkInteract.addEventListener('change', (e) => { state.allowInteraction = e.target.checked; applyTransform(); saveSettings(); });
    elements.checkCenter.addEventListener('change', (e) => {
        state.centerBoard = e.target.checked;
        if (state.centerBoard) document.body.classList.add('chess-3d-centered');
        else document.body.classList.remove('chess-3d-centered');
        applyStaticLayoutStyles();
        applyTransform();
        saveSettings();
    });

    elements.checkThor.addEventListener('change', (e) => {
        state.thorEnabled = e.target.checked; updateControlsVisibility();
        if (!state.thorEnabled && boardEl && !isAnimatingThor) { boardEl.style.setProperty('opacity', '1', 'important'); }
        saveSettings();
    });

    elements.thorDuration.addEventListener('input', (e) => { state.thorDuration = parseFloat(e.target.value); elements.valDur.innerText = state.thorDuration + 's'; saveSettings(); });
    elements.thorDistance.addEventListener('input', (e) => { state.thorDistance = parseFloat(e.target.value); elements.valDist.innerText = state.thorDistance; saveSettings(); });
    elements.thorSpin.addEventListener('input', (e) => { state.thorSpin = parseFloat(e.target.value); elements.valSpin.innerText = state.thorSpin; saveSettings(); });

    window.addEventListener('keydown', (e) => {
        const key = e.key.toLowerCase();
        if (key === 'p') {
            state.locked = !state.locked;
            if (state.locked && inertiaFrame) { cancelAnimationFrame(inertiaFrame); vx = 0; vy = 0; }
            elements.lockStatus.textContent = state.locked ? '[ Posição Estática (P) ]' : '';
        }
        if (key === 'o' && !isAnimatingThor) { triggerThorAnimation(); }
    });

    window.addEventListener('wheel', (e) => {
        if (e.target.closest('#chess-3d-gui') || e.target.closest('.chess-3d-gui')) return;
        if (e.target.closest('.chess-board-3d-wrapper') || e.target.closest('chess-board') || e.target.closest('.chess-3d-overlay') || e.target.closest('.board-layout-main')) {
            e.preventDefault();
            const zoomSpeed = 0.001;
            state.scale -= e.deltaY * zoomSpeed;
            if (state.scale < 0.05) state.scale = 0.05;
            if (state.scale > 20) state.scale = 20;

            if (!rAF_pending) {
                rAF_pending = true;
                requestAnimationFrame(() => { applyTransform(); rAF_pending = false; });
            }
            saveSettings();
        } else if (e.button === 1) e.preventDefault();
    }, { passive: false });

    document.addEventListener('mousedown', (e) => {
        if (e.target.closest('#chess-3d-gui') || e.target.closest('#chess-3d-toggle-btn') || isAnimatingThor) return;
        if (e.button === 1) { e.preventDefault(); resetTransform(); return; }

        if (e.button === 0 && !state.locked) {
            if (inertiaFrame) cancelAnimationFrame(inertiaFrame);
            vx = 0; vy = 0;
            const isTargetingBoard = e.target.closest('.chess-board-3d-target') || e.target.closest('.chess-3d-overlay');
            const isDisplaced = Math.abs(state.rx) > 0.5 || Math.abs(state.ry) > 0.5 || Math.abs(state.tx) > 0.5 || Math.abs(state.ty) > 0.5;

            if (isTargetingBoard) {
                if (isDisplaced && !state.allowInteraction) { isPanning = true; lastX = e.clientX; lastY = e.clientY; }
            } else { isRotating = true; lastX = e.clientX; lastY = e.clientY; }
        }
    }, { passive: false });

    document.addEventListener('mousemove', (e) => {
        if (state.locked || isAnimatingThor) return;
        if (isRotating || isPanning) {
            const deltaX = e.clientX - lastX;
            const deltaY = e.clientY - lastY;
            lastX = e.clientX;
            lastY = e.clientY;

            if (isRotating) {
                vx = deltaX * rotSensitivity;
                vy = deltaY * rotSensitivity;
                state.rx -= vy;
                state.ry += vx;
            } else {
                state.tx += deltaX * panSensitivity;
                state.ty += deltaY * panSensitivity;
            }

            if (!rAF_pending) {
                rAF_pending = true;
                requestAnimationFrame(() => { applyTransform(); rAF_pending = false; });
            }
        }
    });

    document.addEventListener('mouseup', () => {
        if (isRotating) { isRotating = false; if (Math.abs(vx) > 1 || Math.abs(vy) > 1) startInertia(); }
        isPanning = false;
    });

    document.addEventListener('auxclick', (e) => { if (e.button === 1) e.preventDefault(); });

    window.addEventListener('resize', () => {
        if (!isRotating && !isPanning && !isAnimatingThor) {
            syncBackSize();
        }
    });

    const domObserver = new MutationObserver(() => {
        if (state.centerBoard && !document.body.classList.contains('chess-3d-centered')) document.body.classList.add('chess-3d-centered');
    });
    domObserver.observe(document.body, { childList: true, subtree: true });

    setInterval(() => {
        findBoard();

        if (boardEl && !isRotating && !isPanning && !isAnimatingThor) {
            syncBackSize();
            killGhostBoards();

            boardEl.style.setProperty('pointer-events', 'auto', 'important');
            boardEl.style.setProperty('touch-action', 'none', 'important');
            boardEl.style.setProperty('overflow', 'visible', 'important');

            const pieces = boardEl.getElementsByClassName('piece');
            for(let i=0; i<pieces.length; i++) {
                pieces[i].style.setProperty('pointer-events', 'auto', 'important');
                pieces[i].style.setProperty('z-index', '100000', 'important');
            }

            const squares = boardEl.getElementsByClassName('square');
            for(let i=0; i<squares.length; i++) {
                squares[i].style.setProperty('z-index', '90000', 'important');
            }

            const svgs = boardEl.getElementsByTagName('svg');
            for(let i=0; i<svgs.length; i++) {
                svgs[i].style.setProperty('pointer-events', 'none', 'important');
            }

            const currentPieces = pieces.length;
            if (state.thorEnabled) {
                if (lastPiecesCount === 0 && currentPieces > 0) triggerThorAnimation();
                else if (currentPieces === 0 && !isAnimatingThor) boardEl.style.setProperty('opacity', '0', 'important');
                else if (currentPieces > 0 && !isAnimatingThor) boardEl.style.setProperty('opacity', '1', 'important');
            }
            lastPiecesCount = currentPieces;
        }

        const ghostPanels = document.querySelectorAll('.analysis-tools, .layout-right, [data-cy="analysis-panel"], .sidebar, .game-overview, .vertical-move-list, #board-layout-sidebar');
        if (state.centerBoard) {
            if (!document.body.classList.contains('chess-3d-centered')) document.body.classList.add('chess-3d-centered');
            for(let i=0; i<ghostPanels.length; i++) {
                const p = ghostPanels[i];
                p.style.setProperty('opacity', '0', 'important');
                p.style.setProperty('position', 'absolute', 'important');
                p.style.setProperty('pointer-events', 'none', 'important');
                p.style.setProperty('visibility', 'hidden', 'important');
                p.style.setProperty('display', 'none', 'important');
            }
        } else {
            for(let i=0; i<ghostPanels.length; i++) {
                const p = ghostPanels[i];
                p.style.removeProperty('opacity');
                p.style.removeProperty('position');
                p.style.removeProperty('pointer-events');
                p.style.removeProperty('visibility');
                p.style.removeProperty('display');
            }
        }
    }, 500);

    updateControlsVisibility();

})();