ЁЯФДЁЯО▓ 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)
// ==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();
})();