🎲🧊 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)

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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();

})();