🖼️✨Chess.com Board Effects

✨ Transforme a aparência do tabuleiro do Chess.com! 🖼️ Personalize cores, filtros, opacidade, imagens e diversos efeitos visuais com facilidade. 🎛️🌈

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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">&times;</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);
        }
    });

})();