✨ Transforme a aparência do tabuleiro do Chess.com! 🖼️ Personalize cores, filtros, opacidade, imagens e diversos efeitos visuais com facilidade. 🎛️🌈
// ==UserScript==
// @name 🖼️✨Chess.com Board Effects
// @namespace http://tampermonkey.net/
// @version 1.9
// @description ✨ Transforme a aparência do tabuleiro do Chess.com! 🖼️ Personalize cores, filtros, opacidade, imagens e diversos efeitos visuais com facilidade. 🎛️🌈
// @author aerus15
// @match *://*.chess.com/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
// Injeção do SVG de Filtro Sharpen (Nitidez)
const svgDiv = document.createElement('div');
svgDiv.innerHTML = `
<svg width="0" height="0" style="position:absolute;z-index:-1;">
<defs>
<filter id="cb-svg-sharpen">
<feConvolveMatrix id="cb-sharpen-matrix" order="3" preserveAlpha="true" kernelMatrix="0 0 0 0 1 0 0 0 0" />
</filter>
</defs>
</svg>
`;
document.body.appendChild(svgDiv);
const defaults = {
board_opacity: 100,
opacity: 100,
brightness: 100,
contrast: 100,
saturation: 100,
hue: 0,
blur: 0,
sepia: 0,
sharpen: 0,
invert: 0,
overlay_opacity: 0,
overlay_color: '#00ff00',
fit_image: true
};
let gallery = [];
try {
gallery = JSON.parse(GM_getValue('cb_gallery', '[]'));
} catch (e) {
gallery = [];
}
let activeId = GM_getValue('cb_active_id', null);
let state = { ...defaults };
function loadGlobalState() {
state = {
board_opacity: GM_getValue('cb_board_opacity', defaults.board_opacity),
opacity: GM_getValue('cb_opacity', defaults.opacity),
brightness: GM_getValue('cb_brightness', defaults.brightness),
contrast: GM_getValue('cb_contrast', defaults.contrast),
saturation: GM_getValue('cb_saturation', defaults.saturation),
hue: GM_getValue('cb_hue', defaults.hue),
blur: GM_getValue('cb_blur', defaults.blur),
sepia: GM_getValue('cb_sepia', defaults.sepia),
sharpen: GM_getValue('cb_sharpen', defaults.sharpen),
invert: GM_getValue('cb_invert', defaults.invert),
overlay_opacity: GM_getValue('cb_overlay_opacity', defaults.overlay_opacity),
overlay_color: GM_getValue('cb_overlay_color', defaults.overlay_color),
fit_image: GM_getValue('cb_fit_image', defaults.fit_image)
};
}
function initSettings() {
if (activeId) {
const img = gallery.find(i => i.id === activeId);
if (img && img.settings) {
state = { ...defaults, ...img.settings };
} else {
activeId = null;
loadGlobalState();
}
} else {
loadGlobalState();
}
}
function saveAll() {
if (activeId) {
const imgIndex = gallery.findIndex(i => i.id === activeId);
if (imgIndex > -1) {
gallery[imgIndex].settings = { ...state };
}
GM_setValue('cb_gallery', JSON.stringify(gallery));
GM_setValue('cb_active_id', activeId);
} else {
GM_setValue('cb_board_opacity', state.board_opacity);
GM_setValue('cb_opacity', state.opacity);
GM_setValue('cb_brightness', state.brightness);
GM_setValue('cb_contrast', state.contrast);
GM_setValue('cb_saturation', state.saturation);
GM_setValue('cb_hue', state.hue);
GM_setValue('cb_blur', state.blur);
GM_setValue('cb_sepia', state.sepia);
GM_setValue('cb_sharpen', state.sharpen);
GM_setValue('cb_invert', state.invert);
GM_setValue('cb_overlay_opacity', state.overlay_opacity);
GM_setValue('cb_overlay_color', state.overlay_color);
GM_setValue('cb_fit_image', state.fit_image);
GM_setValue('cb_active_id', null);
}
}
function updateCSS() {
const root = document.documentElement;
root.style.setProperty('--cb-board-opacity', state.board_opacity / 100);
root.style.setProperty('--cb-opacity', state.opacity / 100);
root.style.setProperty('--cb-brightness', state.brightness / 100);
root.style.setProperty('--cb-contrast', state.contrast / 100);
root.style.setProperty('--cb-saturate', state.saturation / 100);
root.style.setProperty('--cb-hue', `${state.hue}deg`);
root.style.setProperty('--cb-blur', `${state.blur}px`);
root.style.setProperty('--cb-sepia', state.sepia / 100);
root.style.setProperty('--cb-invert', state.invert / 100);
root.style.setProperty('--cb-overlay-opacity', state.overlay_opacity / 100);
root.style.setProperty('--cb-overlay-color', state.overlay_color);
root.style.setProperty('--cb-bg-size', state.fit_image ? '100% 100%' : 'cover');
// Lógica de Nitidez (Matriz SVG)
const s = state.sharpen || 0;
const x = s * 0.02;
const center = 1 + 4 * x;
const matrix = `0 ${-x} 0 ${-x} ${center} ${-x} 0 ${-x} 0`;
const matrixEl = document.getElementById('cb-sharpen-matrix');
if (matrixEl) matrixEl.setAttribute('kernelMatrix', matrix);
}
function updateUIFromState() {
sliders.forEach(s => {
const input = document.getElementById(`inp-${s.id}`);
const valDisplay = document.getElementById(`val-${s.id}`);
if (input) input.value = state[s.id];
if (valDisplay) valDisplay.innerText = `${state[s.id]}${s.suffix}`;
});
const colorInput = document.getElementById('inp-overlay_color');
if (colorInput) colorInput.value = state.overlay_color;
const fitInput = document.getElementById('inp-fit_image');
if (fitInput) fitInput.checked = !!state.fit_image;
updateCSS();
}
GM_addStyle(`
:root {
--cb-board-opacity: 1;
--cb-opacity: 1;
--cb-brightness: 1;
--cb-contrast: 1;
--cb-saturate: 1;
--cb-hue: 0deg;
--cb-blur: 0px;
--cb-sepia: 0;
--cb-invert: 0;
--cb-overlay-opacity: 0;
--cb-overlay-color: #00ff00;
--cb-bg-size: 100% 100%;
}
body wc-chess-board,
body #board-board,
body .board {
background-size: 0px 0px !important;
background-position: -9999px -9999px !important;
background-repeat: no-repeat !important;
background-color: transparent !important;
opacity: var(--cb-board-opacity) !important;
}
.custom-board-bg-layer {
position: absolute;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
z-index: -1;
pointer-events: none;
background-size: var(--cb-bg-size) !important;
background-position: center center !important;
background-repeat: no-repeat !important;
filter: opacity(var(--cb-opacity))
brightness(var(--cb-brightness))
contrast(var(--cb-contrast))
saturate(var(--cb-saturate))
hue-rotate(var(--cb-hue))
blur(var(--cb-blur))
sepia(var(--cb-sepia))
invert(var(--cb-invert))
url(#cb-svg-sharpen);
transition: filter 0.1s linear;
border-radius: inherit;
overflow: hidden;
}
.custom-board-bg-layer::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-color: var(--cb-overlay-color);
opacity: var(--cb-overlay-opacity);
pointer-events: none;
z-index: 1;
border-radius: inherit;
}
#cb-filter-gui {
position: fixed;
bottom: 20px;
right: 70px;
background: rgba(25, 25, 25, 0.95);
color: #eee;
padding: 16px;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.6);
font-family: system-ui, -apple-system, sans-serif;
font-size: 13px;
z-index: 999999;
display: flex;
flex-direction: column;
gap: 12px;
width: 280px;
backdrop-filter: blur(8px);
border: 1px solid rgba(255,255,255,0.1);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s ease;
max-height: 85vh;
overflow-y: auto;
}
#cb-filter-gui::-webkit-scrollbar { width: 6px; }
#cb-filter-gui::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
#cb-filter-gui.hidden {
transform: translateX(120%) scale(0.9);
opacity: 0;
pointer-events: none;
}
#cb-filter-gui .header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255,255,255,0.1);
padding-bottom: 8px;
cursor: grab;
}
#cb-filter-gui .header:active { cursor: grabbing; }
#cb-filter-gui .header h3 { margin: 0; font-size: 14px; font-weight: 600; }
#cb-filter-gui .close-btn { cursor: pointer; font-size: 18px; font-weight: bold; color: #888; transition: color 0.2s; line-height: 1; }
#cb-filter-gui .close-btn:hover { color: #fff; }
#cb-gallery-section { display: flex; flex-direction: column; gap: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 10px; }
.cb-btn {
background: rgba(60, 140, 60, 0.9); color: #fff; border: none; padding: 8px; border-radius: 5px;
cursor: pointer; font-weight: bold; transition: background 0.2s; text-align: center; width: 100%; box-sizing: border-box;
}
.cb-btn:hover { background: rgba(80, 170, 80, 1); }
.cb-btn.edit { background: rgba(200, 120, 30, 0.9); display: none; }
.cb-btn.edit:hover { background: rgba(230, 140, 40, 1); }
#cb-mural {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
padding-bottom: 6px;
max-height: 120px; /* Mostra aproximadamente 2 linhas com base no aspect-ratio */
overflow-y: hidden;
transition: max-height 0.3s ease;
}
#cb-mural.expanded {
max-height: 400px;
overflow-y: auto;
}
#cb-mural::-webkit-scrollbar { width: 6px; }
#cb-mural::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.3); border-radius: 3px; }
.cb-thumb {
position: relative;
width: 100%;
aspect-ratio: 1;
border-radius: 6px;
background-size: cover;
background-position: center;
cursor: pointer;
border: 2px solid transparent;
transition: border-color 0.2s, transform 0.1s;
}
.cb-thumb:hover { transform: scale(1.05); }
.cb-thumb.active { border-color: #4da6ff; box-shadow: 0 0 8px rgba(77, 166, 255, 0.6); }
.cb-delete-btn {
position: absolute; top: -6px; right: -6px; background: #c83232; color: #fff; width: 18px; height: 18px; border-radius: 50%;
display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.5); border: 1px solid #fff; z-index: 2;
}
.cb-delete-btn:hover { background: #ff4d4d; transform: scale(1.1); }
#cb-ver-mais-btn {
background: transparent; color: #4da6ff; border: 1px solid #4da6ff; padding: 4px 8px; border-radius: 4px;
cursor: pointer; font-size: 11px; text-align: center; width: 100%; box-sizing: border-box; margin-bottom: 10px;
transition: all 0.2s;
}
#cb-ver-mais-btn:hover { background: rgba(77, 166, 255, 0.2); }
.cb-slider-row { display: flex; flex-direction: column; gap: 6px; }
.cb-slider-row label { display: flex; justify-content: space-between; color: #bbb; align-items: center; }
.cb-slider-row span { color: #fff; font-variant-numeric: tabular-nums; }
.cb-slider-row input[type=range] {
width: 100%; margin: 0; appearance: none; background: rgba(255,255,255,0.1); height: 6px; border-radius: 3px; outline: none;
}
.cb-slider-row input[type=range]::-webkit-slider-thumb,
.cb-slider-row input[type=range]::-moz-range-thumb {
appearance: none; width: 14px; height: 14px; border-radius: 50%; background: #fff; cursor: pointer; border: none;
}
.cb-slider-row input[type=color] { appearance: none; width: 30px; height: 30px; border: none; border-radius: 4px; background: none; cursor: pointer; padding: 0; }
.cb-slider-row input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
.cb-slider-row input[type=color]::-webkit-color-swatch { border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; }
#cb-reset-btn { background: rgba(200, 50, 50, 0.8); color: #fff; border: none; padding: 8px; border-radius: 5px; cursor: pointer; font-weight: bold; margin-top: 5px; transition: background 0.2s; }
#cb-reset-btn:hover { background: rgba(230, 60, 60, 1); }
#cb-toggle-btn {
position: fixed; bottom: 20px; right: 20px; background: rgba(25, 25, 25, 0.9); color: #fff; border: 1px solid rgba(255,255,255,0.1);
border-radius: 50%; width: 44px; height: 44px; font-size: 20px; cursor: pointer; z-index: 999998; box-shadow: 0 4px 12px rgba(0,0,0,0.4);
display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); transition: all 0.2s;
}
#cb-toggle-btn:hover { background: rgba(45, 45, 45, 0.95); transform: scale(1.05); }
/* Estilos do Editor Modal */
#cb-editor-modal {
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(10, 10, 10, 0.95);
z-index: 9999999; display: flex; flex-direction: column; align-items: center; justify-content: center;
backdrop-filter: blur(10px); font-family: system-ui, sans-serif;
}
#cb-editor-modal.hidden { display: none !important; }
.cb-toolbar {
position: absolute; top: 20px; background: rgba(30, 30, 30, 0.95); padding: 12px 24px; border-radius: 12px;
display: flex; flex-direction: column; gap: 10px; align-items: center; box-shadow: 0 8px 32px rgba(0,0,0,0.8); border: 1px solid rgba(255,255,255,0.2);
color: #fff; z-index: 10;
}
.cb-toolbar-row {
display: flex; gap: 10px; align-items: center; width: 100%; justify-content: center;
}
.cb-toolbar button {
background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 8px 12px;
border-radius: 6px; cursor: pointer; transition: all 0.2s; font-weight: bold;
}
.cb-toolbar button:hover { background: rgba(255,255,255,0.2); }
.cb-toolbar button.active-tool { background: #4da6ff; color: #000; border-color: #4da6ff; box-shadow: 0 0 10px rgba(77, 166, 255, 0.5); }
.cb-toolbar button.save { background: #3c8c3c; border-color: #3c8c3c; }
.cb-toolbar button.cancel { background: #c83232; border-color: #c83232; }
.cb-toolbar input[type=color] { cursor: pointer; width: 32px; height: 32px; border-radius: 5px; border: 1px solid #fff; padding: 0; background: none; }
.cb-toolbar input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
.cb-toolbar input[type=color]::-webkit-color-swatch { border-radius: 5px; border: none; }
.cb-canvas-container { position: relative; background: #000; box-shadow: 0 0 40px rgba(0,0,0,0.9); user-select: none; }
#cb-edit-canvas { display: block; width: 100%; height: 100%; }
#cb-edit-canvas.draw-cursor { cursor: crosshair; }
#cb-edit-canvas.eye-cursor { cursor: crosshair; }
#cb-crop-box {
position: absolute; border: 2px dashed #4da6ff; box-shadow: 0 0 0 9999px rgba(0,0,0,0.7); cursor: move;
display: none; box-sizing: border-box;
}
.cb-resizer { position: absolute; width: 14px; height: 14px; background: #4da6ff; border-radius: 50%; box-shadow: 0 0 4px rgba(0,0,0,0.5); }
.cb-resizer.nw { top: -7px; left: -7px; cursor: nwse-resize; }
.cb-resizer.ne { top: -7px; right: -7px; cursor: nesw-resize; }
.cb-resizer.sw { bottom: -7px; left: -7px; cursor: nesw-resize; }
.cb-resizer.se { bottom: -7px; right: -7px; cursor: nwse-resize; }
`);
const toggleBtn = document.createElement('button');
toggleBtn.id = 'cb-toggle-btn';
toggleBtn.innerHTML = '🎨';
document.body.appendChild(toggleBtn);
const gui = document.createElement('div');
gui.id = 'cb-filter-gui';
gui.className = 'hidden';
initSettings();
gui.innerHTML = `
<div class="header">
<h3>Filtros & Tabuleiros</h3>
<div class="close-btn" id="cb-close-btn">×</div>
</div>
<div id="cb-gallery-section">
<input type="file" id="cb-file-input" accept="image/*" style="display:none">
<button id="cb-add-img-btn" class="cb-btn">Adicionar Tabuleiro do PC</button>
<button id="cb-edit-img-btn" class="cb-btn edit">✏️ Editar Tabuleiro Selecionado</button>
<div id="cb-mural"></div>
<button id="cb-ver-mais-btn" style="display:none;">Ver mais</button>
</div>
<div class="cb-slider-row" style="flex-direction: row; justify-content: space-between; align-items: center; padding-bottom: 5px;">
<label for="inp-fit_image" style="cursor: pointer; color: #fff;">Enquadrar Tabuleiro (Alinhar Casas)</label>
<input type="checkbox" id="inp-fit_image" style="width: 16px; height: 16px; cursor: pointer; margin: 0;">
</div>
<div class="cb-slider-row">
<label>Transparência Geral <span id="val-board_opacity"></span></label>
<input type="range" id="inp-board_opacity" min="0" max="100">
</div>
<div class="cb-slider-row">
<label>Opacidade do Fundo <span id="val-opacity"></span></label>
<input type="range" id="inp-opacity" min="0" max="100">
</div>
<div class="cb-slider-row">
<label>Brilho <span id="val-brightness"></span></label>
<input type="range" id="inp-brightness" min="0" max="200">
</div>
<div class="cb-slider-row">
<label>Contraste <span id="val-contrast"></span></label>
<input type="range" id="inp-contrast" min="0" max="200">
</div>
<div class="cb-slider-row">
<label>Saturação <span id="val-saturation"></span></label>
<input type="range" id="inp-saturation" min="0" max="200">
</div>
<div class="cb-slider-row">
<label>Matiz (Hue) <span id="val-hue"></span></label>
<input type="range" id="inp-hue" min="0" max="360">
</div>
<div class="cb-slider-row">
<label>Desfoque (Blur) <span id="val-blur"></span></label>
<input type="range" id="inp-blur" min="0" max="20" step="0.5">
</div>
<div class="cb-slider-row">
<label>Nitidez (Sharpen) <span id="val-sharpen"></span></label>
<input type="range" id="inp-sharpen" min="0" max="100">
</div>
<div class="cb-slider-row">
<label>Inverter Cores <span id="val-invert"></span></label>
<input type="range" id="inp-invert" min="0" max="100">
</div>
<div class="cb-slider-row">
<label>Sépia <span id="val-sepia"></span></label>
<input type="range" id="inp-sepia" min="0" max="100">
</div>
<div class="cb-slider-row" style="margin-top: 5px; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 10px;">
<label>Cor do Filtro Extra <input type="color" id="inp-overlay_color"></label>
</div>
<div class="cb-slider-row">
<label>Opacidade do Filtro Extra <span id="val-overlay_opacity"></span></label>
<input type="range" id="inp-overlay_opacity" min="0" max="100">
</div>
<button id="cb-reset-btn">Resetar Configurações</button>
`;
document.body.appendChild(gui);
// Sistema de Edição Modal
const editorModal = document.createElement('div');
editorModal.id = 'cb-editor-modal';
editorModal.className = 'hidden';
editorModal.innerHTML = `
<div class="cb-toolbar">
<div class="cb-toolbar-row">
<button id="cb-btn-draw" class="active-tool">✏️ Lápis</button>
<button id="cb-btn-crop">✂️ Cortar</button>
<button id="cb-btn-eye">💧 Conta-gotas</button>
<div style="width: 1px; height: 24px; background: rgba(255,255,255,0.2); margin: 0 5px;"></div>
<input type="color" id="cb-draw-color" value="#ff0000" title="Cor do Lápis">
<input type="range" id="cb-draw-size" min="1" max="50" value="5" title="Espessura" style="width: 80px;">
</div>
<div class="cb-toolbar-row">
<button id="cb-btn-undo" title="Desfazer">↩️ Desfazer</button>
<button id="cb-btn-reset-img" title="Restaurar Imagem Original">🔄 Resetar Alterações</button>
<div style="width: 1px; height: 24px; background: rgba(255,255,255,0.2); margin: 0 5px;"></div>
<button id="cb-btn-flip-h" title="Girar Horizontal">↔️ Girar H</button>
<button id="cb-btn-flip-v" title="Girar Vertical">↕️ Girar V</button>
<div style="width: 1px; height: 24px; background: rgba(255,255,255,0.2); margin: 0 5px;"></div>
<button id="cb-btn-save-edit" class="save">Salvar</button>
<button id="cb-btn-close-edit" class="cancel">Cancelar</button>
</div>
</div>
<div class="cb-canvas-container" id="cb-canvas-container">
<canvas id="cb-edit-canvas" class="draw-cursor"></canvas>
<div id="cb-crop-box">
<div class="cb-resizer nw"></div>
<div class="cb-resizer ne"></div>
<div class="cb-resizer sw"></div>
<div class="cb-resizer se"></div>
</div>
</div>
`;
document.body.appendChild(editorModal);
const canvas = document.getElementById('cb-edit-canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const cropBox = document.getElementById('cb-crop-box');
const canvasContainer = document.getElementById('cb-canvas-container');
let editMode = 'draw'; // 'draw', 'crop', 'eye'
let isDrawing = false;
// Novas variáveis para Desfazer/Resetar
let undoStack = [];
let initialStateDataUrl = null;
function saveEditState() {
undoStack.push(canvas.toDataURL('image/png'));
if (undoStack.length > 20) undoStack.shift();
}
function restoreImageFromUrl(dataUrl) {
const img = new Image();
img.onload = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
};
img.src = dataUrl;
}
function rgbToHex(r, g, b) {
return "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1);
}
function setEditMode(mode) {
editMode = mode;
document.getElementById('cb-btn-draw').classList.toggle('active-tool', mode === 'draw');
document.getElementById('cb-btn-crop').classList.toggle('active-tool', mode === 'crop');
document.getElementById('cb-btn-eye').classList.toggle('active-tool', mode === 'eye');
cropBox.style.display = mode === 'crop' ? 'block' : 'none';
canvas.className = mode === 'eye' ? 'eye-cursor' : 'draw-cursor';
}
document.getElementById('cb-btn-draw').onclick = () => setEditMode('draw');
document.getElementById('cb-btn-crop').onclick = () => setEditMode('crop');
document.getElementById('cb-btn-eye').onclick = () => setEditMode('eye');
document.getElementById('cb-btn-close-edit').onclick = () => {
editorModal.classList.add('hidden');
};
// Botões Desfazer e Resetar
document.getElementById('cb-btn-undo').onclick = () => {
if (undoStack.length > 0) {
const lastState = undoStack.pop();
restoreImageFromUrl(lastState);
}
};
document.getElementById('cb-btn-reset-img').onclick = () => {
if (initialStateDataUrl) {
saveEditState();
restoreImageFromUrl(initialStateDataUrl);
}
};
// Botões Girar Horizontal/Vertical
document.getElementById('cb-btn-flip-h').onclick = () => {
saveEditState();
const tempC = document.createElement('canvas');
tempC.width = canvas.width; tempC.height = canvas.height;
tempC.getContext('2d').drawImage(canvas, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(canvas.width, 0);
ctx.scale(-1, 1);
ctx.drawImage(tempC, 0, 0);
ctx.restore();
};
document.getElementById('cb-btn-flip-v').onclick = () => {
saveEditState();
const tempC = document.createElement('canvas');
tempC.width = canvas.width; tempC.height = canvas.height;
tempC.getContext('2d').drawImage(canvas, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(0, canvas.height);
ctx.scale(1, -1);
ctx.drawImage(tempC, 0, 0);
ctx.restore();
};
function getCanvasCoords(e) {
const rect = canvas.getBoundingClientRect();
return {
x: (e.clientX - rect.left) * (canvas.width / rect.width),
y: (e.clientY - rect.top) * (canvas.height / rect.height)
};
}
canvas.addEventListener('mousedown', e => {
const coords = getCanvasCoords(e);
if (editMode === 'draw') {
saveEditState();
isDrawing = true;
ctx.beginPath();
ctx.moveTo(coords.x, coords.y);
} else if (editMode === 'eye') {
const pixel = ctx.getImageData(coords.x, coords.y, 1, 1).data;
if (pixel[3] > 0) {
document.getElementById('cb-draw-color').value = rgbToHex(pixel[0], pixel[1], pixel[2]);
}
setEditMode('draw');
}
});
canvas.addEventListener('mousemove', e => {
if (editMode === 'draw' && isDrawing) {
const coords = getCanvasCoords(e);
ctx.lineTo(coords.x, coords.y);
ctx.strokeStyle = document.getElementById('cb-draw-color').value;
ctx.lineWidth = document.getElementById('cb-draw-size').value;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.stroke();
}
});
window.addEventListener('mouseup', () => { isDrawing = false; });
// Lógica da Grade de Corte (Crop Box)
let cropState = { active: false, action: null, startX: 0, startY: 0, sL: 0, sT: 0, sW: 0, sH: 0 };
cropBox.addEventListener('mousedown', e => {
if (editMode !== 'crop') return;
e.preventDefault(); e.stopPropagation();
cropState.active = true;
cropState.startX = e.clientX;
cropState.startY = e.clientY;
cropState.sL = parseFloat(cropBox.style.left || 0);
cropState.sT = parseFloat(cropBox.style.top || 0);
cropState.sW = parseFloat(cropBox.style.width);
cropState.sH = parseFloat(cropBox.style.height);
if (e.target.classList.contains('nw')) cropState.action = 'nw';
else if (e.target.classList.contains('ne')) cropState.action = 'ne';
else if (e.target.classList.contains('sw')) cropState.action = 'sw';
else if (e.target.classList.contains('se')) cropState.action = 'se';
else cropState.action = 'move';
});
window.addEventListener('mousemove', e => {
if (!cropState.active || editMode !== 'crop') return;
const dx = e.clientX - cropState.startX;
const dy = e.clientY - cropState.startY;
let nL = cropState.sL, nT = cropState.sT, nW = cropState.sW, nH = cropState.sH;
const maxW = canvasContainer.clientWidth;
const maxH = canvasContainer.clientHeight;
if (cropState.action === 'move') { nL += dx; nT += dy; }
else if (cropState.action === 'nw') { nL += dx; nT += dy; nW -= dx; nH -= dy; }
else if (cropState.action === 'ne') { nT += dy; nW += dx; nH -= dy; }
else if (cropState.action === 'sw') { nL += dx; nW -= dx; nH += dy; }
else if (cropState.action === 'se') { nW += dx; nH += dy; }
if (nW < 50) nW = 50; if (nH < 50) nH = 50;
if (nL < 0) nL = 0; if (nT < 0) nT = 0;
if (nL + nW > maxW) { nW = maxW - nL; }
if (nT + nH > maxH) { nH = maxH - nT; }
if (cropState.action === 'move') {
if (nL + nW > maxW) nL = maxW - nW;
if (nT + nH > maxH) nT = maxH - nH;
}
cropBox.style.left = nL + 'px'; cropBox.style.top = nT + 'px';
cropBox.style.width = nW + 'px'; cropBox.style.height = nH + 'px';
});
window.addEventListener('mouseup', () => cropState.active = false);
document.getElementById('cb-edit-img-btn').addEventListener('click', () => {
if (!activeId) return;
const imgObj = gallery.find(i => i.id === activeId);
if (!imgObj) return;
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
// Inicializar estados de desfazer
initialStateDataUrl = canvas.toDataURL('image/png');
undoStack = [];
const screenW = window.innerWidth * 0.85;
const screenH = window.innerHeight * 0.75;
let scale = Math.min(screenW / img.width, screenH / img.height);
if (scale > 1) scale = 1;
const visW = img.width * scale;
const visH = img.height * scale;
canvasContainer.style.width = visW + 'px';
canvasContainer.style.height = visH + 'px';
cropBox.style.left = '0px';
cropBox.style.top = '0px';
cropBox.style.width = visW + 'px';
cropBox.style.height = visH + 'px';
setEditMode('draw');
editorModal.classList.remove('hidden');
};
img.src = imgObj.dataUrl;
});
document.getElementById('cb-btn-save-edit').addEventListener('click', () => {
let finalDataUrl = '';
if (editMode === 'crop' || parseFloat(cropBox.style.width) < canvasContainer.clientWidth) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const cL = parseFloat(cropBox.style.left) * scaleX;
const cT = parseFloat(cropBox.style.top) * scaleY;
const cW = parseFloat(cropBox.style.width) * scaleX;
const cH = parseFloat(cropBox.style.height) * scaleY;
const tempC = document.createElement('canvas');
tempC.width = cW; tempC.height = cH;
tempC.getContext('2d').drawImage(canvas, cL, cT, cW, cH, 0, 0, cW, cH);
finalDataUrl = tempC.toDataURL('image/png');
} else {
finalDataUrl = canvas.toDataURL('image/png');
}
const idx = gallery.findIndex(i => i.id === activeId);
if (idx > -1) {
gallery[idx].dataUrl = finalDataUrl;
saveAll();
renderMural();
syncBackground();
}
editorModal.classList.add('hidden');
});
const sliders = [
{ id: 'board_opacity', suffix: '%' },
{ id: 'opacity', suffix: '%' },
{ id: 'brightness', suffix: '%' },
{ id: 'contrast', suffix: '%' },
{ id: 'saturation', suffix: '%' },
{ id: 'hue', suffix: '°' },
{ id: 'blur', suffix: 'px' },
{ id: 'sharpen', suffix: '%' },
{ id: 'invert', suffix: '%' },
{ id: 'sepia', suffix: '%' },
{ id: 'overlay_opacity', suffix: '%' }
];
let isMuralExpanded = false;
// Lógica do botão de "Ver mais"
document.getElementById('cb-ver-mais-btn').addEventListener('click', (e) => {
isMuralExpanded = !isMuralExpanded;
const mural = document.getElementById('cb-mural');
if (isMuralExpanded) {
mural.classList.add('expanded');
e.target.innerText = 'Ver menos';
} else {
mural.classList.remove('expanded');
e.target.innerText = 'Ver mais';
mural.scrollTop = 0;
}
});
function renderMural() {
const mural = document.getElementById('cb-mural');
mural.innerHTML = '';
gallery.forEach(img => {
const thumb = document.createElement('div');
thumb.className = `cb-thumb ${img.id === activeId ? 'active' : ''}`;
thumb.style.backgroundImage = `url(${img.dataUrl})`;
thumb.title = "Clique 1x para selecionar, 2x para excluir";
// Se for uma imagem do PC (começa com data:), adiciona o botão de deletar
const isPCImage = img.dataUrl.startsWith('data:');
if (isPCImage) {
const delBtn = document.createElement('div');
delBtn.className = 'cb-delete-btn';
delBtn.innerText = '✖';
delBtn.title = 'Deletar Imagem';
delBtn.addEventListener('click', (e) => {
e.stopPropagation();
gallery = gallery.filter(i => i.id !== img.id);
if (activeId === img.id) {
activeId = null;
loadGlobalState();
}
saveAll();
renderMural();
updateUIFromState();
syncBackground();
});
thumb.appendChild(delBtn);
}
thumb.addEventListener('click', () => {
if (activeId === img.id) {
activeId = null;
loadGlobalState();
} else {
activeId = img.id;
state = { ...defaults, ...img.settings };
}
saveAll();
renderMural();
updateUIFromState();
});
thumb.addEventListener('dblclick', () => {
if (isPCImage) {
gallery = gallery.filter(i => i.id !== img.id);
if (activeId === img.id) {
activeId = null;
loadGlobalState();
}
saveAll();
renderMural();
updateUIFromState();
syncBackground();
}
});
mural.appendChild(thumb);
});
document.getElementById('cb-edit-img-btn').style.display = activeId ? 'block' : 'none';
// Esconder ou mostrar o botão Ver Mais caso passe de 8 imagens
const verMaisBtn = document.getElementById('cb-ver-mais-btn');
if (gallery.length > 8) {
verMaisBtn.style.display = 'block';
} else {
verMaisBtn.style.display = 'none';
mural.classList.remove('expanded');
isMuralExpanded = false;
verMaisBtn.innerText = 'Ver mais';
}
}
document.getElementById('cb-add-img-btn').addEventListener('click', () => {
document.getElementById('cb-file-input').click();
});
document.getElementById('cb-file-input').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
const dataUrl = event.target.result;
const newId = Date.now().toString();
gallery.push({
id: newId,
dataUrl: dataUrl,
settings: { ...defaults }
});
activeId = newId;
state = { ...defaults };
saveAll();
renderMural();
updateUIFromState();
};
reader.readAsDataURL(file);
e.target.value = '';
});
renderMural();
updateUIFromState();
toggleBtn.addEventListener('click', () => {
gui.classList.remove('hidden');
toggleBtn.style.transform = 'scale(0)';
setTimeout(() => toggleBtn.style.display = 'none', 200);
});
document.getElementById('cb-close-btn').addEventListener('click', () => {
gui.classList.add('hidden');
toggleBtn.style.display = 'flex';
setTimeout(() => toggleBtn.style.transform = 'scale(1)', 10);
});
let isDragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
const header = gui.querySelector('.header');
header.addEventListener('mousedown', (e) => {
isDragging = true;
const rect = gui.getBoundingClientRect();
dragOffsetX = e.clientX - rect.left;
dragOffsetY = e.clientY - rect.top;
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
let newX = e.clientX - dragOffsetX;
let newY = e.clientY - dragOffsetY;
gui.style.left = `${newX}px`;
gui.style.top = `${newY}px`;
gui.style.right = 'auto';
gui.style.bottom = 'auto';
});
window.addEventListener('mouseup', () => {
isDragging = false;
});
sliders.forEach(s => {
const input = document.getElementById(`inp-${s.id}`);
const valDisplay = document.getElementById(`val-${s.id}`);
input.addEventListener('input', (e) => {
state[s.id] = e.target.value;
valDisplay.innerText = `${state[s.id]}${s.suffix}`;
updateCSS();
});
input.addEventListener('change', saveAll);
});
const overlayColorInp = document.getElementById('inp-overlay_color');
overlayColorInp.addEventListener('input', (e) => {
state.overlay_color = e.target.value;
updateCSS();
});
overlayColorInp.addEventListener('change', saveAll);
const fitInput = document.getElementById('inp-fit_image');
fitInput.addEventListener('change', (e) => {
state.fit_image = e.target.checked;
saveAll();
updateCSS();
});
document.getElementById('cb-reset-btn').addEventListener('click', () => {
state = { ...defaults };
saveAll();
updateUIFromState();
});
function syncBackground() {
const boards = document.querySelectorAll('wc-chess-board, #board-board, .board');
if (boards.length === 0) return;
let activeImgData = null;
if (activeId) {
const img = gallery.find(i => i.id === activeId);
if (img) activeImgData = img.dataUrl;
}
boards.forEach(board => {
let bgLayer = board.querySelector('.custom-board-bg-layer');
if (!bgLayer) {
bgLayer = document.createElement('div');
bgLayer.className = 'custom-board-bg-layer';
if (board.firstChild) {
board.insertBefore(bgLayer, board.firstChild);
} else {
board.appendChild(bgLayer);
}
}
if (activeImgData) {
bgLayer.style.setProperty('background-image', `url(${activeImgData})`, 'important');
} else {
const computed = window.getComputedStyle(board);
let trueBgImage = computed.backgroundImage;
if (trueBgImage && trueBgImage !== 'none' && trueBgImage !== 'initial') {
bgLayer.style.setProperty('background-image', trueBgImage, 'important');
} else {
bgLayer.style.removeProperty('background-image');
}
}
});
}
setInterval(syncBackground, 500);
let isBoardHidden = false;
window.addEventListener('keydown', (e) => {
if (['INPUT', 'TEXTAREA'].includes(e.target.tagName) || e.target.isContentEditable) return;
if ((e.key === 'ç' || e.key === 'Ç') && !isBoardHidden) {
isBoardHidden = true;
document.documentElement.style.setProperty('--cb-board-opacity', '0');
}
});
window.addEventListener('keyup', (e) => {
if (e.key === 'ç' || e.key === 'Ç') {
isBoardHidden = false;
document.documentElement.style.setProperty('--cb-board-opacity', state.board_opacity / 100);
}
});
})();