🎨✨ Transforme seu tabuleiro do Chess.com com efeitos visuais, partículas, rastros, animações e efeitos especiais personalizados! 🪄⚡
// ==UserScript==
// @name 🪄✨ Chess.com VFX Effects
// @namespace http://tampermonkey.net/
// @version 5.1
// @description 🎨✨ Transforme seu tabuleiro do Chess.com com efeitos visuais, partículas, rastros, animações e efeitos especiais personalizados! 🪄⚡
// @author aerus15
// @match *://*.chess.com/*
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
/* Força a peça arrastada a ficar na camada z-index 100000 para permitir que o canvas fique atrás ou na frente de forma exata */
.piece.dragging, wc-chess-board .piece.dragging, .piece.dragging-active {
z-index: 100000 !important;
}
#vfx-toggle-btn {
position: fixed;
bottom: 40px;
left: 10px;
width: 38px;
height: 38px;
background: #2b2b2b;
color: #00e5ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 2147483647;
box-shadow: 0 4px 10px rgba(0, 229, 255, 0.3);
font-size: 20px;
user-select: none;
transition: transform 0.2s, box-shadow 0.2s;
border: 1px solid #00e5ff;
}
#vfx-toggle-btn:hover {
transform: scale(1.1);
box-shadow: 0 4px 15px rgba(0, 229, 255, 0.6);
}
#vfx-panel {
position: fixed;
bottom: 90px;
left: 10px;
background: rgba(15, 15, 18, 0.95);
color: #eee;
border-radius: 12px;
z-index: 2147483647;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 12px;
display: none;
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
width: 300px;
overflow: hidden;
}
.vfx-header {
font-weight: bold;
text-align: center;
font-size: 15px;
color: #00e5ff;
padding: 12px;
background: rgba(0, 0, 0, 0.3);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
letter-spacing: 1px;
text-transform: uppercase;
}
.vfx-tabs {
display: flex;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.vfx-tab-btn {
flex: 1;
padding: 10px 0;
background: transparent;
color: #aaa;
border: none;
cursor: pointer;
font-weight: bold;
transition: all 0.2s;
font-size: 12px;
}
.vfx-tab-btn:hover {
color: #fff;
background: rgba(255,255,255,0.05);
}
.vfx-tab-btn.active {
color: #00e5ff;
border-bottom: 2px solid #00e5ff;
background: rgba(0, 229, 255, 0.05);
}
.vfx-tab-content {
display: none;
padding: 15px;
max-height: 500px;
overflow-y: auto;
}
.vfx-tab-content.active {
display: block;
}
.vfx-tab-content::-webkit-scrollbar {
width: 6px;
}
.vfx-tab-content::-webkit-scrollbar-thumb {
background: #444;
border-radius: 3px;
}
#vfx-panel label {
display: flex;
justify-content: space-between;
margin-top: 12px;
margin-bottom: 6px;
font-weight: 600;
color: #ddd;
}
#vfx-panel input[type="range"], #vfx-panel select, #vfx-panel input[type="color"], #vfx-panel input[type="file"] {
width: 100%;
background: rgba(0, 0, 0, 0.4);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
padding: 6px;
box-sizing: border-box;
outline: none;
transition: border-color 0.2s;
}
#vfx-panel input[type="color"] {
height: 35px;
padding: 2px;
cursor: pointer;
}
#vfx-panel select:focus {
border-color: #00e5ff;
}
#vfx-panel input[type="range"] {
-webkit-appearance: none;
height: 6px;
padding: 0;
border: none;
background: #444;
border-radius: 3px;
}
#vfx-panel input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #00e5ff;
cursor: pointer;
box-shadow: 0 0 5px rgba(0,229,255,0.5);
}
#vfx-canvas {
pointer-events: none;
}
#vfx-hover-light {
position: absolute;
width: 12.5%;
height: 12.5%;
top: 0;
left: 0;
pointer-events: none;
z-index: 2;
transition: transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1), background-color 0.2s, box-shadow 0.2s, opacity 0.2s;
opacity: 0;
will-change: transform, opacity;
border-radius: 4px;
box-sizing: border-box;
}
.vfx-sub-config {
background: rgba(255, 255, 255, 0.05);
padding: 10px;
border-radius: 8px;
margin-top: 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.vfx-checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 6px;
}
.vfx-checkbox-label {
display: flex !important;
align-items: center;
gap: 5px;
font-size: 11px;
color: #eee;
cursor: pointer;
margin: 0 !important;
}
.vfx-btn-action {
background: #00e5ff;
color: #000;
border: none;
padding: 6px 10px;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
font-size: 11px;
transition: background 0.2s;
}
.vfx-btn-action:hover {
background: #00b3cc;
}
.vfx-glitch { animation: glitch-anim 2s infinite linear alternate-reverse; filter: contrast(1.2) brightness(1.1); }
.vfx-glitch-piece { animation: glitch-piece-anim 0.3s infinite !important; }
.vfx-vibrate { animation: vibrate-anim 0.1s infinite !important; }
@keyframes glitch-anim {
0% { clip-path: inset(10% 0 80% 0); transform: translate(-2px, 2px); filter: hue-rotate(90deg); }
20% { clip-path: inset(80% 0 5% 0); transform: translate(2px, -2px); filter: hue-rotate(-90deg); }
40% { clip-path: inset(30% 0 30% 0); transform: translate(-2px, 0); filter: hue-rotate(0deg); }
60% { clip-path: inset(50% 0 30% 0); transform: translate(2px, 2px); filter: hue-rotate(45deg); }
80% { clip-path: inset(5% 0 80% 0); transform: translate(-1px, -1px); filter: hue-rotate(-45deg); }
100% { clip-path: inset(40% 0 10% 0); transform: translate(1px, 1px); filter: hue-rotate(0deg); }
}
@keyframes glitch-piece-anim {
0% { transform: translate(0); opacity: 1; }
20% { transform: translate(-3px, 1px) skewX(20deg); opacity: 0.8; }
40% { transform: translate(2px, -2px) skewX(-20deg); opacity: 0.9; }
60% { transform: translate(-1px, 2px) skewX(10deg); opacity: 1; }
80% { transform: translate(3px, -1px) skewX(-10deg); opacity: 0.8; }
100% { transform: translate(0); opacity: 1; }
}
@keyframes vibrate-anim {
0% { transform: translate(2px, 1px) rotate(0deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
`);
const uiBtn = document.createElement('div');
uiBtn.id = 'vfx-toggle-btn';
uiBtn.innerText = '🪄';
document.body.appendChild(uiBtn);
const uiPanel = document.createElement('div');
uiPanel.id = 'vfx-panel';
uiPanel.innerHTML = `
<div class="vfx-header">VFX Tabuleiro Pro</div>
<div class="vfx-tabs">
<button class="vfx-tab-btn active" data-target="tab-geral">Gerais</button>
<button class="vfx-tab-btn" data-target="tab-movimento">Movimentos</button>
</div>
<div id="tab-geral" class="vfx-tab-content active">
<label>Efeito Global:</label>
<select id="vfx-select" class="vfx-save">
<option value="none">Nenhum</option>
<option value="space">Espaço (Estrelas)</option>
<option value="warp">Hyperespaço (Warp)</option>
<option value="rain">Chuva</option>
<option value="snow">Neve</option>
<option value="smoke">Fumaça</option>
<option value="fire">Fogo</option>
<option value="wind">Vento</option>
<option value="lights">Luzes Mágicas</option>
<option value="fairydust">Pó de Fada</option>
<option value="fireflies">Vaga-lumes</option>
<option value="meteors">Meteoros</option>
<option value="lightning">Raios</option>
<option value="birds">Corvos / Pássaros</option>
<option value="lasers">Guerra (Tiros/Lasers)</option>
<option value="matrix">Matrix</option>
<option value="glitch">Glitch / Bugado</option>
<option value="vibrate">Terremoto (Vibração)</option>
</select>
<label>Velocidade: <span id="vfx-speed-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-speed" class="vfx-save" min="0.1" max="5" step="0.1" value="1">
<label>Tamanho: <span id="vfx-size-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-size" class="vfx-save" min="0.5" max="5" step="0.1" value="1">
</div>
<div id="tab-movimento" class="vfx-tab-content">
<label>Partículas ao Arrastar:</label>
<select id="vfx-drag-effect" class="vfx-save">
<option value="none">Nenhum</option>
<option value="water">Ondas de Água</option>
<option value="sparkles">Faíscas</option>
<option value="magic">Pó Mágico</option>
<option value="trail">Rastro (Fumaça/Luz)</option>
<option value="image">Adicionar Imagens</option>
</select>
<div id="vfx-sparkles-cfg" class="vfx-sub-config" style="display: none;">
<label>Intensidade das Faíscas: <span id="vfx-sparkle-val" style="color:#ffae00;">50%</span></label>
<input type="range" id="vfx-sparkle-int" class="vfx-save" min="10" max="100" step="5" value="50">
</div>
<div id="vfx-trail-cfg" class="vfx-sub-config" style="display: none;">
<label>Cor do Rastro:</label>
<input type="color" id="vfx-trail-color" class="vfx-save" value="#ffffff">
<label>Espessura do Rastro: <span id="vfx-trail-thick-val" style="color:#00e5ff;">15px</span></label>
<input type="range" id="vfx-trail-thick" class="vfx-save" min="5" max="40" step="1" value="15">
<label>Velocidade de Sumiço: <span id="vfx-trail-fade-val" style="color:#00e5ff;">Média</span></label>
<input type="range" id="vfx-trail-fade" class="vfx-save" min="0.01" max="0.1" step="0.01" value="0.04">
</div>
<div id="vfx-image-cfg" class="vfx-sub-config" style="display: none;">
<label>Imagem da Peça:</label>
<input type="file" id="vfx-img-file" accept="image/*">
<label>Posição X: <span id="vfx-img-x-val" style="color:#00e5ff;">0px</span></label>
<input type="range" id="vfx-img-x" class="vfx-save" min="-150" max="150" step="1" value="0">
<label>Posição Y: <span id="vfx-img-y-val" style="color:#00e5ff;">0px</span></label>
<input type="range" id="vfx-img-y" class="vfx-save" min="-150" max="150" step="1" value="0">
<label>Tamanho da Imagem: <span id="vfx-img-scale-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-img-scale" class="vfx-save" min="0.2" max="3" step="0.05" value="1">
<hr style="border:0; border-top:1px solid rgba(255,255,255,0.1); margin: 10px 0;">
<label>Tamanho dos Efeitos: <span id="vfx-eff-size-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-eff-size" class="vfx-save" min="0.2" max="3" step="0.1" value="1">
<label>Intensidade dos Efeitos: <span id="vfx-eff-int-val" style="color:#00e5ff;">100%</span></label>
<input type="range" id="vfx-eff-int" class="vfx-save" min="10" max="300" step="10" value="100">
<label>Efeitos na Imagem:</label>
<div class="vfx-checkbox-group">
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-jetpack" class="vfx-save"> Jetpack</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-fire" class="vfx-save"> Fogo</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-smoke" class="vfx-save"> Fumaça</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-sparkles" class="vfx-save"> Faíscas</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-trail" class="vfx-save"> Rastro</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-snow" class="vfx-save"> Neve</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-fairydust" class="vfx-save"> Pó de Fada</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-magiclights" class="vfx-save"> Luzes Mágicas</label>
<label class="vfx-checkbox-label"><input type="checkbox" id="vfx-eff-fireflies" class="vfx-save"> Vagalumes</label>
<label class="vfx-checkbox-label" style="width: 100%; color: #00e5ff;"><input type="checkbox" id="vfx-eff-dash" class="vfx-save"> Rastro Dash (Fantasmas)</label>
<label class="vfx-checkbox-label" style="width: 100%; color: #ffae00; margin-top: 4px;"><input type="checkbox" id="vfx-eff-keep-active" class="vfx-save"> Manter Efeito Ativo (Parado / Ao Segurar)</label>
</div>
<label>Camada dos Efeitos:</label>
<select id="vfx-effect-layer" class="vfx-save">
<option value="behind_piece">Atrás da Peça</option>
<option value="over_piece">Por Cima da Peça</option>
</select>
<label>Preview & Origem dos Efeitos:</label>
<div style="position: relative; width: 100%; height: 160px; background: #111; border: 1px solid rgba(0,229,255,0.3); border-radius: 6px; margin-top: 5px; overflow: hidden; display: flex; align-items: center; justify-content: center;">
<canvas id="vfx-preview-canvas" width="220" height="150" style="cursor: crosshair;"></canvas>
</div>
<div style="display: flex; gap: 5px; margin-top: 8px;">
<button id="vfx-add-dot-btn" type="button" class="vfx-btn-action" style="flex:1;">+ Ponto Efeito</button>
<button id="vfx-clear-dots-btn" type="button" class="vfx-btn-action" style="flex:1; background:#ff4444; color:#fff;">Resetar Pontos</button>
</div>
</div>
<hr style="border:0; border-top:1px solid rgba(255,255,255,0.1); margin: 15px 0;">
<label>Iluminar Casas (Hover):</label>
<select id="vfx-hover-toggle" class="vfx-save">
<option value="on">Ativado</option>
<option value="off">Desativado</option>
</select>
<label>Cor da Iluminação:</label>
<input type="color" id="vfx-light-color" class="vfx-save" value="#00e5ff">
<label>Intensidade da Cor: <span id="vfx-intensity-val" style="color:#00e5ff;">50%</span></label>
<input type="range" id="vfx-light-intensity" class="vfx-save" min="0" max="1" step="0.05" value="0.5">
<label>Vazamento da Luz (Blur): <span id="vfx-bleed-val" style="color:#00e5ff;">25px</span></label>
<input type="range" id="vfx-light-bleed" class="vfx-save" min="0" max="80" step="1" value="25">
</div>
`;
document.body.appendChild(uiPanel);
let panelOpen = false;
uiBtn.addEventListener('click', () => {
panelOpen = !panelOpen;
uiPanel.style.display = panelOpen ? 'block' : 'none';
if (panelOpen) renderPreview();
});
document.querySelectorAll('.vfx-tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.vfx-tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.vfx-tab-content').forEach(c => c.classList.remove('active'));
e.target.classList.add('active');
document.getElementById(e.target.getAttribute('data-target')).classList.add('active');
if (e.target.getAttribute('data-target') === 'tab-movimento') {
renderPreview();
}
});
});
const canvas = document.createElement('canvas');
canvas.id = 'vfx-canvas';
const ctx = canvas.getContext('2d', { alpha: true });
let boardEl = null;
let hoverLightEl = null;
let particles = [];
let dragParticles = [];
let animationId = null;
let currentEffect = 'none';
let speedMult = 1;
let sizeMult = 1;
let customImg = new Image();
let customImgLoaded = false;
let emissionPoints = [{ x: 0.5, y: 0.8 }];
// Inicializado com coordenadas distantes para evitar vazamentos acidentais no começo
let lastDragMousePos = { x: -1000, y: -1000 };
const previewCanvas = document.getElementById('vfx-preview-canvas');
const previewCtx = previewCanvas.getContext('2d');
let activeDotIndex = -1;
let isDraggingDot = false;
// Helper para obter o container correto de coordenadas
function getTargetContainer(board) {
if (!board) return null;
return board.tagName.toLowerCase() === 'canvas' ? board.parentElement : board;
}
function saveSettings() {
const settings = {};
document.querySelectorAll('.vfx-save').forEach(input => {
if (input.type === 'checkbox') {
settings[input.id] = input.checked;
} else {
settings[input.id] = input.value;
}
});
localStorage.setItem('vfx_chess_pro_settings', JSON.stringify(settings));
localStorage.setItem('vfx_emission_points', JSON.stringify(emissionPoints));
}
function loadSettings() {
const saved = localStorage.getItem('vfx_chess_pro_settings');
if (saved) {
try {
const settings = JSON.parse(saved);
Object.keys(settings).forEach(id => {
const el = document.getElementById(id);
if (el) {
if (el.type === 'checkbox') {
el.checked = settings[id];
} else {
el.value = settings[id];
}
}
});
} catch (e) {}
}
const savedImg = localStorage.getItem('vfx_custom_img_data');
if (savedImg) {
customImg.onload = () => {
customImgLoaded = true;
renderPreview();
};
customImg.src = savedImg;
}
const savedPoints = localStorage.getItem('vfx_emission_points');
if (savedPoints) {
try {
emissionPoints = JSON.parse(savedPoints);
} catch (e) {
emissionPoints = [{ x: 0.5, y: 0.8 }];
}
}
document.getElementById('vfx-select').dispatchEvent(new Event('change'));
document.getElementById('vfx-speed').dispatchEvent(new Event('input'));
document.getElementById('vfx-size').dispatchEvent(new Event('input'));
document.getElementById('vfx-drag-effect').dispatchEvent(new Event('change'));
document.getElementById('vfx-sparkle-int').dispatchEvent(new Event('input'));
document.getElementById('vfx-trail-thick').dispatchEvent(new Event('input'));
document.getElementById('vfx-trail-fade').dispatchEvent(new Event('input'));
document.getElementById('vfx-img-x').dispatchEvent(new Event('input'));
document.getElementById('vfx-img-y').dispatchEvent(new Event('input'));
document.getElementById('vfx-img-scale').dispatchEvent(new Event('input'));
document.getElementById('vfx-eff-size').dispatchEvent(new Event('input'));
document.getElementById('vfx-eff-int').dispatchEvent(new Event('input'));
document.getElementById('vfx-light-intensity').dispatchEvent(new Event('input'));
document.getElementById('vfx-light-bleed').dispatchEvent(new Event('input'));
updateLightStyles();
}
document.querySelectorAll('.vfx-save').forEach(el => {
el.addEventListener('change', () => { saveSettings(); renderPreview(); });
el.addEventListener('input', () => { saveSettings(); renderPreview(); });
});
document.getElementById('vfx-select').addEventListener('change', (e) => {
currentEffect = e.target.value;
initParticles();
handleBoardEffects();
});
document.getElementById('vfx-speed').addEventListener('input', (e) => {
speedMult = parseFloat(e.target.value);
document.getElementById('vfx-speed-val').innerText = speedMult + 'x';
});
document.getElementById('vfx-size').addEventListener('input', (e) => {
sizeMult = parseFloat(e.target.value);
document.getElementById('vfx-size-val').innerText = sizeMult + 'x';
initParticles();
});
document.getElementById('vfx-drag-effect').addEventListener('change', (e) => {
const val = e.target.value;
document.getElementById('vfx-sparkles-cfg').style.display = val === 'sparkles' ? 'block' : 'none';
document.getElementById('vfx-trail-cfg').style.display = val === 'trail' ? 'block' : 'none';
document.getElementById('vfx-image-cfg').style.display = val === 'image' ? 'block' : 'none';
if (val === 'image') renderPreview();
});
document.getElementById('vfx-sparkle-int').addEventListener('input', (e) => {
document.getElementById('vfx-sparkle-val').innerText = e.target.value + '%';
});
document.getElementById('vfx-trail-thick').addEventListener('input', (e) => {
document.getElementById('vfx-trail-thick-val').innerText = e.target.value + 'px';
});
document.getElementById('vfx-trail-fade').addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
let texto = "Média";
if (val <= 0.02) texto = "Lenta";
else if (val >= 0.08) texto = "Rápida";
document.getElementById('vfx-trail-fade-val').innerText = texto;
});
document.getElementById('vfx-img-x').addEventListener('input', (e) => {
document.getElementById('vfx-img-x-val').innerText = e.target.value + 'px';
renderPreview();
});
document.getElementById('vfx-img-y').addEventListener('input', (e) => {
document.getElementById('vfx-img-y-val').innerText = e.target.value + 'px';
renderPreview();
});
document.getElementById('vfx-img-scale').addEventListener('input', (e) => {
document.getElementById('vfx-img-scale-val').innerText = e.target.value + 'x';
renderPreview();
});
document.getElementById('vfx-eff-size').addEventListener('input', (e) => {
document.getElementById('vfx-eff-size-val').innerText = e.target.value + 'x';
});
document.getElementById('vfx-eff-int').addEventListener('input', (e) => {
document.getElementById('vfx-eff-int-val').innerText = e.target.value + '%';
});
document.getElementById('vfx-img-file').addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (evt) => {
customImg = new Image();
customImg.onload = () => {
customImgLoaded = true;
localStorage.setItem('vfx_custom_img_data', evt.target.result);
renderPreview();
};
customImg.src = evt.target.result;
};
reader.readAsDataURL(file);
}
});
document.getElementById('vfx-add-dot-btn').addEventListener('click', () => {
emissionPoints.push({ x: 0.5, y: 0.8 });
saveSettings();
renderPreview();
});
document.getElementById('vfx-clear-dots-btn').addEventListener('click', () => {
emissionPoints = [{ x: 0.5, y: 0.8 }];
saveSettings();
renderPreview();
});
const lightInputs = ['vfx-light-color', 'vfx-light-intensity', 'vfx-light-bleed', 'vfx-hover-toggle'];
lightInputs.forEach(id => {
document.getElementById(id).addEventListener('input', () => {
if (id === 'vfx-light-intensity') document.getElementById('vfx-intensity-val').innerText = Math.round(document.getElementById(id).value * 100) + '%';
if (id === 'vfx-light-bleed') document.getElementById('vfx-bleed-val').innerText = document.getElementById(id).value + 'px';
updateLightStyles();
});
});
function getPreviewImageBounds() {
const pw = previewCanvas.width;
const ph = previewCanvas.height;
const scale = parseFloat(document.getElementById('vfx-img-scale').value);
const offsetX = parseFloat(document.getElementById('vfx-img-x').value) * 0.5;
const offsetY = parseFloat(document.getElementById('vfx-img-y').value) * 0.5;
let iw = customImgLoaded ? customImg.width : 60;
let ih = customImgLoaded ? customImg.height : 60;
let aspect = iw / ih;
let drawW = 60 * scale;
let drawH = drawW / aspect;
let cx = pw / 2 + offsetX;
let cy = ph / 2 + offsetY;
return {
x: cx - drawW / 2,
y: cy - drawH / 2,
w: drawW,
h: drawH,
cx: cx,
cy: cy
};
}
function renderPreview() {
if (!previewCanvas) return;
previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
previewCtx.fillStyle = '#18181c';
previewCtx.fillRect(0, 0, previewCanvas.width, previewCanvas.height);
const bounds = getPreviewImageBounds();
previewCtx.beginPath();
previewCtx.arc(previewCanvas.width / 2, previewCanvas.height / 2, 22, 0, Math.PI * 2);
previewCtx.fillStyle = 'rgba(255, 255, 255, 0.15)';
previewCtx.strokeStyle = 'rgba(0, 229, 255, 0.4)';
previewCtx.lineWidth = 1;
previewCtx.fill();
previewCtx.stroke();
previewCtx.fillStyle = '#00e5ff';
previewCtx.font = '10px sans-serif';
previewCtx.textAlign = 'center';
previewCtx.fillText('PEÇA', previewCanvas.width / 2, previewCanvas.height / 2 + 3);
if (customImgLoaded) {
previewCtx.drawImage(customImg, bounds.x, bounds.y, bounds.w, bounds.h);
previewCtx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
previewCtx.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
} else {
previewCtx.strokeStyle = '#555';
previewCtx.setLineDash([4, 4]);
previewCtx.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
previewCtx.setLineDash([]);
previewCtx.fillStyle = '#888';
previewCtx.font = '10px sans-serif';
previewCtx.fillText('Sem Imagem', bounds.cx, bounds.cy);
}
emissionPoints.forEach((pt, index) => {
let px = bounds.x + pt.x * bounds.w;
let py = bounds.y + pt.y * bounds.h;
previewCtx.beginPath();
previewCtx.arc(px, py, 6, 0, Math.PI * 2);
previewCtx.fillStyle = '#ffffff';
previewCtx.shadowBlur = 8;
previewCtx.shadowColor = '#00e5ff';
previewCtx.fill();
previewCtx.shadowBlur = 0;
previewCtx.strokeStyle = '#00e5ff';
previewCtx.lineWidth = 2;
previewCtx.stroke();
previewCtx.fillStyle = '#000';
previewCtx.font = '9px sans-serif';
previewCtx.fillText((index + 1).toString(), px, py + 3);
});
}
previewCanvas.addEventListener('mousedown', (e) => {
const rect = previewCanvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const bounds = getPreviewImageBounds();
activeDotIndex = -1;
for (let i = 0; i < emissionPoints.length; i++) {
let px = bounds.x + emissionPoints[i].x * bounds.w;
let py = bounds.y + emissionPoints[i].y * bounds.h;
let dist = Math.hypot(mx - px, my - py);
if (dist <= 10) {
activeDotIndex = i;
break;
}
}
if (activeDotIndex !== -1) {
isDraggingDot = true;
} else if (bounds.w > 0 && bounds.h > 0) {
let nx = (mx - bounds.x) / bounds.w;
let ny = (my - bounds.y) / bounds.h;
nx = Math.max(0, Math.min(1, nx));
ny = Math.max(0, Math.min(1, ny));
emissionPoints.push({ x: nx, y: ny });
activeDotIndex = emissionPoints.length - 1;
isDraggingDot = true;
saveSettings();
renderPreview();
}
});
previewCanvas.addEventListener('mousemove', (e) => {
if (!isDraggingDot || activeDotIndex === -1) return;
const rect = previewCanvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const bounds = getPreviewImageBounds();
if (bounds.w > 0 && bounds.h > 0) {
let nx = (mx - bounds.x) / bounds.w;
let ny = (my - bounds.y) / bounds.h;
nx = Math.max(0, Math.min(1, nx));
ny = Math.max(0, Math.min(1, ny));
emissionPoints[activeDotIndex] = { x: nx, y: ny };
renderPreview();
}
});
window.addEventListener('mouseup', () => {
if (isDraggingDot) {
isDraggingDot = false;
saveSettings();
}
});
function getBoard() {
return document.querySelector('wc-chess-board') || document.querySelector('.board') || document.querySelector('#board-layout-main canvas');
}
function updateCanvasBounds() {
boardEl = getBoard();
const targetContainer = getTargetContainer(boardEl);
if (targetContainer) {
if (canvas.parentNode !== targetContainer) {
targetContainer.appendChild(canvas);
if (getComputedStyle(targetContainer).position === 'static') {
targetContainer.style.position = 'relative';
}
}
canvas.style.position = 'absolute';
canvas.style.top = '0px';
canvas.style.left = '0px';
canvas.style.pointerEvents = 'none';
const rect = targetContainer.getBoundingClientRect();
if (canvas.width !== Math.floor(rect.width) || canvas.height !== Math.floor(rect.height)) {
canvas.width = rect.width;
canvas.height = rect.height;
initParticles();
}
if (!hoverLightEl) {
hoverLightEl = document.createElement('div');
hoverLightEl.id = 'vfx-hover-light';
targetContainer.appendChild(hoverLightEl);
updateLightStyles();
} else if (hoverLightEl.parentNode !== targetContainer) {
targetContainer.appendChild(hoverLightEl);
}
} else {
canvas.width = 0;
canvas.height = 0;
}
}
function updateLightStyles() {
if (!hoverLightEl) return;
const toggle = document.getElementById('vfx-hover-toggle').value;
if (toggle === 'off') {
hoverLightEl.style.display = 'none';
return;
}
hoverLightEl.style.display = 'block';
const colorHex = document.getElementById('vfx-light-color').value;
const intensity = document.getElementById('vfx-light-intensity').value;
const bleed = document.getElementById('vfx-light-bleed').value;
let r = parseInt(colorHex.substring(1,3), 16);
let g = parseInt(colorHex.substring(3,5), 16);
let b = parseInt(colorHex.substring(5,7), 16);
hoverLightEl.style.backgroundColor = `rgba(${r},${g},${b},${intensity})`;
if (bleed > 0) {
hoverLightEl.style.boxShadow = `0 0 ${bleed}px ${bleed/2}px rgba(${r},${g},${b}, ${Math.min(1, intensity * 1.5)})`;
} else {
hoverLightEl.style.boxShadow = 'none';
}
}
function handleBoardEffects() {
if (!boardEl) return;
const pieces = boardEl.querySelectorAll('.piece');
boardEl.classList.remove('vfx-glitch', 'vfx-vibrate');
pieces.forEach(p => p.classList.remove('vfx-glitch-piece'));
document.querySelectorAll('.vfx-glitch-piece').forEach(p => p.classList.remove('vfx-glitch-piece'));
if (currentEffect === 'glitch') {
boardEl.classList.add('vfx-glitch');
pieces.forEach(p => p.classList.add('vfx-glitch-piece'));
} else if (currentEffect === 'vibrate') {
boardEl.classList.add('vfx-vibrate');
}
}
let isDragging = false;
let currentHoverCol = -1;
let currentHoverRow = -1;
let lastSpawnTime = 0;
document.addEventListener('mousedown', (e) => {
if (e.target.closest('.piece') || e.target.closest('.square')) {
isDragging = true;
if (boardEl) {
const targetContainer = getTargetContainer(boardEl);
if (targetContainer) {
const rect = targetContainer.getBoundingClientRect();
lastDragMousePos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
}
}
updateHoverSquare(e);
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
if (hoverLightEl) hoverLightEl.style.opacity = '0';
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
updateHoverSquare(e);
spawnDragParticle(e);
}
});
function updateHoverSquare(e) {
if (!boardEl || !hoverLightEl || document.getElementById('vfx-hover-toggle').value === 'off') return;
const targetContainer = getTargetContainer(boardEl);
if (!targetContainer) return;
const rect = targetContainer.getBoundingClientRect();
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
if (x < 0 || x > rect.width || y < 0 || y > rect.height) {
hoverLightEl.style.opacity = '0';
return;
}
let sqSize = rect.width / 8;
let col = Math.floor(x / sqSize);
let row = Math.floor(y / sqSize);
if (col !== currentHoverCol || row !== currentHoverRow) {
currentHoverCol = col;
currentHoverRow = row;
hoverLightEl.style.transform = `translate(${col * 100}%, ${row * 100}%)`;
hoverLightEl.style.opacity = '1';
}
}
function spawnDragParticle(e) {
const type = document.getElementById('vfx-drag-effect').value;
if (type === 'none' || !boardEl) return;
let now = Date.now();
let delay = type === 'water' ? 80 : (type === 'trail' ? 8 : (type === 'image' ? 15 : 12));
if (now - lastSpawnTime > delay) {
const targetContainer = getTargetContainer(boardEl);
if (!targetContainer) return;
const rect = targetContainer.getBoundingClientRect();
let cx, cy;
if (e) {
cx = e.clientX - rect.left;
cy = e.clientY - rect.top;
lastDragMousePos = { x: cx, y: cy };
} else {
cx = lastDragMousePos.x;
cy = lastDragMousePos.y;
}
if (cx >= 0 && cx <= rect.width && cy >= 0 && cy <= rect.height) {
if (type === 'sparkles') {
const intensity = parseInt(document.getElementById('vfx-sparkle-int').value);
const amount = Math.floor((intensity / 100) * 18) + 2;
for (let i = 0; i < amount; i++) {
dragParticles.push(new DragParticle(cx, cy, type));
}
} else if (type === 'image') {
const scale = parseFloat(document.getElementById('vfx-img-scale').value);
const offX = parseFloat(document.getElementById('vfx-img-x').value);
const offY = parseFloat(document.getElementById('vfx-img-y').value);
const effSize = parseFloat(document.getElementById('vfx-eff-size').value) || 1;
const effInt = (parseFloat(document.getElementById('vfx-eff-int').value) || 100) / 100;
let sqSize = rect.width / 8;
let imgW = (customImgLoaded ? customImg.width : sqSize) * scale;
let imgH = (customImgLoaded ? customImg.height : sqSize) * scale;
let imgX = cx + offX - imgW / 2;
let imgY = cy + offY - imgH / 2;
const activeEffs = {
jetpack: document.getElementById('vfx-eff-jetpack').checked,
fire: document.getElementById('vfx-eff-fire').checked,
smoke: document.getElementById('vfx-eff-smoke').checked,
sparkles: document.getElementById('vfx-eff-sparkles').checked,
trail: document.getElementById('vfx-eff-trail').checked,
snow: document.getElementById('vfx-eff-snow').checked,
fairydust: document.getElementById('vfx-eff-fairydust').checked,
magiclights: document.getElementById('vfx-eff-magiclights').checked,
fireflies: document.getElementById('vfx-eff-fireflies').checked,
dash: document.getElementById('vfx-eff-dash').checked
};
const getCount = (base) => {
let c = base * effInt;
let f = Math.floor(c);
if (Math.random() < (c - f)) f++;
return f;
};
if (activeEffs.dash && customImgLoaded) {
dragParticles.push(new DragParticle(imgX, imgY, 'dash_ghost', { w: imgW, h: imgH, img: customImg }));
}
emissionPoints.forEach(pt => {
let eX = imgX + pt.x * imgW;
let eY = imgY + pt.y * imgH;
if (activeEffs.jetpack) {
for (let i = 0; i < getCount(3); i++) dragParticles.push(new DragParticle(eX, eY, 'jetpack_fire', { sizeMult: effSize }));
for (let i = 0; i < getCount(2); i++) dragParticles.push(new DragParticle(eX, eY, 'jetpack_smoke', { sizeMult: effSize }));
}
if (activeEffs.fire) {
for (let i = 0; i < getCount(2); i++) dragParticles.push(new DragParticle(eX, eY, 'fire_eff', { sizeMult: effSize }));
}
if (activeEffs.smoke) {
for (let i = 0; i < getCount(1); i++) dragParticles.push(new DragParticle(eX, eY, 'smoke_eff', { sizeMult: effSize }));
}
if (activeEffs.sparkles) {
for (let i = 0; i < getCount(3); i++) dragParticles.push(new DragParticle(eX, eY, 'sparkles', { sizeMult: effSize }));
}
if (activeEffs.trail) {
for (let i = 0; i < getCount(1); i++) dragParticles.push(new DragParticle(eX, eY, 'trail', { sizeMult: effSize }));
}
if (activeEffs.snow) {
for (let i = 0; i < getCount(1); i++) dragParticles.push(new DragParticle(eX, eY, 'snow_eff', { sizeMult: effSize }));
}
if (activeEffs.fairydust) {
for (let i = 0; i < getCount(2); i++) dragParticles.push(new DragParticle(eX, eY, 'fairydust_eff', { sizeMult: effSize }));
}
if (activeEffs.magiclights) {
for (let i = 0; i < getCount(1); i++) dragParticles.push(new DragParticle(eX, eY, 'magiclights_eff', { sizeMult: effSize }));
}
if (activeEffs.fireflies) {
for (let i = 0; i < getCount(1); i++) dragParticles.push(new DragParticle(eX, eY, 'fireflies_eff', { sizeMult: effSize }));
}
});
} else {
dragParticles.push(new DragParticle(cx, cy, type));
}
lastSpawnTime = now;
}
}
}
class DragParticle {
constructor(x, y, type, cfg = {}) {
this.x = x;
this.y = y;
this.type = type;
this.life = 1.0;
this.colorHex = document.getElementById('vfx-light-color').value;
let sm = cfg.sizeMult || 1;
if (type === 'dash_ghost') {
this.img = cfg.img;
this.w = cfg.w;
this.h = cfg.h;
this.decay = 0.06;
} else if (type === 'water') {
this.radius = 5 * sm;
this.speed = 1.5 + Math.random();
this.decay = 0.02;
} else if (type === 'sparkles') {
this.colorHex = ['#ffcc00', '#ff9900', '#ffffff', '#ff5500'][Math.floor(Math.random()*4)];
this.vx = (Math.random() - 0.5) * 15;
this.vy = (Math.random() - 0.5) * 15;
this.size = (Math.random() * 2.5 + 0.5) * sm;
this.decay = 0.04 + Math.random() * 0.03;
} else if (type === 'magic') {
this.vx = (Math.random() - 0.5) * 3;
this.vy = (Math.random() - 0.5) * 3 - 1;
this.size = (Math.random() * 4 + 1) * sm;
this.decay = 0.015;
} else if (type === 'trail') {
this.colorHex = document.getElementById('vfx-trail-color').value;
this.radius = parseInt(document.getElementById('vfx-trail-thick').value) * sm;
this.decay = parseFloat(document.getElementById('vfx-trail-fade').value);
this.vx = (Math.random() - 0.5) * 0.5;
this.vy = (Math.random() - 0.5) * 0.5;
} else if (type === 'jetpack_fire') {
this.colorHex = ['#ff2200', '#ff6600', '#ffcc00', '#ffffff'][Math.floor(Math.random()*4)];
this.vx = (Math.random() - 0.5) * 4;
this.vy = Math.random() * 7 + 4;
this.size = (Math.random() * 10 + 5) * sm;
this.decay = 0.05 + Math.random() * 0.03;
} else if (type === 'jetpack_smoke') {
this.colorHex = 'rgba(120, 120, 120, 0.5)';
this.vx = (Math.random() - 0.5) * 5;
this.vy = Math.random() * 5 + 3;
this.size = (Math.random() * 14 + 8) * sm;
this.decay = 0.02 + Math.random() * 0.02;
} else if (type === 'fire_eff') {
this.colorHex = ['#ff3300', '#ff8800', '#ffd700'][Math.floor(Math.random()*3)];
this.vx = (Math.random() - 0.5) * 3;
this.vy = -(Math.random() * 4 + 2);
this.size = (Math.random() * 12 + 6) * sm;
this.decay = 0.04;
} else if (type === 'smoke_eff') {
this.colorHex = 'rgba(100, 100, 100, 0.4)';
this.vx = (Math.random() - 0.5) * 2;
this.vy = -(Math.random() * 3 + 1);
this.size = (Math.random() * 16 + 10) * sm;
this.decay = 0.02;
} else if (type === 'snow_eff') {
this.colorHex = 'rgba(255, 255, 255, 0.9)';
this.vx = (Math.random() - 0.5) * 1.5;
this.vy = (Math.random() * 2 + 1);
this.size = (Math.random() * 3 + 1.5) * sm;
this.decay = 0.02;
} else if (type === 'fairydust_eff') {
this.colorHex = '#ffb6c1';
this.vx = (Math.random() - 0.5) * 2;
this.vy = -(Math.random() * 2 + 1);
this.size = (Math.random() * 2.5 + 1) * sm;
this.decay = 0.03;
this.angle = Math.random() * Math.PI * 2;
} else if (type === 'magiclights_eff') {
const colors = ['#ff00ff', '#00ffff', '#ffff00', '#00ff00', '#ff0055'];
this.colorHex = colors[Math.floor(Math.random() * colors.length)];
this.vx = (Math.random() - 0.5) * 1.5;
this.vy = -(Math.random() - 0.5) * 1.5;
this.size = (Math.random() * 6 + 3) * sm;
this.decay = 0.02;
this.glow = (Math.random() * 15 + 5) * sm;
} else if (type === 'fireflies_eff') {
this.colorHex = '#c6ff00';
this.vx = (Math.random() - 0.5) * 2;
this.vy = (Math.random() - 0.5) * 2;
this.size = (Math.random() * 3 + 1.5) * sm;
this.decay = 0.015;
this.angle = Math.random() * Math.PI * 2;
}
}
update() {
if (this.type === 'dash_ghost') {
} else if (this.type === 'water') {
this.radius += this.speed;
} else if (this.type === 'trail') {
this.x += this.vx;
this.y += this.vy;
this.radius += 0.3;
} else if (this.type === 'jetpack_smoke' || this.type === 'smoke_eff') {
this.x += this.vx;
this.y += this.vy;
this.size += 0.3;
} else if (this.type === 'fairydust_eff' || this.type === 'fireflies_eff') {
this.angle += (Math.random() * 0.2 - 0.1);
this.x += this.vx + Math.cos(this.angle) * 1.5;
this.y += this.vy + Math.sin(this.angle) * 1.5;
} else {
this.x += this.vx;
this.y += this.vy;
if (this.type === 'sparkles') {
this.vy += 0.3;
this.vx *= 0.95;
}
}
this.life -= this.decay;
}
draw(ctx) {
ctx.globalAlpha = Math.max(0, this.life);
if (this.type === 'dash_ghost') {
if (this.img) {
ctx.globalAlpha = Math.max(0, this.life * 0.4);
ctx.drawImage(this.img, this.x, this.y, this.w, this.h);
}
} else if (this.type === 'water') {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.strokeStyle = this.colorHex;
ctx.lineWidth = 2 * this.life;
ctx.stroke();
} else if (this.type === 'sparkles') {
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x - (this.vx * 1.5), this.y - (this.vy * 1.5));
ctx.strokeStyle = this.colorHex;
ctx.lineWidth = this.size;
ctx.lineCap = 'round';
ctx.shadowBlur = 8;
ctx.shadowColor = this.colorHex;
ctx.stroke();
ctx.shadowBlur = 0;
} else if (this.type === 'magic') {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.colorHex;
ctx.shadowBlur = 10;
ctx.shadowColor = this.colorHex;
ctx.fill();
ctx.shadowBlur = 0;
} else if (this.type === 'magiclights_eff') {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.colorHex;
ctx.shadowBlur = this.glow;
ctx.shadowColor = this.colorHex;
ctx.fill();
ctx.shadowBlur = 0;
} else if (this.type === 'fairydust_eff' || this.type === 'fireflies_eff') {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.colorHex;
ctx.shadowBlur = 8;
ctx.shadowColor = this.colorHex;
ctx.fill();
ctx.shadowBlur = 0;
} else if (this.type === 'trail' || this.type === 'jetpack_fire' || this.type === 'jetpack_smoke' || this.type === 'fire_eff' || this.type === 'smoke_eff' || this.type === 'snow_eff') {
ctx.beginPath();
ctx.arc(this.x, this.y, this.type === 'trail' ? this.radius : this.size, 0, Math.PI * 2);
ctx.fillStyle = this.colorHex;
if (this.type === 'jetpack_fire' || this.type === 'fire_eff') {
ctx.shadowBlur = 12;
ctx.shadowColor = this.colorHex;
}
ctx.fill();
ctx.shadowBlur = 0;
}
ctx.globalAlpha = 1.0;
}
}
class Particle {
constructor(type) {
this.type = type;
this.reset(true);
if (type === 'warp') {
this.z = Math.random() * canvas.width;
}
}
reset(initial = false) {
const w = canvas.width;
const h = canvas.height;
if (this.type === 'space') {
this.x = Math.random() * w;
this.y = Math.random() * h;
this.size = (Math.random() * 2 + 0.5) * sizeMult;
this.speed = (Math.random() * 0.5 + 0.1);
this.angle = Math.random() * Math.PI * 2;
const colors = ['#ffffff', '#ffe9c4', '#d4fbff', '#ffb3ba', '#ffffaa'];
this.color = colors[Math.floor(Math.random() * colors.length)];
this.life = Math.random();
}
else if (this.type === 'warp') {
this.x = (Math.random() - 0.5) * w * 2;
this.y = (Math.random() - 0.5) * h * 2;
this.z = w;
this.size = (Math.random() * 5 + 3) * sizeMult;
const colors = ['#ffffff', '#d4fbff', '#00ffff', '#ffb3ba'];
this.color = colors[Math.floor(Math.random() * colors.length)];
}
else if (this.type === 'rain') {
this.x = Math.random() * w;
this.y = initial ? Math.random() * h : -20;
this.size = (Math.random() * 1.5 + 0.5) * sizeMult;
this.speedY = (Math.random() * 10 + 15);
this.speedX = (Math.random() * 2 - 1);
this.color = 'rgba(174, 194, 224, 0.7)';
}
else if (this.type === 'snow') {
this.x = Math.random() * w;
this.y = initial ? Math.random() * h : -10;
this.size = (Math.random() * 3 + 1) * sizeMult;
this.speedY = (Math.random() * 1.5 + 1);
this.speedX = (Math.random() * 2 - 1);
this.color = 'rgba(255, 255, 255, 0.9)';
}
else if (this.type === 'smoke') {
this.x = Math.random() * w;
this.y = initial ? Math.random() * h : h + 50;
this.size = (Math.random() * 40 + 20) * sizeMult;
this.speedY = -(Math.random() * 1.5 + 0.5);
this.speedX = (Math.random() * 2 - 1);
this.life = Math.random() * 0.5 + 0.1;
this.color = `rgba(100, 100, 100, ${this.life * 0.15})`;
}
else if (this.type === 'fire') {
this.x = Math.random() * w;
this.y = h + (Math.random() * 20);
this.size = (Math.random() * 20 + 10) * sizeMult;
this.speedY = -(Math.random() * 3 + 1);
this.speedX = (Math.random() * 2 - 1);
this.life = 1.0;
const colors = ['#ff4500', '#ff8c00', '#ffd700'];
this.baseColor = colors[Math.floor(Math.random() * colors.length)];
}
else if (this.type === 'wind') {
this.x = initial ? Math.random() * w : -100;
this.y = Math.random() * h;
this.size = (Math.random() * 50 + 20) * sizeMult;
this.speedX = (Math.random() * 15 + 20);
this.color = 'rgba(255, 255, 255, 0.4)';
}
else if (this.type === 'lights') {
this.x = Math.random() * w;
this.y = Math.random() * h;
this.size = (Math.random() * 6 + 3) * sizeMult;
this.speedX = (Math.random() * 2 - 1);
this.speedY = (Math.random() * 2 - 1);
const colors = ['#ff00ff', '#00ffff', '#ffff00', '#00ff00', '#ff0055'];
this.color = colors[Math.floor(Math.random() * colors.length)];
this.glow = (Math.random() * 15 + 5) * sizeMult;
}
else if (this.type === 'fairydust') {
this.x = Math.random() * w;
this.y = initial ? Math.random() * h : h + 20;
this.size = (Math.random() * 3 + 1) * sizeMult;
this.speedY = -(Math.random() * 2 + 1);
this.speedX = (Math.random() * 2 - 1);
this.life = 1.0;
this.angle = Math.random() * Math.PI * 2;
this.color = '#ffb6c1';
}
else if (this.type === 'fireflies') {
this.x = Math.random() * w;
this.y = Math.random() * h;
this.size = (Math.random() * 3 + 1.5) * sizeMult;
this.speedX = (Math.random() * 1.5 - 0.75);
this.speedY = (Math.random() * 1.5 - 0.75);
this.color = '#c6ff00';
this.angle = Math.random() * Math.PI * 2;
}
else if (this.type === 'meteors') {
this.x = Math.random() * w * 1.5;
this.y = -50;
this.size = (Math.random() * 4 + 2) * sizeMult;
this.speedX = -(Math.random() * 10 + 10);
this.speedY = (Math.random() * 10 + 10);
this.length = (Math.random() * 60 + 30) * sizeMult;
const colors = ['#ffaa00', '#ff5500', '#ffffff'];
this.color = colors[Math.floor(Math.random() * colors.length)];
}
else if (this.type === 'lightning') {
this.life = initial ? Math.random() : 1.0;
this.points = [];
let currX = Math.random() * w;
let currY = 0;
this.points.push({x: currX, y: currY});
while(currY < h) {
currY += Math.random() * 40 + 20;
currX += (Math.random() * 60 - 30);
this.points.push({x: currX, y: currY});
}
}
else if (this.type === 'birds') {
this.x = initial ? Math.random() * w : -50;
this.y = Math.random() * (h * 0.7);
this.size = (Math.random() * 8 + 4) * sizeMult;
this.speedX = (Math.random() * 3 + 2);
this.speedY = (Math.random() * 1 - 0.5);
this.flapSpeed = Math.random() * 0.2 + 0.1;
this.flapPhase = Math.random() * Math.PI * 2;
}
else if (this.type === 'lasers') {
this.x = initial ? Math.random() * w : (Math.random() > 0.5 ? -100 : w + 100);
this.y = Math.random() * h;
this.size = (Math.random() * 3 + 2) * sizeMult;
this.speedX = (this.x < 0 ? 1 : -1) * (Math.random() * 20 + 20);
this.speedY = (Math.random() * 4 - 2);
this.length = (Math.random() * 100 + 50) * sizeMult;
const colors = ['#ff0000', '#00ff00', '#0088ff'];
this.color = colors[Math.floor(Math.random() * colors.length)];
}
else if (this.type === 'matrix') {
this.x = Math.floor(Math.random() * (w / 20)) * 20;
this.y = initial ? Math.random() * h : -20;
this.size = (Math.random() * 5 + 12) * sizeMult;
this.speedY = (Math.random() * 5 + 2);
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*';
this.char = chars.charAt(Math.floor(Math.random() * chars.length));
this.life = Math.random();
}
}
update() {
const w = canvas.width;
const h = canvas.height;
if (this.type === 'space') {
this.x += Math.cos(this.angle) * this.speed * speedMult;
this.y += Math.sin(this.angle) * this.speed * speedMult;
this.life -= 0.005 * speedMult;
if (this.life <= 0 || this.x < 0 || this.x > w || this.y < 0 || this.y > h) this.reset();
}
else if (this.type === 'warp') {
this.z -= (12 * speedMult);
if (this.z <= 0) this.reset();
}
else if (this.type === 'rain') {
this.x += this.speedX * speedMult;
this.y += this.speedY * speedMult;
if (this.y > h) this.reset();
}
else if (this.type === 'snow') {
this.x += (this.speedX + Math.sin(Date.now()*0.001)*0.5) * speedMult;
this.y += this.speedY * speedMult;
if (this.y > h) this.reset();
}
else if (this.type === 'smoke') {
this.x += this.speedX * speedMult;
this.y += this.speedY * speedMult;
this.size += 0.1 * speedMult;
if (this.y < -this.size) this.reset();
}
else if (this.type === 'fire') {
this.x += this.speedX * speedMult;
this.y += this.speedY * speedMult;
this.size -= 0.3 * speedMult;
this.life -= 0.02 * speedMult;
if (this.life <= 0 || this.size <= 0) this.reset();
}
else if (this.type === 'wind') {
this.x += this.speedX * speedMult;
if (this.x > w + this.size) this.reset();
}
else if (this.type === 'lights') {
this.x += this.speedX * speedMult;
this.y += this.speedY * speedMult;
if (this.x < 0 || this.x > w || this.y < 0 || this.y > h) this.reset();
}
else if (this.type === 'fairydust') {
this.angle += (Math.random() * 0.2 - 0.1) * speedMult;
this.x += Math.cos(this.angle) * 2 * speedMult;
this.y += this.speedY * speedMult;
this.life -= 0.01 * speedMult;
if (this.life <= 0 || this.y < -10) this.reset();
}
else if (this.type === 'fireflies') {
this.angle += (Math.random() * 0.2 - 0.1) * speedMult;
this.x += Math.cos(this.angle) * 2 * speedMult;
this.y += Math.sin(this.angle) * 2 * speedMult;
if (this.x < 0 || this.x > w || this.y < 0 || this.y > h) this.reset();
}
else if (this.type === 'meteors') {
this.x += this.speedX * speedMult;
this.y += this.speedY * speedMult;
if (this.y > h + this.length || this.x < -this.length) this.reset();
}
else if (this.type === 'lightning') {
this.life -= 0.05 * speedMult;
if (this.life <= 0 && Math.random() > 0.95) this.reset();
}
else if (this.type === 'birds') {
this.x += this.speedX * speedMult;
this.y += this.speedY * speedMult;
this.flapPhase += this.flapSpeed * speedMult;
if (this.x > w + 50 || this.y < -50 || this.y > h + 50) this.reset();
}
else if (this.type === 'lasers') {
this.x += this.speedX * speedMult;
this.y += this.speedY * speedMult;
if (this.x > w + this.length*2 || this.x < -this.length*2 || this.y < -100 || this.y > h + 100) this.reset();
}
else if (this.type === 'matrix') {
this.y += this.speedY * speedMult;
if (Math.random() < 0.05) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*';
this.char = chars.charAt(Math.floor(Math.random() * chars.length));
}
if (this.y > h + 20) this.reset();
}
}
draw(ctx) {
const w = canvas.width;
const h = canvas.height;
ctx.beginPath();
if (this.type === 'space') {
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.globalAlpha = Math.max(0, this.life);
ctx.fill();
ctx.globalAlpha = 1;
}
else if (this.type === 'warp') {
if (this.z <= 0) return;
let sx = (this.x / this.z) * w + w / 2;
let sy = (this.y / this.z) * h + h / 2;
let r = (w / this.z) * this.size * 0.015;
if (sx >= 0 && sx <= w && sy >= 0 && sy <= h) {
ctx.arc(sx, sy, Math.max(0.5, r), 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.strokeStyle = this.color;
ctx.lineWidth = Math.max(0.5, r * 0.8);
ctx.lineCap = 'round';
ctx.moveTo(sx, sy);
ctx.lineTo(sx - (this.x / this.z) * (w*0.12), sy - (this.y / this.z) * (h*0.12));
ctx.stroke();
}
}
else if (this.type === 'rain') {
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + this.speedX, this.y + this.size * 10);
ctx.strokeStyle = this.color;
ctx.lineWidth = 1;
ctx.stroke();
}
else if (this.type === 'snow') {
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
else if (this.type === 'smoke') {
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
else if (this.type === 'fire') {
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.baseColor;
ctx.globalAlpha = Math.max(0, this.life);
ctx.fill();
ctx.globalAlpha = 1;
}
else if (this.type === 'wind') {
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + this.size, this.y);
ctx.strokeStyle = this.color;
ctx.lineWidth = 2 * sizeMult;
ctx.stroke();
}
else if (this.type === 'lights') {
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.shadowBlur = this.glow;
ctx.shadowColor = this.color;
ctx.fill();
ctx.shadowBlur = 0;
}
else if (this.type === 'fairydust') {
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.globalAlpha = Math.max(0, this.life);
ctx.shadowBlur = 5 * sizeMult;
ctx.shadowColor = this.color;
ctx.fill();
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
}
else if (this.type === 'fireflies') {
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.shadowBlur = 10 * sizeMult;
ctx.shadowColor = this.color;
ctx.fill();
ctx.shadowBlur = 0;
}
else if (this.type === 'meteors') {
const grad = ctx.createLinearGradient(this.x, this.y, this.x - this.speedX*3, this.y - this.speedY*3);
grad.addColorStop(0, this.color);
grad.addColorStop(1, 'transparent');
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x - this.speedX*3, this.y - this.speedY*3);
ctx.strokeStyle = grad;
ctx.lineWidth = this.size;
ctx.lineCap = 'round';
ctx.stroke();
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff';
ctx.shadowBlur = 15;
ctx.shadowColor = this.color;
ctx.fill();
ctx.shadowBlur = 0;
}
else if (this.type === 'lightning') {
if (this.life > 0 && this.points.length > 0) {
ctx.moveTo(this.points[0].x, this.points[0].y);
for (let i = 1; i < this.points.length; i++) {
ctx.lineTo(this.points[i].x, this.points[i].y);
}
ctx.strokeStyle = '#e0f7fa';
ctx.lineWidth = (Math.random() * 3 + 1) * sizeMult;
ctx.shadowBlur = 20;
ctx.shadowColor = '#00e5ff';
ctx.globalAlpha = Math.max(0, this.life);
ctx.stroke();
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
}
}
else if (this.type === 'birds') {
const wingY = Math.sin(this.flapPhase) * this.size;
ctx.moveTo(this.x - this.size, this.y - wingY);
ctx.quadraticCurveTo(this.x, this.y, this.x + this.size, this.y - wingY);
ctx.strokeStyle = '#111';
ctx.lineWidth = 2 * sizeMult;
ctx.lineCap = 'round';
ctx.stroke();
}
else if (this.type === 'lasers') {
ctx.moveTo(this.x, this.y);
const dirX = this.speedX > 0 ? -1 : 1;
ctx.lineTo(this.x + dirX * this.length, this.y - this.speedY*2);
ctx.strokeStyle = this.color;
ctx.lineWidth = this.size;
ctx.lineCap = 'round';
ctx.shadowBlur = 15;
ctx.shadowColor = this.color;
ctx.stroke();
ctx.shadowBlur = 0;
}
else if (this.type === 'matrix') {
ctx.font = `${this.size}px monospace`;
ctx.fillStyle = `rgba(0, 255, 65, ${Math.max(0.2, this.life)})`;
ctx.shadowBlur = 5;
ctx.shadowColor = '#00ff41';
ctx.fillText(this.char, this.x, this.y);
ctx.shadowBlur = 0;
}
}
}
function initParticles() {
particles = [];
if (currentEffect === 'none' || currentEffect === 'glitch' || currentEffect === 'vibrate') return;
let count = 0;
if (currentEffect === 'space') count = 200;
else if (currentEffect === 'warp') count = 350;
else if (currentEffect === 'rain') count = 400;
else if (currentEffect === 'snow') count = 250;
else if (currentEffect === 'smoke') count = 50;
else if (currentEffect === 'fire') count = 150;
else if (currentEffect === 'wind') count = 30;
else if (currentEffect === 'lights') count = 40;
else if (currentEffect === 'fairydust') count = 100;
else if (currentEffect === 'fireflies') count = 60;
else if (currentEffect === 'meteors') count = 15;
else if (currentEffect === 'lightning') count = 3;
else if (currentEffect === 'birds') count = 15;
else if (currentEffect === 'lasers') count = 25;
else if (currentEffect === 'matrix') count = 100;
for (let i = 0; i < count; i++) {
particles.push(new Particle(currentEffect));
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const dragType = document.getElementById('vfx-drag-effect').value;
const effectLayer = document.getElementById('vfx-effect-layer').value;
// Ajusta camada Z do canvas conforme 'behind_piece' ou 'over_piece'
canvas.style.zIndex = effectLayer === 'behind_piece' ? '9999' : '100001';
// Se estiver segurando e a opção 'Manter Efeito Ativo' estiver marcada, continua gerando partículas mesmo parado
if (isDragging) {
const keepActiveEl = document.getElementById('vfx-eff-keep-active');
if (keepActiveEl && keepActiveEl.checked) {
spawnDragParticle();
}
}
// Função encapsulada para desenhar a imagem da peça
const drawPieceImage = () => {
if (dragType === 'image' && isDragging && customImgLoaded && boardEl) {
const scale = parseFloat(document.getElementById('vfx-img-scale').value);
const offX = parseFloat(document.getElementById('vfx-img-x').value);
const offY = parseFloat(document.getElementById('vfx-img-y').value);
let imgW = customImg.width * scale;
let imgH = customImg.height * scale;
let imgX = lastDragMousePos.x + offX - imgW / 2;
let imgY = lastDragMousePos.y + offY - imgH / 2;
ctx.drawImage(customImg, imgX, imgY, imgW, imgH);
}
};
// 1. Desenha as partículas globais do tabuleiro
for (let p of particles) {
p.update();
p.draw(ctx);
}
// 2. Se a configuração for POR CIMA, desenhamos a imagem
if (effectLayer === 'over_piece') {
drawPieceImage();
}
// 3. Desenha as partículas do rastro / arrasto
for (let i = dragParticles.length - 1; i >= 0; i--) {
let dp = dragParticles[i];
dp.update();
dp.draw(ctx);
if (dp.life <= 0) dragParticles.splice(i, 1);
}
// 4. Se a configuração for ATRÁS, desenhamos a imagem
if (effectLayer === 'behind_piece') {
drawPieceImage();
}
if (currentEffect === 'glitch' && Math.random() < 0.05 * speedMult) {
ctx.fillStyle = `rgba(${Math.random()*255},${Math.random()*255},${Math.random()*255},0.2)`;
ctx.fillRect(Math.random()*canvas.width, Math.random()*canvas.height, Math.random()*100, Math.random()*20);
}
animationId = requestAnimationFrame(animate);
}
setInterval(() => {
updateCanvasBounds();
if (currentEffect === 'glitch' || currentEffect === 'vibrate') {
handleBoardEffects();
}
}, 500);
window.addEventListener('resize', updateCanvasBounds);
loadSettings();
initParticles();
animate();
// --- INÍCIO DA ADIÇÃO DO MURAL DE IMAGENS ---
const imageWallHTML = `
<label style="color: #00e5ff; font-weight: bold; margin-top: 15px;">Mural de Imagens Salvas:</label>
<div id="vfx-image-wall" style="display: flex; gap: 8px; overflow-x: auto; padding: 10px 5px; margin-bottom: 10px; background: rgba(0,0,0,0.4); border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); min-height: 50px; align-items: center;">
</div>
<div style="display: flex; gap: 5px; margin-bottom: 15px;">
<button id="vfx-clear-wall-btn" type="button" class="vfx-btn-action" style="flex:1; background:#ff4444; color:#fff;">Limpar Imagens Baixadas</button>
</div>
`;
const imgCfgContainer = document.getElementById('vfx-image-cfg');
imgCfgContainer.insertAdjacentHTML('afterbegin', imageWallHTML);
// Constante com os links do github (Adicione mais links aqui se precisar)
const defaultImages = [
"https://raw.githubusercontent.com/dasfar45/chess-assets-2/main/jetpack.png",
"https://raw.githubusercontent.com/dasfar45/chess-assets-2/main/asas.png?",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Abutre%20Louco.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Asas%20Atro%C3%A7as.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Butterfly.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Joias%20Misticas.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Mogged.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Money%2C%20Money%2C%20Money.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/asas%20vermelha.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/jetpack%202.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Espadas%20Da%20Conspira%C3%A7%C3%A3o.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/Spiral%20Mortif%C3%A9ra.png",
"https://raw.githubusercontent.com/dasfar45/Assets-VFX/main/capa%20doutor%20estranho.png"
];
// Configurações de fábrica para o Jetpack e Asas
const defaultWallSettings = {
'vfx-img-x': "1",
'vfx-img-y': "15",
'vfx-img-scale': "0.2",
'vfx-eff-size': "0.4",
'vfx-eff-int': "20",
'vfx-eff-jetpack': true,
'vfx-eff-fire': false,
'vfx-eff-smoke': false,
'vfx-eff-sparkles': false,
'vfx-eff-trail': false,
'vfx-eff-snow': false,
'vfx-eff-fairydust': false,
'vfx-eff-magiclights': false,
'vfx-eff-fireflies': false,
'vfx-eff-dash': true,
'vfx-eff-keep-active': true,
'vfx-effect-layer': 'behind_piece'
};
let userWallImages = JSON.parse(localStorage.getItem('vfx_user_wall_images')) || [];
let wallConfigs = JSON.parse(localStorage.getItem('vfx_wall_configs')) || {};
let currentWallImageId = null;
// Garante que as configurações de fábrica estejam salvas para as imagens do github
defaultImages.forEach(src => {
if (!wallConfigs[src]) {
wallConfigs[src] = { ...defaultWallSettings };
}
});
const configKeys = Object.keys(defaultWallSettings);
function renderImageWall() {
const wall = document.getElementById('vfx-image-wall');
wall.innerHTML = '';
const allImages = [...defaultImages.map(src => ({ id: src, src: src, isDefault: true })), ...userWallImages];
allImages.forEach(imgData => {
const imgEl = document.createElement('img');
imgEl.src = imgData.src;
imgEl.style.width = '45px';
imgEl.style.height = '45px';
imgEl.style.objectFit = 'contain';
imgEl.style.cursor = 'pointer';
imgEl.style.transition = 'all 0.2s';
imgEl.style.border = currentWallImageId === imgData.id ? '2px solid #00e5ff' : '2px solid transparent';
imgEl.style.borderRadius = '6px';
imgEl.style.background = 'rgba(255,255,255,0.05)';
imgEl.style.padding = '2px';
if (currentWallImageId === imgData.id) {
imgEl.style.boxShadow = '0 0 8px rgba(0,229,255,0.6)';
}
imgEl.onclick = () => selectWallImage(imgData.id, imgData.src);
wall.appendChild(imgEl);
});
}
function applyConfigToUI(id) {
const conf = wallConfigs[id] || defaultWallSettings;
configKeys.forEach(key => {
const el = document.getElementById(key);
if (el) {
if (el.type === 'checkbox') {
el.checked = conf[key] !== undefined ? conf[key] : defaultWallSettings[key];
} else {
el.value = conf[key] !== undefined ? conf[key] : defaultWallSettings[key];
}
el.dispatchEvent(new Event('input')); // Atualiza os sliders na interface
}
});
}
function selectWallImage(id, src) {
currentWallImageId = id;
localStorage.setItem('vfx_last_wall_id', id);
renderImageWall();
customImg = new Image();
customImg.crossOrigin = "Anonymous";
customImg.onload = () => {
customImgLoaded = true;
applyConfigToUI(id);
renderPreview();
};
customImg.onerror = () => {
customImgLoaded = false;
};
customImg.src = src;
}
function saveCurrentWallConfig() {
if (!currentWallImageId) return;
let conf = {};
configKeys.forEach(key => {
const el = document.getElementById(key);
if (el) {
conf[key] = el.type === 'checkbox' ? el.checked : el.value;
}
});
wallConfigs[currentWallImageId] = conf;
localStorage.setItem('vfx_wall_configs', JSON.stringify(wallConfigs));
}
// Adiciona listener nos controles para salvar a config da imagem atual em tempo real
configKeys.forEach(key => {
const el = document.getElementById(key);
if (el) {
el.addEventListener('change', saveCurrentWallConfig);
el.addEventListener('input', saveCurrentWallConfig);
}
});
// Sobrescreve o input de arquivo clonando para remover o listener antigo que apagava a imagem atual
const oldFileInput = document.getElementById('vfx-img-file');
const newFileInput = oldFileInput.cloneNode(true);
oldFileInput.parentNode.replaceChild(newFileInput, oldFileInput);
newFileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (evt) => {
const src = evt.target.result;
const id = 'custom_' + Date.now();
userWallImages.push({ id: id, src: src, isDefault: false });
localStorage.setItem('vfx_user_wall_images', JSON.stringify(userWallImages));
// Aplica a config base de fábrica para novas imagens
wallConfigs[id] = { ...defaultWallSettings };
localStorage.setItem('vfx_wall_configs', JSON.stringify(wallConfigs));
selectWallImage(id, src);
};
reader.readAsDataURL(file);
}
});
// Botão para limpar imagens salvas pelo usuário
document.getElementById('vfx-clear-wall-btn').addEventListener('click', () => {
if(confirm("Deseja remover todas as imagens que você enviou do mural?")) {
userWallImages = [];
localStorage.setItem('vfx_user_wall_images', JSON.stringify([]));
selectWallImage(defaultImages[0], defaultImages[0]);
}
});
// Inicia o Mural carregando a última imagem escolhida ou a primeira do GitHub
const savedLastId = localStorage.getItem('vfx_last_wall_id');
if (savedLastId && (defaultImages.includes(savedLastId) || userWallImages.find(i => i.id === savedLastId))) {
const imgObj = userWallImages.find(i => i.id === savedLastId) || { src: savedLastId };
selectWallImage(savedLastId, imgObj.src);
} else {
selectWallImage(defaultImages[0], defaultImages[0]);
}
// --- FIM DA ADIÇÃO DO MURAL DE IMAGENS ---
// =========================================================================
// --- SISTEMA UNIFICADO DE EFEITOS NO TABULEIRO (VFX BOARD OVERLAY) ---
// =========================================================================
// Injeção de Estilos CSS para a lista unificada
const styleElem = document.createElement('style');
styleElem.innerHTML = `
.vfx-eff-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 8px;
margin-bottom: 4px;
background: rgba(255,255,255,0.05);
border-radius: 4px;
border: 1px solid rgba(255,255,255,0.05);
}
.vfx-eff-row:hover {
background: rgba(255,255,255,0.1);
}
.vfx-eff-row input[type="color"] {
width: 26px;
height: 22px;
border: none;
padding: 0;
background: transparent;
cursor: pointer;
border-radius: 3px;
}
`;
document.head.appendChild(styleElem);
// 1. INJEÇÃO DA INTERFACE UNIFICADA (UI)
// 1.1 Lista Única de Efeitos Globais na Aba "Gerais"
const tabGeral = document.getElementById('tab-geral');
if (tabGeral) {
tabGeral.insertAdjacentHTML('beforeend', `
<hr style="border:0; border-top:1px solid rgba(255,255,255,0.1); margin: 12px 0;">
<label style="font-weight: bold; color: #00e5ff; display: block; margin-bottom: 6px;">Efeitos Globais do Tabuleiro (Ative vários):</label>
<div id="vfx-global-effects-list" style="max-height: 220px; overflow-y: auto; background: rgba(0,0,0,0.3); padding: 6px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); margin-bottom: 10px;">
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="petals"> Pétalas de Cerejeira</label><input type="color" class="vfx-effect-color vfx-save" data-effect="petals" value="#ffb7c5"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="leaves"> Folhas ao Vento</label><input type="color" class="vfx-effect-color vfx-save" data-effect="leaves" value="#ffaa33"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="dust"> Poeira ao Vento</label><input type="color" class="vfx-effect-color vfx-save" data-effect="dust" value="#ffe89c"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="feathers"> Penas ao Vento</label><input type="color" class="vfx-effect-color vfx-save" data-effect="feathers" value="#ffffff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="butterflies"> Borboletas</label><input type="color" class="vfx-effect-color vfx-save" data-effect="butterflies" value="#00e5ff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="magic_dust"> Poeira Mágica</label><input type="color" class="vfx-effect-color vfx-save" data-effect="magic_dust" value="#e066ff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="shooting_stars"> Estrelas Cadentes</label><input type="color" class="vfx-effect-color vfx-save" data-effect="shooting_stars" value="#ffffff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="vortex"> Vórtice Mágico</label><input type="color" class="vfx-effect-color vfx-save" data-effect="vortex" value="#00f0ff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="constellations"> Constelações</label><input type="color" class="vfx-effect-color vfx-save" data-effect="constellations" value="#77aaff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="rainbow"> Arco-Íris em Movimento</label><input type="color" class="vfx-effect-color vfx-save" data-effect="rainbow" value="#ff007f"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="snow"> Neve</label><input type="color" class="vfx-effect-color vfx-save" data-effect="snow" value="#ffffff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="rain"> Chuva</label><input type="color" class="vfx-effect-color vfx-save" data-effect="rain" value="#88ccff"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="matrix"> Matrix</label><input type="color" class="vfx-effect-color vfx-save" data-effect="matrix" value="#00ff66"></div>
<div class="vfx-eff-row"><label class="vfx-checkbox-label" style="margin:0 !important;"><input type="checkbox" class="vfx-effect-enable vfx-save" data-effect="stars"> Brilhos / Stardust</label><input type="color" class="vfx-effect-color vfx-save" data-effect="stars" value="#ffffff"></div>
</div>
<label>Intensidade Geral dos Efeitos: <span id="vfx-global-intensity-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-global-intensity" class="vfx-save" min="0.1" max="3" step="0.1" value="1">
<label>Direção do Fluxo:</label>
<select id="vfx-global-direction" class="vfx-save">
<option value="default">Padrão</option>
<option value="horizontal">Horizontal</option>
<option value="vertical">Vertical</option>
<option value="diagonal_down">Diagonal Descendente</option>
<option value="diagonal_up">Diagonal Ascendente</option>
</select>
`);
}
// =========================================================================
// 1.2 SISTEMA SEGURO E ISOLADO DE GIRAR/TREMER (ANTI-TRAVAMENTO)
// =========================================================================
(function sistemaSeguroGirarTremer() {
const CHAVE_COFRE = '__VFX_GIRAR_TREMER_SALVO__';
// Valores de fábrica
const estadoPadrao = {
'vfx-eff-rotate': false,
'vfx-rotate-speed': '1',
'vfx-rotate-int': '100',
'vfx-eff-shake': false,
'vfx-shake-speed': '10',
'vfx-shake-int': '5'
};
// Lê o que você salvou no navegador
function lerCofre() {
try {
let dados = localStorage.getItem(CHAVE_COFRE);
return dados ? { ...estadoPadrao, ...JSON.parse(dados) } : { ...estadoPadrao };
} catch (e) {
return { ...estadoPadrao };
}
}
// Salva silenciosamente sempre que você interagir
function aoInteragir(e) {
let id = e.target.id;
if (estadoPadrao[id] !== undefined) {
let estado = lerCofre();
estado[id] = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
try {
localStorage.setItem(CHAVE_COFRE, JSON.stringify(estado));
} catch(err) {}
}
}
// Sistema leve que checa a tela a cada 500ms (Não trava o navegador)
setInterval(() => {
let container = document.getElementById('vfx-image-cfg');
if (!container) return; // Se o menu estiver fechado, não gasta processamento
let imgCheckGroup = container.querySelector('.vfx-checkbox-group');
if (!imgCheckGroup) return;
// 1. Injeta os controles apenas se eles sumirem da tela
if (!document.getElementById('vfx-eff-rotate')) {
imgCheckGroup.insertAdjacentHTML('beforeend', `
<label class="vfx-checkbox-label" style="margin-top: 4px; color:#00e5ff;">
<input type="checkbox" id="vfx-eff-rotate"> Girar Imagem
</label>
<label class="vfx-checkbox-label" style="color:#ffaa33;">
<input type="checkbox" id="vfx-eff-shake"> Tremer Imagem
</label>
`);
container.insertAdjacentHTML('beforeend', `
<div id="vfx-rotate-cfg" class="vfx-sub-config" style="display: none; background: rgba(0,0,0,0.3); padding: 8px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); margin-top: 8px;">
<label>Velocidade do Giro: <span id="vfx-rotate-speed-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-rotate-speed" min="0.1" max="5" step="0.1" value="1">
<label>Intensidade do Giro: <span id="vfx-rotate-int-val" style="color:#00e5ff;">100%</span></label>
<input type="range" id="vfx-rotate-int" min="10" max="300" step="10" value="100">
</div>
<div id="vfx-shake-cfg" class="vfx-sub-config" style="display: none; background: rgba(0,0,0,0.3); padding: 8px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); margin-top: 8px;">
<label>Velocidade do Tremer: <span id="vfx-shake-speed-val" style="color:#00e5ff;">10</span></label>
<input type="range" id="vfx-shake-speed" min="1" max="30" step="1" value="10">
<label>Intensidade do Tremer: <span id="vfx-shake-int-val" style="color:#00e5ff;">5px</span></label>
<input type="range" id="vfx-shake-int" min="1" max="25" step="1" value="5">
</div>
`);
// Conecta a função de salvar no exato momento que você mexe
Object.keys(estadoPadrao).forEach(id => {
let el = document.getElementById(id);
if (el) {
el.addEventListener('change', aoInteragir);
el.addEventListener('input', aoInteragir);
}
});
}
// 2. Mantém a tela com os valores que você salvou (Só muda se for diferente, evitando travamentos)
let salvos = lerCofre();
['vfx-eff-rotate', 'vfx-eff-shake'].forEach(id => {
let el = document.getElementById(id);
if (el && el.checked !== Boolean(salvos[id])) {
el.checked = Boolean(salvos[id]);
}
});
['vfx-rotate-speed', 'vfx-rotate-int', 'vfx-shake-speed', 'vfx-shake-int'].forEach(id => {
let el = document.getElementById(id);
if (el && el.value !== String(salvos[id])) {
el.value = String(salvos[id]);
}
});
// 3. Atualiza os painéis visuais
let rotCfg = document.getElementById('vfx-rotate-cfg');
let displayRot = salvos['vfx-eff-rotate'] ? 'block' : 'none';
if (rotCfg && rotCfg.style.display !== displayRot) rotCfg.style.display = displayRot;
let rotSpeedVal = document.getElementById('vfx-rotate-speed-val');
let txtRotSpeed = salvos['vfx-rotate-speed'] + 'x';
if (rotSpeedVal && rotSpeedVal.innerText !== txtRotSpeed) rotSpeedVal.innerText = txtRotSpeed;
let rotIntVal = document.getElementById('vfx-rotate-int-val');
let txtRotInt = salvos['vfx-rotate-int'] + '%';
if (rotIntVal && rotIntVal.innerText !== txtRotInt) rotIntVal.innerText = txtRotInt;
let shakeCfg = document.getElementById('vfx-shake-cfg');
let displayShake = salvos['vfx-eff-shake'] ? 'block' : 'none';
if (shakeCfg && shakeCfg.style.display !== displayShake) shakeCfg.style.display = displayShake;
let shakeSpeedVal = document.getElementById('vfx-shake-speed-val');
let txtShakeSpeed = String(salvos['vfx-shake-speed']);
if (shakeSpeedVal && shakeSpeedVal.innerText !== txtShakeSpeed) shakeSpeedVal.innerText = txtShakeSpeed;
let shakeIntVal = document.getElementById('vfx-shake-int-val');
let txtShakeInt = salvos['vfx-shake-int'] + 'px';
if (shakeIntVal && shakeIntVal.innerText !== txtShakeInt) shakeIntVal.innerText = txtShakeInt;
}, 500); // 500 milissegundos
})();
// 1.3 Iluminação Hover das Casas
const lightColorInput = document.getElementById('vfx-light-color');
if (lightColorInput && lightColorInput.parentNode) {
lightColorInput.insertAdjacentHTML('afterend', `
<label style="margin-top: 10px;">Modo de Cor da Iluminação:</label>
<select id="vfx-light-mode" class="vfx-save">
<option value="solid">Cor Fixa</option>
<option value="rgb_smooth">RGB Suave (Degradê Completo)</option>
<option value="rgb_custom">RGB Cores Isoladas/Específicas</option>
</select>
<div id="vfx-hover-rgb-cfg" class="vfx-sub-config" style="display: none;">
<label>Velocidade de Transição do RGB: <span id="vfx-hover-rgb-speed-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-hover-rgb-speed" class="vfx-save" min="0.1" max="5" step="0.1" value="1">
<div id="vfx-hover-custom-rgb-container" style="display: none; margin-top: 8px;">
<label>Cores Isoladas do RGB:</label>
<div style="display: flex; gap: 5px; margin-top: 4px;">
<input type="color" id="vfx-hover-rgb-c1" class="vfx-save" value="#ff8800">
<input type="color" id="vfx-hover-rgb-c2" class="vfx-save" value="#ff0000">
<input type="color" id="vfx-hover-rgb-c3" class="vfx-save" value="#8800ff">
<input type="color" id="vfx-hover-rgb-c4" class="vfx-save" value="#00e5ff">
</div>
</div>
</div>
`);
}
// 2. FUNÇÕES AUXILIARES DE CORES E TABULEIRO
function hexToRgb(hex) {
if (!hex) return { r: 255, g: 255, b: 255 };
hex = hex.replace('#', '');
if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
const num = parseInt(hex, 16);
return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
}
function hslToRgb(h, s, l) {
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color);
};
return { r: f(0), g: f(8), b: f(4) };
}
function interpolateColors(c1Hex, c2Hex, factor) {
const rgb1 = hexToRgb(c1Hex);
const rgb2 = hexToRgb(c2Hex);
return {
r: Math.round(rgb1.r + factor * (rgb2.r - rgb1.r)),
g: Math.round(rgb1.g + factor * (rgb2.g - rgb1.g)),
b: Math.round(rgb1.b + factor * (rgb2.b - rgb1.b))
};
}
// Retorna dinamicamente os limites do tabuleiro
function getBoardRect() {
const boardEl = document.querySelector('chess-board, #board, .board, .cg-wrap, #chessboard, .board-area');
if (boardEl) {
return boardEl.getBoundingClientRect();
}
const size = Math.min(window.innerWidth, window.innerHeight) * 0.8;
return {
left: (window.innerWidth - size) / 2,
top: (window.innerHeight - size) / 2,
width: size,
height: size
};
}
// 3. DESENHO SEGURO DA IMAGEM DO MURAL (GIRAR E TREMER)
let currentRotationAngle = 0;
let lastShakeTime = 0;
let currentShakeOffsetX = 0;
let currentShakeOffsetY = 0;
const originalDrawImage = ctx.drawImage;
ctx.drawImage = function(image, ...args) {
if (!image) return;
try {
const isRotate = document.getElementById('vfx-eff-rotate')?.checked;
const isShake = document.getElementById('vfx-eff-shake')?.checked;
if (isRotate || isShake) {
let dx, dy, dw, dh;
if (args.length === 2) {
dx = args[0]; dy = args[1];
dw = image.naturalWidth || image.width || 50;
dh = image.naturalHeight || image.height || 50;
} else if (args.length === 4) {
dx = args[0]; dy = args[1]; dw = args[2]; dh = args[3];
} else if (args.length === 8) {
dx = args[4]; dy = args[5]; dw = args[6]; dh = args[7];
} else {
return originalDrawImage.apply(this, [image, ...args]);
}
if (isRotate) {
const speed = parseFloat(document.getElementById('vfx-rotate-speed')?.value || 1);
const intensity = parseFloat(document.getElementById('vfx-rotate-int')?.value || 100) / 100;
currentRotationAngle += (0.02 * speed * intensity);
}
if (isShake) {
const shakeSpeed = parseFloat(document.getElementById('vfx-shake-speed')?.value || 10);
const shakeInt = parseFloat(document.getElementById('vfx-shake-int')?.value || 5);
const now = Date.now();
if (now - lastShakeTime > (1000 / shakeSpeed)) {
currentShakeOffsetX = (Math.random() - 0.5) * 2 * shakeInt;
currentShakeOffsetY = (Math.random() - 0.5) * 2 * shakeInt;
lastShakeTime = now;
}
} else {
currentShakeOffsetX = 0;
currentShakeOffsetY = 0;
}
const centerX = dx + dw / 2 + currentShakeOffsetX;
const centerY = dy + dh / 2 + currentShakeOffsetY;
this.save();
this.translate(centerX, centerY);
if (isRotate) this.rotate(currentRotationAngle);
if (args.length === 8) {
originalDrawImage.call(this, image, args[0], args[1], args[2], args[3], -dw / 2, -dh / 2, dw, dh);
} else {
originalDrawImage.call(this, image, -dw / 2, -dh / 2, dw, dh);
}
this.restore();
return;
}
} catch (e) {}
return originalDrawImage.apply(this, [image, ...args]);
};
// 4. EFEITOS VETORIAIS PUROS (SEM EMOJIS, TOTALMENTE LEVES)
class BoardParticle {
constructor(type, rect, color) {
this.type = type;
this.color = color || '#ffffff';
this.dead = false;
this.time = Math.random() * 100;
this.x = rect.left + Math.random() * rect.width;
this.y = rect.top + Math.random() * rect.height;
if (type === 'snow') {
this.size = Math.random() * 2.5 + 1.5;
this.speedY = Math.random() * 1.2 + 0.5;
this.speedX = Math.random() * 0.4 - 0.2;
} else if (type === 'rain') {
this.size = Math.random() * 10 + 6;
this.speedY = Math.random() * 7 + 6;
this.speedX = -1;
} else if (type === 'matrix') {
this.size = Math.random() * 4 + 10;
this.speedY = Math.random() * 2.5 + 1.5;
this.speedX = 0;
this.char = String.fromCharCode(0x30A0 + Math.floor(Math.random() * 96));
} else if (type === 'stars') {
this.size = Math.random() * 2.5 + 1;
this.speedY = 0;
this.speedX = 0;
this.alpha = Math.random();
this.alphaSpeed = Math.random() * 0.03 + 0.01;
} else if (type === 'petals') {
this.size = Math.random() * 5 + 4;
this.speedY = Math.random() * 1 + 0.5;
this.speedX = Math.random() * 0.6 - 0.3;
this.rotation = Math.random() * Math.PI * 2;
this.rotSpeed = (Math.random() - 0.5) * 0.04;
} else if (type === 'leaves') {
this.size = Math.random() * 6 + 5;
this.speedY = Math.random() * 1.2 + 0.6;
this.speedX = Math.random() * 1 + 0.2;
this.rotation = Math.random() * Math.PI * 2;
this.rotSpeed = (Math.random() - 0.5) * 0.05;
} else if (type === 'dust') {
this.size = Math.random() * 2 + 1;
this.speedY = Math.random() * 0.4 + 0.1;
this.speedX = Math.random() * 0.6 - 0.3;
} else if (type === 'feathers') {
this.size = Math.random() * 8 + 6;
this.speedY = Math.random() * 0.7 + 0.3;
this.speedX = 0;
this.rotation = 0;
} else if (type === 'butterflies') {
this.size = Math.random() * 4 + 4;
this.speedY = Math.random() * 0.6 + 0.2;
this.speedX = Math.random() * 1 - 0.5;
this.wingAngle = 0;
} else if (type === 'magic_dust') {
this.size = Math.random() * 3 + 2;
this.speedY = -(Math.random() * 0.8 + 0.2);
this.speedX = Math.random() * 0.8 - 0.4;
this.pulse = Math.random() * Math.PI;
} else if (type === 'shooting_stars') {
this.x = rect.left + Math.random() * rect.width;
this.y = rect.top + Math.random() * (rect.height * 0.4);
this.length = Math.random() * 40 + 20;
this.speedX = Math.random() * 6 + 4;
this.speedY = Math.random() * 3 + 2;
} else if (type === 'vortex') {
this.angle = Math.random() * Math.PI * 2;
this.radius = Math.random() * (rect.width * 0.45);
this.angularSpeed = Math.random() * 0.03 + 0.01;
this.radialSpeed = Math.random() * 0.3 + 0.1;
this.size = Math.random() * 2.5 + 1;
} else if (type === 'constellations') {
this.size = Math.random() * 2 + 1.5;
this.speedX = (Math.random() - 0.5) * 0.6;
this.speedY = (Math.random() - 0.5) * 0.6;
}
}
update(rect, globalDir, globalInt) {
this.time += 0.05;
let vx = this.speedX || 0;
let vy = this.speedY || 0;
if (this.type === 'vortex') {
this.angle += this.angularSpeed * globalInt;
this.radius -= this.radialSpeed * globalInt;
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
this.x = centerX + Math.cos(this.angle) * this.radius;
this.y = centerY + Math.sin(this.angle) * this.radius;
if (this.radius <= 5) this.dead = true;
return;
}
if (this.type === 'petals') {
vx += Math.sin(this.time) * 0.4;
this.rotation += this.rotSpeed;
} else if (this.type === 'leaves') {
vx += Math.sin(this.time * 0.8) * 0.6;
this.rotation += this.rotSpeed;
} else if (this.type === 'feathers') {
vx = Math.sin(this.time * 0.6) * 1.2;
this.rotation = Math.sin(this.time * 0.6) * 0.3;
} else if (this.type === 'butterflies') {
vx += Math.cos(this.time) * 0.6;
vy += Math.sin(this.time * 1.5) * 0.3;
this.wingAngle = Math.sin(this.time * 12);
} else if (this.type === 'magic_dust') {
this.pulse += 0.05;
} else if (this.type === 'stars') {
this.alpha += this.alphaSpeed;
if (this.alpha > 1 || this.alpha < 0.2) this.alphaSpeed = -this.alphaSpeed;
}
if (globalDir === 'horizontal') { vx = Math.abs(vy || 2); vy = 0; }
else if (globalDir === 'vertical') { vx = 0; vy = Math.abs(vy || 2); }
else if (globalDir === 'diagonal_down') { vx = Math.abs(vy || 2); vy = Math.abs(vy || 2); }
else if (globalDir === 'diagonal_up') { vx = Math.abs(vy || 2); vy = -Math.abs(vy || 2); }
this.x += vx * globalInt;
this.y += vy * globalInt;
// Se sair da área do tabuleiro, morre para dar lugar a outra
if (this.x < rect.left - 20 || this.x > rect.left + rect.width + 20 ||
this.y < rect.top - 20 || this.y > rect.top + rect.height + 20) {
this.dead = true;
}
}
draw(ctx) {
ctx.save();
const rgb = hexToRgb(this.color);
if (this.type === 'petals') { // Pétala vetorial
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`;
ctx.beginPath();
ctx.ellipse(0, 0, this.size, this.size / 2, 0, 0, Math.PI * 2);
ctx.fill();
} else if (this.type === 'leaves') { // Folha vetorial
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.8)`;
ctx.beginPath();
ctx.moveTo(0, -this.size);
ctx.quadraticCurveTo(this.size, 0, 0, this.size);
ctx.quadraticCurveTo(-this.size, 0, 0, -this.size);
ctx.fill();
} else if (this.type === 'dust') { // Poeira suave
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.5)`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
} else if (this.type === 'feathers') { // Pena vetorial
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.7)`;
ctx.lineWidth = 1.2;
ctx.beginPath();
ctx.moveTo(0, -this.size);
ctx.quadraticCurveTo(this.size / 2, 0, 0, this.size);
ctx.stroke();
} else if (this.type === 'butterflies') { // Borboleta vetorial
ctx.translate(this.x, this.y);
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.85)`;
const wingScale = Math.abs(this.wingAngle || 1);
ctx.beginPath();
ctx.ellipse(-this.size * wingScale, 0, this.size, this.size / 1.5, 0, 0, Math.PI * 2);
ctx.ellipse(this.size * wingScale, 0, this.size, this.size / 1.5, 0, 0, Math.PI * 2);
ctx.fill();
} else if (this.type === 'magic_dust') { // Poeira mágica com brilho
const pAlpha = 0.3 + Math.sin(this.pulse) * 0.4;
const grad = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 2);
grad.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${pAlpha})`);
grad.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * 2, 0, Math.PI * 2);
ctx.fill();
} else if (this.type === 'shooting_stars') { // Estrela cadente
const grad = ctx.createLinearGradient(this.x, this.y, this.x - this.speedX * 5, this.y - this.speedY * 5);
grad.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)`);
grad.addColorStop(1, 'rgba(0,0,0,0)');
ctx.strokeStyle = grad;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x - this.speedX * 5, this.y - this.speedY * 5);
ctx.stroke();
} else if (this.type === 'vortex' || this.type === 'snow' || this.type === 'constellations') {
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.8)`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
} else if (this.type === 'rain') {
ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.65)`;
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x - 1, this.y + this.size);
ctx.stroke();
} else if (this.type === 'matrix') {
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.85)`;
ctx.font = `${this.size}px monospace`;
ctx.fillText(this.char, this.x, this.y);
} else if (this.type === 'stars') {
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${this.alpha || 0.8})`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
}
// 5. GERENCIADOR DO CANVAS DO TABULEIRO (RESTRITO AO TABULEIRO)
let vfxGlobalCanvas = document.getElementById('vfx-global-canvas');
if (!vfxGlobalCanvas) {
vfxGlobalCanvas = document.createElement('canvas');
vfxGlobalCanvas.id = 'vfx-global-canvas';
vfxGlobalCanvas.style.position = 'fixed';
vfxGlobalCanvas.style.top = '0';
vfxGlobalCanvas.style.left = '0';
vfxGlobalCanvas.style.width = '100vw';
vfxGlobalCanvas.style.height = '100vh';
vfxGlobalCanvas.style.pointerEvents = 'none';
vfxGlobalCanvas.style.zIndex = '9999';
document.body.appendChild(vfxGlobalCanvas);
}
const globalVfxCtx = vfxGlobalCanvas.getContext('2d');
function resizeGlobalVFXCanvas() {
if (vfxGlobalCanvas) {
vfxGlobalCanvas.width = window.innerWidth;
vfxGlobalCanvas.height = window.innerHeight;
}
}
window.addEventListener('resize', resizeGlobalVFXCanvas);
resizeGlobalVFXCanvas();
const globalParticles = [];
let rainbowAngle = 0;
function renderGlobalVFXLoop() {
if (globalVfxCtx && vfxGlobalCanvas) {
globalVfxCtx.clearRect(0, 0, vfxGlobalCanvas.width, vfxGlobalCanvas.height);
const rect = getBoardRect();
// MÁSCARA DE RECORTE: Garante que NADA saia do tabuleiro
globalVfxCtx.save();
globalVfxCtx.beginPath();
globalVfxCtx.rect(rect.left, rect.top, rect.width, rect.height);
globalVfxCtx.clip();
const activeEffects = [];
const effectColors = {};
document.querySelectorAll('.vfx-effect-enable').forEach(cb => {
if (cb.checked) {
const eff = cb.getAttribute('data-effect');
activeEffects.push(eff);
const colorInput = document.querySelector(`.vfx-effect-color[data-effect="${eff}"]`);
effectColors[eff] = colorInput ? colorInput.value : '#ffffff';
}
});
// Efeito Especial: Arco-Íris em Movimento (Desenho de fundo no tabuleiro)
if (activeEffects.includes('rainbow')) {
rainbowAngle += 0.005;
const rColor = hexToRgb(effectColors['rainbow'] || '#ff007f');
const grad = globalVfxCtx.createLinearGradient(
rect.left + Math.cos(rainbowAngle) * rect.width,
rect.top + Math.sin(rainbowAngle) * rect.height,
rect.left + rect.width,
rect.top + rect.height
);
grad.addColorStop(0, `rgba(${rColor.r}, ${rColor.g}, ${rColor.b}, 0.15)`);
grad.addColorStop(0.5, 'rgba(0, 229, 255, 0.15)');
grad.addColorStop(1, 'rgba(255, 230, 0, 0.15)');
globalVfxCtx.fillStyle = grad;
globalVfxCtx.fillRect(rect.left, rect.top, rect.width, rect.height);
}
// Limpa partículas inativas
for (let i = globalParticles.length - 1; i >= 0; i--) {
if (!activeEffects.includes(globalParticles[i].type)) {
globalParticles.splice(i, 1);
}
}
// Criação de novas partículas
activeEffects.forEach(eff => {
if (eff === 'rainbow') return;
const count = globalParticles.filter(p => p.type === eff).length;
const limit = (eff === 'dust' || eff === 'snow' || eff === 'rain') ? 40 : 20;
if (count < limit) {
globalParticles.push(new BoardParticle(eff, rect, effectColors[eff]));
}
});
const globalDir = document.getElementById('vfx-global-direction')?.value || 'default';
const globalInt = parseFloat(document.getElementById('vfx-global-intensity')?.value || 1);
// Efeito Especial: Constelações (Linhas ligando os pontos próximos)
if (activeEffects.includes('constellations')) {
const constParticles = globalParticles.filter(p => p.type === 'constellations');
const cColor = hexToRgb(effectColors['constellations'] || '#77aaff');
for (let i = 0; i < constParticles.length; i++) {
for (let j = i + 1; j < constParticles.length; j++) {
const dist = Math.hypot(constParticles[i].x - constParticles[j].x, constParticles[i].y - constParticles[j].y);
if (dist < 75) {
globalVfxCtx.strokeStyle = `rgba(${cColor.r}, ${cColor.g}, ${cColor.b}, ${1 - dist / 75})`;
globalVfxCtx.lineWidth = 0.8;
globalVfxCtx.beginPath();
globalVfxCtx.moveTo(constParticles[i].x, constParticles[i].y);
globalVfxCtx.lineTo(constParticles[j].x, constParticles[j].y);
globalVfxCtx.stroke();
}
}
}
}
// Atualiza e Desenha cada partícula
for (let i = globalParticles.length - 1; i >= 0; i--) {
const p = globalParticles[i];
p.color = effectColors[p.type] || p.color;
p.update(rect, globalDir, globalInt);
p.draw(globalVfxCtx);
if (p.dead) {
globalParticles.splice(i, 1);
}
}
globalVfxCtx.restore(); // Fecha o recorte da máscara do tabuleiro
}
requestAnimationFrame(renderGlobalVFXLoop);
}
requestAnimationFrame(renderGlobalVFXLoop);
// 6. ILUMINAÇÃO HOVER DAS CASAS
function updateRGBHoverLight() {
if (!hoverLightEl) return;
const toggle = document.getElementById('vfx-hover-toggle')?.value;
if (toggle === 'off') {
hoverLightEl.style.display = 'none';
return;
}
hoverLightEl.style.display = 'block';
const mode = document.getElementById('vfx-light-mode')?.value || 'solid';
const speed = parseFloat(document.getElementById('vfx-hover-rgb-speed')?.value || 1);
const intensity = parseFloat(document.getElementById('vfx-light-intensity')?.value || 0.5);
const bleed = parseFloat(document.getElementById('vfx-light-bleed')?.value || 25);
let rgb = { r: 0, g: 229, b: 255 };
if (mode === 'solid') {
const colorHex = document.getElementById('vfx-light-color')?.value || '#00e5ff';
rgb = hexToRgb(colorHex);
} else if (mode === 'rgb_smooth') {
const hue = (Date.now() * 0.05 * speed) % 360;
rgb = hslToRgb(hue, 100, 50);
} else if (mode === 'rgb_custom') {
const c1 = document.getElementById('vfx-hover-rgb-c1')?.value || '#ff8800';
const c2 = document.getElementById('vfx-hover-rgb-c2')?.value || '#ff0000';
const c3 = document.getElementById('vfx-hover-rgb-c3')?.value || '#8800ff';
const c4 = document.getElementById('vfx-hover-rgb-c4')?.value || '#00e5ff';
const colors = [c1, c2, c3, c4];
const N = colors.length;
const totalProgress = (Date.now() * 0.001 * speed) % N;
const i = Math.floor(totalProgress);
const j = (i + 1) % N;
const factor = totalProgress - i;
rgb = interpolateColors(colors[i], colors[j], factor);
}
hoverLightEl.style.backgroundColor = `rgba(${rgb.r},${rgb.g},${rgb.b},${intensity})`;
if (bleed > 0) {
hoverLightEl.style.boxShadow = `0 0 ${bleed}px ${bleed / 2}px rgba(${rgb.r},${rgb.g},${rgb.b}, ${Math.min(1, intensity * 1.5)})`;
} else {
hoverLightEl.style.boxShadow = 'none';
}
}
setInterval(() => {
const mode = document.getElementById('vfx-light-mode')?.value;
if (mode === 'rgb_smooth' || mode === 'rgb_custom') {
updateRGBHoverLight();
}
}, 16);
// 7. ATUALIZAÇÃO E SALVAMENTO AUTOMÁTICO
function updateUIElementsVisibility() {
const rotateChecked = document.getElementById('vfx-eff-rotate')?.checked;
const rotateCfg = document.getElementById('vfx-rotate-cfg');
if (rotateCfg) rotateCfg.style.display = rotateChecked ? 'block' : 'none';
const shakeChecked = document.getElementById('vfx-eff-shake')?.checked;
const shakeCfg = document.getElementById('vfx-shake-cfg');
if (shakeCfg) shakeCfg.style.display = shakeChecked ? 'block' : 'none';
const lightMode = document.getElementById('vfx-light-mode')?.value || 'solid';
const hoverRgbCfg = document.getElementById('vfx-hover-rgb-cfg');
const customRgbContainer = document.getElementById('vfx-hover-custom-rgb-container');
if (hoverRgbCfg) hoverRgbCfg.style.display = (lightMode === 'solid') ? 'none' : 'block';
if (customRgbContainer) customRgbContainer.style.display = (lightMode === 'rgb_custom') ? 'block' : 'none';
const gInt = document.getElementById('vfx-global-intensity');
if (gInt) document.getElementById('vfx-global-intensity-val').innerText = gInt.value + 'x';
const rSpeed = document.getElementById('vfx-rotate-speed');
if (rSpeed) document.getElementById('vfx-rotate-speed-val').innerText = rSpeed.value + 'x';
const rInt = document.getElementById('vfx-rotate-int');
if (rInt) document.getElementById('vfx-rotate-int-val').innerText = rInt.value + '%';
const sSpeed = document.getElementById('vfx-shake-speed');
if (sSpeed) document.getElementById('vfx-shake-speed-val').innerText = sSpeed.value;
const sInt = document.getElementById('vfx-shake-int');
if (sInt) document.getElementById('vfx-shake-int-val').innerText = sInt.value + 'px';
const rgbSpeed = document.getElementById('vfx-hover-rgb-speed');
if (rgbSpeed) document.getElementById('vfx-hover-rgb-speed-val').innerText = rgbSpeed.value + 'x';
updateRGBHoverLight();
}
document.querySelectorAll('#vfx-panel .vfx-save').forEach(el => {
el.addEventListener('input', () => {
updateUIElementsVisibility();
if (typeof saveSettings === 'function') saveSettings();
});
el.addEventListener('change', () => {
updateUIElementsVisibility();
if (typeof saveSettings === 'function') saveSettings();
});
});
if (typeof loadSettings === 'function') {
loadSettings();
}
updateUIElementsVisibility();
// =========================================================================
// --- FIM DAS ATUALIZAÇÕES DOS EFEITOS GERAIS ---
// =========================================================================
// --- 7. LÓGICA INTERNA: APLICAR COR NO GLITCH/BUGADO ---
function aplicarCorGlitch(cor, intensidade) {
try {
const elementosGlitch = document.querySelectorAll('.vfx-glitch-active, .glitch-effect, #vfx-glitch-layer, [data-vfx="glitch"]');
if (!elementosGlitch || elementosGlitch.length === 0) return;
const corFinal = cor || (typeof salvos !== 'undefined' && salvos['vfx-glitch-color']) || '#ff0055';
const rotInt = intensidade !== undefined ? intensidade : ((typeof salvos !== 'undefined' && salvos['vfx-rotate-int']) || 0);
elementosGlitch.forEach(el => {
el.style.setProperty('--glitch-color', corFinal);
el.style.filter = `hue-rotate(${rotInt}deg) drop-shadow(0 0 5px ${corFinal})`;
});
} catch (err) {
console.error("Erro ao aplicar cor no glitch:", err);
}
}
// Loop de verificação e atualização contínua dos efeitos visuais e do glitch
setInterval(function() {
try {
aplicarCorGlitch();
if (typeof salvos !== 'undefined') {
const rotIntVal = document.getElementById('vfx-rotate-int-val');
let txtRotInt = (salvos['vfx-rotate-int'] || 0) + '%';
if (rotIntVal && rotIntVal.innerText !== txtRotInt) {
rotIntVal.innerText = txtRotInt;
}
}
} catch (e) {}
}, 500);
// =========================================================================
// --- SISTEMA DE PERSISTÊNCIA E FORÇAMENTO DO MURAL DE IMAGENS em 2º PLANO ---
// =========================================================================
(function sistemaPersistenciaMural() {
const CHAVE_ULTIMA_IMG_ID = 'vfx_last_wall_id';
const CHAVE_ULTIMA_IMG_SRC = 'vfx_last_wall_src';
// Salva forçadamente no localStorage todos os identificadores da imagem selecionada
function forcarSalvarImagemMural(id, src) {
if (!id || !src) return;
try {
localStorage.setItem(CHAVE_ULTIMA_IMG_ID, id);
localStorage.setItem(CHAVE_ULTIMA_IMG_SRC, src);
localStorage.setItem('vfx_custom_img_data', src);
} catch (err) {}
}
// Sobrescreve a função original de seleção do mural garantindo o salvamento forçado
if (typeof selectWallImage === 'function') {
const selectWallImageOriginal = selectWallImage;
selectWallImage = function(id, src) {
forcarSalvarImagemMural(id, src);
selectWallImageOriginal(id, src);
};
}
// Escuta cliques diretos no mural de imagens para garantir salvamento imediato
document.addEventListener('click', function(e) {
const imgWall = e.target.closest('#vfx-image-wall img');
if (imgWall && imgWall.src) {
const idSalvo = imgWall.getAttribute('data-id') || localStorage.getItem(CHAVE_ULTIMA_IMG_ID) || imgWall.src;
forcarSalvarImagemMural(idSalvo, imgWall.src);
}
}, true);
// Sistema de restauração em segundo plano (força a restruturação após F5)
function restaurarImagemForcada() {
const savedId = localStorage.getItem(CHAVE_ULTIMA_IMG_ID);
const savedSrc = localStorage.getItem(CHAVE_ULTIMA_IMG_SRC) || localStorage.getItem('vfx_custom_img_data');
if (savedId && savedSrc) {
if (typeof currentWallImageId !== 'undefined') {
currentWallImageId = savedId;
}
if (typeof customImg !== 'undefined') {
if (!customImg.src || customImg.src !== savedSrc) {
customImg = new Image();
customImg.crossOrigin = "Anonymous";
customImg.onload = () => {
if (typeof customImgLoaded !== 'undefined') customImgLoaded = true;
if (typeof applyConfigToUI === 'function') applyConfigToUI(savedId);
if (typeof renderPreview === 'function') renderPreview();
if (typeof renderImageWall === 'function') renderImageWall();
};
customImg.src = savedSrc;
}
}
}
}
// Executa imediatamente no carregamento e mantém um ciclo leve de verificação
setTimeout(restaurarImagemForcada, 100);
setTimeout(restaurarImagemForcada, 500);
setInterval(restaurarImagemForcada, 2000);
})();
// --- 3. QUANTIDADE E COR NO 'EFEITO GLOBAL' (PRIMEIRA LISTA) ---
const vfxSizeInput = document.getElementById('vfx-size');
if (vfxSizeInput && !document.getElementById('vfx-global-qty')) {
vfxSizeInput.insertAdjacentHTML('afterend', `
<label style="margin-top: 10px;">Quantidade do Efeito: <span id="vfx-global-qty-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-global-qty" class="vfx-save" min="0.1" max="5" step="0.1" value="1">
<label style="margin-top: 10px;">Cor do Efeito (Lasers, Glitch, etc):</label>
<div style="display: flex; gap: 5px; align-items: center; margin-top: 5px;">
<input type="color" id="vfx-global-color" class="vfx-save" value="#ff0000" style="width: 40px; height: 30px; cursor: pointer;">
<label class="vfx-checkbox-label" style="margin:0 !important; cursor: pointer;">
<input type="checkbox" id="vfx-use-global-color" class="vfx-save"> Ativar Cor Especial
</label>
</div>
`);
document.getElementById('vfx-global-qty').addEventListener('input', (e) => {
document.getElementById('vfx-global-qty-val').innerText = e.target.value + 'x';
if (typeof initParticles === 'function') initParticles();
if (typeof saveSettings === 'function') saveSettings();
});
document.getElementById('vfx-global-color').addEventListener('input', () => {
if (typeof initParticles === 'function') initParticles();
if (typeof handleBoardEffects === 'function') handleBoardEffects();
if (typeof saveSettings === 'function') saveSettings();
});
document.getElementById('vfx-use-global-color').addEventListener('change', () => {
if (typeof initParticles === 'function') initParticles();
if (typeof handleBoardEffects === 'function') handleBoardEffects();
if (typeof saveSettings === 'function') saveSettings();
});
}
// --- 4. TAMANHO E QUANTIDADE EM 'EFEITOS GLOBAIS DO TABULEIRO (ATIVE VÁRIOS)' ---
const globalDirSelect = document.getElementById('vfx-global-direction');
if (globalDirSelect && !document.getElementById('vfx-multi-size')) {
globalDirSelect.insertAdjacentHTML('afterend', `
<hr style="border:0; border-top:1px solid rgba(255,255,255,0.1); margin: 12px 0;">
<label>Tamanho dos Efeitos (Ative Vários): <span id="vfx-multi-size-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-multi-size" class="vfx-save" min="0.1" max="5" step="0.1" value="1">
<label style="margin-top: 10px;">Quantidade dos Efeitos (Ative Vários): <span id="vfx-multi-qty-val" style="color:#00e5ff;">1x</span></label>
<input type="range" id="vfx-multi-qty" class="vfx-save" min="0.1" max="5" step="0.1" value="1">
`);
document.getElementById('vfx-multi-size').addEventListener('input', (e) => {
document.getElementById('vfx-multi-size-val').innerText = e.target.value + 'x';
if (typeof saveSettings === 'function') saveSettings();
});
document.getElementById('vfx-multi-qty').addEventListener('input', (e) => {
document.getElementById('vfx-multi-qty-val').innerText = e.target.value + 'x';
if (typeof saveSettings === 'function') saveSettings();
});
}
// Restaura valores específicos criados pela nova injeção
if (typeof loadSettings === 'function') {
const saved = localStorage.getItem('vfx_chess_pro_settings');
if (saved) {
try {
const settings = JSON.parse(saved);
['vfx-global-qty', 'vfx-global-color', 'vfx-use-global-color', 'vfx-multi-size', 'vfx-multi-qty'].forEach(id => {
const el = document.getElementById(id);
if (el && settings[id] !== undefined) {
if (el.type === 'checkbox') el.checked = settings[id];
else el.value = settings[id];
el.dispatchEvent(new Event('input'));
}
});
} catch(e) {}
}
}
// --- 5. LÓGICA INTERNA: APLICAR QUANTIDADE NA initParticles ---
if (typeof initParticles === 'function') {
const oldInit = initParticles;
initParticles = function() {
oldInit();
const qtyEl = document.getElementById('vfx-global-qty');
const qtyMult = qtyEl ? parseFloat(qtyEl.value) : 1;
if (typeof particles !== 'undefined' && particles && particles.length > 0 && qtyMult !== 1) {
const baseCount = particles.length;
const newCount = Math.floor(baseCount * qtyMult);
if (newCount > baseCount) {
const type = particles[0].type;
for (let i = 0; i < (newCount - baseCount); i++) {
if (typeof Particle !== 'undefined') particles.push(new Particle(type));
}
} else if (newCount < baseCount) {
particles.splice(newCount);
}
}
};
initParticles();
}
// --- 6. LÓGICA INTERNA: APLICAR COR EM PARTÍCULAS (LASERS, ETC) ---
if (typeof Particle !== 'undefined' && Particle.prototype.reset) {
if (!Particle.prototype._originalReset) {
Particle.prototype._originalReset = Particle.prototype.reset;
Particle.prototype.reset = function(initial) {
this._originalReset(initial);
const useColorEl = document.getElementById('vfx-use-global-color');
const colorEl = document.getElementById('vfx-global-color');
if (useColorEl && useColorEl.checked && colorEl) {
this.color = colorEl.value;
if (this.type === 'fire') {
this.baseColor = colorEl.value;
}
}
};
}
}
// --- 7. LÓGICA INTERNA: APLICAR COR NO GLITCH/BUGADO ---
function aplicarCorGlitch(cor, intensidade) {
try {
const elementosGlitch = document.querySelectorAll('.vfx-glitch-active, .glitch-effect, #vfx-glitch-layer, [data-vfx="glitch"]');
if (!elementosGlitch || elementosGlitch.length === 0) return;
const corFinal = cor || (typeof salvos !== 'undefined' && salvos['vfx-glitch-color']) || '#ff0055';
const rotInt = intensidade !== undefined ? intensidade : ((typeof salvos !== 'undefined' && salvos['vfx-rotate-int']) || 0);
elementosGlitch.forEach(el => {
el.style.setProperty('--glitch-color', corFinal);
el.style.filter = `hue-rotate(${rotInt}deg) drop-shadow(0 0 5px ${corFinal})`;
});
} catch (err) {
console.error("Erro ao aplicar cor no glitch:", err);
}
}
// Loop de verificação e atualização contínua dos efeitos visuais e do glitch
setInterval(function() {
try {
aplicarCorGlitch();
if (typeof salvos !== 'undefined') {
const rotIntVal = document.getElementById('vfx-rotate-int-val');
const txtRotInt = (salvos['vfx-rotate-int'] || 0) + '%';
if (rotIntVal && rotIntVal.innerText !== txtRotInt) {
rotIntVal.innerText = txtRotInt;
}
}
} catch (e) {}
}, 500);
// =========================================================================
// --- SISTEMA DE PERSISTÊNCIA E FORÇAMENTO DO MURAL DE IMAGENS em 2º PLANO ---
// =========================================================================
(function sistemaPersistenciaMural() {
const CHAVE_ULTIMA_IMG_ID = 'vfx_last_wall_id';
const CHAVE_ULTIMA_IMG_SRC = 'vfx_last_wall_src';
// Salva forçadamente no localStorage todos os identificadores da imagem selecionada
function forcarSalvarImagemMural(id, src) {
if (!id || !src) return;
try {
localStorage.setItem(CHAVE_ULTIMA_IMG_ID, id);
localStorage.setItem(CHAVE_ULTIMA_IMG_SRC, src);
localStorage.setItem('vfx_custom_img_data', src);
} catch (err) {}
}
// Sobrescreve a função original de seleção do mural garantindo o salvamento forçado
if (typeof selectWallImage === 'function') {
const selectWallImageOriginal = selectWallImage;
selectWallImage = function(id, src) {
forcarSalvarImagemMural(id, src);
selectWallImageOriginal(id, src);
};
}
// Escuta cliques diretos no mural de imagens para garantir salvamento imediato
document.addEventListener('click', function(e) {
const imgWall = e.target.closest('#vfx-image-wall img');
if (imgWall && imgWall.src) {
const idSalvo = localStorage.getItem(CHAVE_ULTIMA_IMG_ID) || imgWall.src;
forcarSalvarImagemMural(idSalvo, imgWall.src);
}
}, true);
// Sistema de restauração em segundo plano (força a restruturação após F5)
function restaurarImagemForcada() {
const savedId = localStorage.getItem(CHAVE_ULTIMA_IMG_ID);
const savedSrc = localStorage.getItem(CHAVE_ULTIMA_IMG_SRC) || localStorage.getItem('vfx_custom_img_data');
if (savedId && savedSrc) {
if (typeof currentWallImageId !== 'undefined') {
currentWallImageId = savedId;
}
if (typeof customImg !== 'undefined') {
if (!customImg.src || customImg.src !== savedSrc) {
customImg = new Image();
customImg.crossOrigin = "Anonymous";
customImg.onload = () => {
if (typeof customImgLoaded !== 'undefined') customImgLoaded = true;
if (typeof applyConfigToUI === 'function') applyConfigToUI(savedId);
if (typeof renderPreview === 'function') renderPreview();
if (typeof renderImageWall === 'function') renderImageWall();
};
customImg.src = savedSrc;
}
}
}
}
// Executa imediatamente no carregamento e mantém um ciclo leve de verificação
setTimeout(restaurarImagemForcada, 100);
setTimeout(restaurarImagemForcada, 500);
setInterval(restaurarImagemForcada, 2000);
})();
})();