🖼️🎨 Lichess.org Themes & Interface

🎨 Transforme o Lichess.org em uma experiência única! 🖼️ Crie tabuleiros incríveis, use vídeos e imagens de fundo, personalize cores, efeitos, transparência e deixe seu xadrez com a sua identidade. 🚀✨ (funciona melhor em firefox)

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         🖼️🎨 Lichess.org Themes & Interface
// @namespace    http://tampermonkey.net/
// @version      10.0
// @description  🎨 Transforme o Lichess.org em uma experiência única! 🖼️ Crie tabuleiros incríveis, use vídeos e imagens de fundo, personalize cores, efeitos, transparência e deixe seu xadrez com a sua identidade. 🚀✨ (funciona melhor em firefox)
// @match        *://lichess.org/*
// @match        *://*.lichess.org/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    let currentBlobUrl = null;
    let neonCursorEl = null;
    let neonEnabled = GM_getValue('lichess_custom_neon_enabled', false);
    let neonColor = GM_getValue('lichess_custom_neon_color', '#39FF14');

    // Imagens nativas carregadas direto do GitHub (Formato Raw)
    const defaultBoards = [
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/1.jpg",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/14.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/15.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/19.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/20.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/25.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/33.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/34.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/38.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/40.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/41.png",
        "https://raw.githubusercontent.com/dasfar45/Chessboard-images/main/42.png"
    ];

    // 1. Converte o Base64 pesado em um Link Curto (Blob)
    function base64ToBlobUrl(base64) {
        const parts = base64.split(',');
        const mime = parts[0].match(/:(.*?);/)[1];
        const bstr = atob(parts[1]);
        let n = bstr.length;
        const u8arr = new Uint8Array(n);
        while(n--) {
            u8arr[n] = bstr.charCodeAt(n);
        }
        const blob = new Blob([u8arr], {type: mime});
        return URL.createObjectURL(blob);
    }

    // Aplica o CSS Permanente de Z-Index (Separando Marcações e Setas perfeitamente)
    function injectPermanentStyles() {
        let style = document.getElementById('lichess-custom-permanent-style');
        if (!style) {
            style = document.createElement('style');
            style.id = 'lichess-custom-permanent-style';
            document.head.appendChild(style);
        }
        style.textContent = `
            .cg-wrap cg-board { z-index: auto !important; }
            cg-board piece, .cg-wrap piece { z-index: 2 !important; }

            /* Camada Original (Fica embaixo das peças, exibe apenas os círculos/marcações) */
            .cg-wrap cg-board > svg.cg-shapes { z-index: 1 !important; }
            .cg-wrap cg-board > svg.cg-shapes line,
            .cg-wrap cg-board > svg.cg-shapes marker,
            .cg-wrap cg-board > svg.cg-shapes polygon,
            .cg-wrap cg-board > svg.cg-shapes path { display: none !important; }

            /* Camada Clone Topo (Fica acima das peças, exibe apenas as setas) */
            #lichess-custom-arrows-layer { z-index: 4 !important; }
            #lichess-custom-arrows-layer circle { display: none !important; }
        `;
    }

    // 2. Aplica a imagem na camada de fundo exata do Lichess
    function applyBoardImage(source) {
        let styleEl = document.getElementById('lichess-custom-board-force-style');
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = 'lichess-custom-board-force-style';
            document.head.appendChild(styleEl);
        }

        if (source) {
            let finalUrl = source;
            if (source.startsWith('data:image')) {
                if (currentBlobUrl) URL.revokeObjectURL(currentBlobUrl);
                currentBlobUrl = base64ToBlobUrl(source);
                finalUrl = currentBlobUrl;
            } else {
                if (currentBlobUrl) {
                    URL.revokeObjectURL(currentBlobUrl);
                    currentBlobUrl = null;
                }
            }

            styleEl.textContent = `
                .is2d cg-board::before, .cg-wrap cg-board::before {
                    background-image: url("${finalUrl}") !important;
                    background-size: 100% 100% !important;
                    background-repeat: no-repeat !important;
                    background-position: center !important;
                    opacity: 1 !important;
                    z-index: -1 !important;
                    pointer-events: none !important;
                }
                .is2d cg-board, .cg-wrap cg-board {
                    background-color: transparent !important;
                }
            `;
        } else {
            styleEl.textContent = '';
            if (currentBlobUrl) {
                URL.revokeObjectURL(currentBlobUrl);
                currentBlobUrl = null;
            }
        }
    }

    // Aplica a Opacidade das Casas Negras (Gera o grid em cima de imagens limpas)
    function applyDarkSquares(opacity, colorHex) {
        let styleEl = document.getElementById('lichess-custom-darksquares-style');
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = 'lichess-custom-darksquares-style';
            document.head.appendChild(styleEl);
        }

        if (opacity > 0) {
            const alphaHex = Math.round((opacity / 100) * 255).toString(16).padStart(2, '0');
            const finalColor = colorHex + alphaHex;

            styleEl.textContent = `
                .is2d cg-board::after, .cg-wrap cg-board::after {
                    content: '' !important;
                    position: absolute !important;
                    top: 0 !important; left: 0 !important;
                    width: 100% !important; height: 100% !important;
                    pointer-events: none !important;
                    z-index: 0 !important;
                    background-size: 25% 25% !important;
                    background-image: conic-gradient(${finalColor} 90deg, transparent 90deg 180deg, ${finalColor} 180deg 270deg, transparent 270deg) !important;
                }
            `;
        } else {
            styleEl.textContent = '';
        }
    }

    // Aplica os filtros de imagem em tempo real
    function applyFilters(b, s, c, h) {
        let styleEl = document.getElementById('lichess-custom-filters-style');
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = 'lichess-custom-filters-style';
            document.head.appendChild(styleEl);
        }
        styleEl.textContent = `
            .is2d cg-board::before, .cg-wrap cg-board::before {
                filter: brightness(${b}%) saturate(${s}%) contrast(${c}%) hue-rotate(${h}deg) !important;
            }
        `;
    }

    // Cursor Neon
    function updateNeonCursorManager() {
        if (neonEnabled) {
            if (!neonCursorEl) {
                neonCursorEl = document.createElement('div');
                neonCursorEl.id = 'lichess-custom-neon-cursor';
                document.body.appendChild(neonCursorEl);

                document.addEventListener('mousemove', (e) => {
                    if (neonEnabled && neonCursorEl) {
                        neonCursorEl.style.transform = `translate3d(${e.clientX - 40}px, ${e.clientY - 40}px, 0)`;
                    }
                });
            }
            neonCursorEl.style.display = 'block';
            neonCursorEl.style.position = 'fixed';
            neonCursorEl.style.top = '0';
            neonCursorEl.style.left = '0';
            neonCursorEl.style.width = '80px';
            neonCursorEl.style.height = '80px';
            neonCursorEl.style.borderRadius = '50%';
            neonCursorEl.style.pointerEvents = 'none';
            neonCursorEl.style.zIndex = '9999999';
            neonCursorEl.style.background = `radial-gradient(circle, ${neonColor}99 0%, ${neonColor}50 30%, transparent 70%)`;
            neonCursorEl.style.mixBlendMode = 'screen';
            neonCursorEl.style.willChange = 'transform';
        } else {
            if (neonCursorEl) neonCursorEl.style.display = 'none';
        }
    }

    function injectSvgDefs(color) {
        let svg = document.getElementById('lichess-custom-defs');
        if (!svg) {
            svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svg.id = 'lichess-custom-defs';
            Object.assign(svg.style, {
                position: 'absolute', width: '0', height: '0',
                pointerEvents: 'none', zIndex: '-999'
            });
            document.body.appendChild(svg);
        }

        svg.innerHTML = `
            <defs>
                <radialGradient id="customCircleRadial" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="${color}" stop-opacity="1" />
                    <stop offset="35%" stop-color="${color}" stop-opacity="0.95" />
                    <stop offset="65%" stop-color="${color}" stop-opacity="0.6" />
                    <stop offset="85%" stop-color="${color}" stop-opacity="0.2" />
                    <stop offset="100%" stop-color="${color}" stop-opacity="0" />
                </radialGradient>
            </defs>
        `;
    }

    // 3. Aplica as cores nas Setas e Marcações
    function applyColors(arrowColor, circleColor, circleStyle) {
        let styleEl = document.getElementById('lichess-custom-shapes-style');
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = 'lichess-custom-shapes-style';
            document.head.appendChild(styleEl);
        }

        let cssRules = '';
        if (arrowColor) {
            cssRules += `
                .cg-shapes line {
                    stroke: ${arrowColor} !important;
                    opacity: 1 !important;
                }
                .cg-shapes marker path,
                .cg-shapes marker polygon,
                .cg-shapes polygon {
                    fill: ${arrowColor} !important;
                    stroke: none !important;
                    opacity: 1 !important;
                }
            `;
        }

        if (circleColor) {
            injectSvgDefs(circleColor);
            if (circleStyle === 'radial') {
                cssRules += `
                    .cg-shapes circle {
                        stroke: none !important;
                        fill: url(#customCircleRadial) !important;
                        transform-box: fill-box !important;
                        transform-origin: center !important;
                        transform: scale(1.5) !important;
                    }
                `;
            } else if (circleStyle === 'filled') {
                cssRules += `
                    .cg-shapes circle {
                        stroke: ${circleColor} !important;
                        fill: ${circleColor} !important;
                        opacity: 1 !important;
                    }
                `;
            } else {
                cssRules += `
                    .cg-shapes circle {
                        stroke: ${circleColor} !important;
                        fill: transparent !important;
                        opacity: 1 !important;
                        stroke-width: 0.15 !important;
                    }
                `;
            }
        }
        styleEl.textContent = cssRules;
    }

    // 4. Criador Dinâmico da Camada das Setas (Para colocar APENAS setas acima das peças)
    function initShapesSplitter() {
        setInterval(() => {
            const board = document.querySelector('cg-board');
            if (!board) return;
            const container = board.parentElement;

            let topSvg = document.getElementById('lichess-custom-arrows-layer');
            if (!topSvg) {
                topSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                topSvg.id = 'lichess-custom-arrows-layer';
                topSvg.setAttribute('class', 'cg-shapes');
                Object.assign(topSvg.style, {
                    position: 'absolute', top: 0, left: 0, width: '100%', height: '100%',
                    pointerEvents: 'none', zIndex: 4
                });
                container.appendChild(topSvg);
            }

            const baseSvg = board.querySelector('svg.cg-shapes:not(#lichess-custom-arrows-layer)');
            if (baseSvg) {
                const currentViewBox = baseSvg.getAttribute('viewBox');
                if (topSvg.getAttribute('viewBox') !== currentViewBox) {
                    topSvg.setAttribute('viewBox', currentViewBox || '');
                }
                if (topSvg.innerHTML !== baseSvg.innerHTML) {
                    topSvg.innerHTML = baseSvg.innerHTML;
                }
            } else if (topSvg.innerHTML !== '') {
                topSvg.innerHTML = '';
            }
        }, 50); // Loop ultra-leve, imperceptível pro PC, que separa a visualização em tempo real
    }

    // 5. Criar a Interface (GUI)
    function createGUI() {
        const btn = document.createElement('div');
        btn.innerHTML = '🎨';
        Object.assign(btn.style, {
            position: 'fixed', top: '20px', right: '20px', width: '45px', height: '45px',
            backgroundColor: '#262421', color: 'white', borderRadius: '50%', display: 'flex',
            justifyContent: 'center', alignItems: 'center', fontSize: '20px', cursor: 'pointer',
            boxShadow: '0 4px 12px rgba(0,0,0,0.5)', zIndex: '999999', transition: 'transform 0.2s', userSelect: 'none'
        });
        btn.onmouseenter = () => btn.style.transform = 'scale(1.1)';
        btn.onmouseleave = () => btn.style.transform = 'scale(1)';

        const panel = document.createElement('div');
        Object.assign(panel.style, {
            position: 'fixed', top: '75px', right: '20px', width: '270px', backgroundColor: '#1C1C1C',
            border: '1px solid #333', borderRadius: '12px', padding: '15px', boxShadow: '0 8px 30px rgba(0,0,0,0.9)',
            zIndex: '999999', display: 'none', flexDirection: 'column', gap: '10px', fontFamily: 'system-ui, -apple-system, sans-serif',
            maxHeight: '85vh', overflowY: 'auto', overflowX: 'hidden'
        });

        const extrasContainer = document.createElement('div');
        Object.assign(extrasContainer.style, { display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '5px' });

        const neonRow = document.createElement('div');
        Object.assign(neonRow.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center' });

        const neonLabelWrap = document.createElement('div');
        Object.assign(neonLabelWrap.style, { display: 'flex', alignItems: 'center', gap: '5px' });

        const neonCheckbox = document.createElement('input');
        neonCheckbox.type = 'checkbox';
        neonCheckbox.checked = GM_getValue('lichess_custom_neon_enabled', false);
        const neonLabel = document.createElement('span');
        neonLabel.textContent = 'Neon Cursor';
        neonLabel.style.color = '#39FF14'; neonLabel.style.fontWeight = 'bold'; neonLabel.style.fontSize = '14px';

        neonLabelWrap.appendChild(neonCheckbox);
        neonLabelWrap.appendChild(neonLabel);

        const neonColorPicker = document.createElement('input');
        neonColorPicker.type = 'color';
        neonColorPicker.value = GM_getValue('lichess_custom_neon_color', '#39FF14');
        Object.assign(neonColorPicker.style, { width: '32px', height: '24px', border: 'none', cursor: 'pointer', backgroundColor: 'transparent', padding: '0' });

        neonRow.appendChild(neonLabelWrap);
        neonRow.appendChild(neonColorPicker);

        const updateNeon = () => {
            GM_setValue('lichess_custom_neon_enabled', neonCheckbox.checked);
            GM_setValue('lichess_custom_neon_color', neonColorPicker.value);
            neonEnabled = neonCheckbox.checked;
            neonColor = neonColorPicker.value;
            updateNeonCursorManager();
        };
        neonCheckbox.addEventListener('change', updateNeon);
        neonColorPicker.addEventListener('input', updateNeon);

        extrasContainer.appendChild(neonRow);

        const darkSqContainer = document.createElement('div');
        Object.assign(darkSqContainer.style, { display: 'flex', flexDirection: 'column', gap: '4px' });

        const darkSqHeader = document.createElement('div');
        Object.assign(darkSqHeader.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center' });

        const darkSqLabel = document.createElement('span');
        darkSqLabel.textContent = 'Opacidade das Casas:';
        darkSqLabel.style.color = '#ccc'; darkSqLabel.style.fontSize = '12px';

        const darkSqColorPicker = document.createElement('input');
        darkSqColorPicker.type = 'color';
        darkSqColorPicker.value = GM_getValue('lichess_custom_ds_color', '#000000');
        Object.assign(darkSqColorPicker.style, { width: '32px', height: '24px', border: 'none', cursor: 'pointer', backgroundColor: 'transparent', padding: '0' });

        darkSqHeader.appendChild(darkSqLabel);
        darkSqHeader.appendChild(darkSqColorPicker);

        const darkSqSlider = document.createElement('input');
        darkSqSlider.type = 'range';
        darkSqSlider.min = '0'; darkSqSlider.max = '100';
        darkSqSlider.value = GM_getValue('lichess_custom_ds_opacity', 0);
        Object.assign(darkSqSlider.style, { width: '100%', cursor: 'pointer' });

        const updateDarkSq = () => {
            const op = darkSqSlider.value;
            const col = darkSqColorPicker.value;
            GM_setValue('lichess_custom_ds_opacity', op);
            GM_setValue('lichess_custom_ds_color', col);
            applyDarkSquares(op, col);
        };
        darkSqSlider.addEventListener('input', updateDarkSq);
        darkSqColorPicker.addEventListener('input', updateDarkSq);

        darkSqContainer.appendChild(darkSqHeader);
        darkSqContainer.appendChild(darkSqSlider);
        extrasContainer.appendChild(darkSqContainer);

        const dividerTop = document.createElement('hr');
        Object.assign(dividerTop.style, { border: 'none', borderTop: '1px solid #444', margin: '2px 0' });

        const title = document.createElement('h3');
        title.textContent = 'Customizar Tabuleiro';
        Object.assign(title.style, { margin: '0', fontSize: '16px', fontWeight: '600', textAlign: 'center', color: '#FFFFFF' });

        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = 'image/png, image/jpeg, image/jpg, image/webp';
        Object.assign(fileInput.style, {
            display: 'block', width: '92%', fontSize: '12px', color: '#E0E0E0',
            backgroundColor: '#2C2C2C', padding: '6px', borderRadius: '6px', border: '1px solid #444', cursor: 'pointer'
        });

        const galleryContainer = document.createElement('div');
        const galleryGrid = document.createElement('div');
        Object.assign(galleryGrid.style, {
            display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '6px', padding: '5px 0'
        });
        const expandBtnContainer = document.createElement('div');
        Object.assign(expandBtnContainer.style, { textAlign: 'center', marginTop: '2px' });

        galleryContainer.appendChild(galleryGrid);
        galleryContainer.appendChild(expandBtnContainer);

        let galleryExpanded = false;

        function renderGallery() {
            galleryGrid.innerHTML = '';
            expandBtnContainer.innerHTML = '';

            let userBoards = GM_getValue('lichess_custom_boards_gallery', []);
            const currentActive = GM_getValue('lichess_custom_board_img', '');

            let allBoards = [];
            defaultBoards.forEach(url => allBoards.push({ url, isUser: false }));
            userBoards.forEach((b64, idx) => allBoards.push({ url: b64, isUser: true, index: idx }));

            const visibleCount = galleryExpanded ? allBoards.length : 8;

            for (let i = 0; i < Math.min(allBoards.length, visibleCount); i++) {
                const item = allBoards[i];
                const wrap = document.createElement('div');
                Object.assign(wrap.style, { position: 'relative', width: '100%', aspectRatio: '1/1' });

                const img = document.createElement('img');
                img.src = item.url;
                Object.assign(img.style, {
                    width: '100%', height: '100%', objectFit: 'cover', borderRadius: '4px', cursor: 'pointer',
                    border: currentActive === item.url ? '2px solid #39FF14' : '2px solid transparent', transition: 'border 0.2s'
                });
                img.onclick = () => {
                    GM_setValue('lichess_custom_board_img', item.url);
                    applyBoardImage(item.url);
                    renderGallery();
                };
                wrap.appendChild(img);

                if (item.isUser) {
                    const delBtn = document.createElement('div');
                    delBtn.innerHTML = '×';
                    Object.assign(delBtn.style, {
                        position: 'absolute', top: '-4px', right: '-4px', backgroundColor: '#FF4A4A', color: 'white',
                        borderRadius: '50%', width: '14px', height: '14px', fontSize: '12px', fontWeight: 'bold', display: 'flex',
                        justifyContent: 'center', alignItems: 'center', cursor: 'pointer', boxShadow: '0 2px 4px rgba(0,0,0,0.5)'
                    });
                    delBtn.onclick = (e) => {
                        e.stopPropagation();
                        userBoards.splice(item.index, 1);
                        GM_setValue('lichess_custom_boards_gallery', userBoards);
                        if (currentActive === item.url) {
                            GM_setValue('lichess_custom_board_img', '');
                            applyBoardImage(null);
                        }
                        renderGallery();
                    };
                    wrap.appendChild(delBtn);
                }
                galleryGrid.appendChild(wrap);
            }

            if (allBoards.length > 8) {
                const toggleBtn = document.createElement('div');
                toggleBtn.textContent = galleryExpanded ? 'Ver menos ▲' : `Ver mais ${allBoards.length - 8} ▼`;
                Object.assign(toggleBtn.style, {
                    color: '#ccc', fontSize: '11px', cursor: 'pointer', fontWeight: 'bold', padding: '4px',
                    backgroundColor: '#333', borderRadius: '4px', display: 'inline-block', transition: '0.2s'
                });
                toggleBtn.onmouseenter = () => toggleBtn.style.backgroundColor = '#444';
                toggleBtn.onmouseleave = () => toggleBtn.style.backgroundColor = '#333';
                toggleBtn.onclick = () => {
                    galleryExpanded = !galleryExpanded;
                    renderGallery();
                };
                expandBtnContainer.appendChild(toggleBtn);
            }
        }

        fileInput.addEventListener('change', function(e) {
            const file = e.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = function(event) {
                const img = new Image();
                img.onload = function() {
                    const size = Math.min(img.width, img.height);
                    const startX = (img.width - size) / 2;
                    const startY = (img.height - size) / 2;
                    const canvas = document.createElement('canvas');
                    canvas.width = 1024;
                    canvas.height = 1024;
                    const ctx = canvas.getContext('2d');
                    ctx.drawImage(img, startX, startY, size, size, 0, 0, 1024, 1024);
                    const base64 = canvas.toDataURL('image/jpeg', 0.85);

                    let currentGallery = GM_getValue('lichess_custom_boards_gallery', []);
                    if (!currentGallery.includes(base64)) {
                        currentGallery.push(base64);
                        GM_setValue('lichess_custom_boards_gallery', currentGallery);
                    }

                    GM_setValue('lichess_custom_board_img', base64);
                    applyBoardImage(base64);
                    renderGallery();
                    fileInput.value = '';
                };
                img.src = event.target.result;
            };
            reader.readAsDataURL(file);
        });

        const resetBoardBtn = document.createElement('button');
        resetBoardBtn.textContent = 'Remover Tabuleiro';
        Object.assign(resetBoardBtn.style, {
            backgroundColor: '#444', color: 'white', border: 'none', borderRadius: '6px', padding: '6px',
            fontSize: '12px', fontWeight: 'bold', cursor: 'pointer', transition: 'background-color 0.2s'
        });
        resetBoardBtn.onmouseenter = () => resetBoardBtn.style.backgroundColor = '#666';
        resetBoardBtn.onmouseleave = () => resetBoardBtn.style.backgroundColor = '#444';
        resetBoardBtn.addEventListener('click', () => {
            GM_setValue('lichess_custom_board_img', '');
            applyBoardImage(null);
            renderGallery();
        });

        const divider1 = document.createElement('hr');
        Object.assign(divider1.style, { border: 'none', borderTop: '1px solid #444', margin: '2px 0' });

        const filterControls = document.createElement('div');
        Object.assign(filterControls.style, { display: 'flex', flexDirection: 'column', gap: '5px' });

        function createSlider(labelTxt, gmKey, min, max, def) {
            const row = document.createElement('div');
            Object.assign(row.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center' });

            const lbl = document.createElement('span');
            lbl.textContent = labelTxt;
            Object.assign(lbl.style, { color: '#bbb', fontSize: '12px' });

            const slider = document.createElement('input');
            slider.type = 'range'; slider.min = min; slider.max = max;
            slider.value = GM_getValue(gmKey, def);
            Object.assign(slider.style, { width: '130px', cursor: 'pointer' });

            slider.addEventListener('input', () => {
                GM_setValue(gmKey, slider.value);
                const b = GM_getValue('lichess_custom_brightness', 100);
                const s = GM_getValue('lichess_custom_saturation', 100);
                const c = GM_getValue('lichess_custom_contrast', 100);
                const h = GM_getValue('lichess_custom_hue', 0);
                applyFilters(b, s, c, h);
            });

            row.appendChild(lbl); row.appendChild(slider);
            return row;
        }

        filterControls.appendChild(createSlider('Brilho', 'lichess_custom_brightness', 0, 200, 100));
        filterControls.appendChild(createSlider('Saturação', 'lichess_custom_saturation', 0, 200, 100));
        filterControls.appendChild(createSlider('Contraste', 'lichess_custom_contrast', 0, 200, 100));
        filterControls.appendChild(createSlider('Matiz', 'lichess_custom_hue', 0, 360, 0));

        const divider2 = document.createElement('hr');
        Object.assign(divider2.style, { border: 'none', borderTop: '1px solid #444', margin: '2px 0' });

        const colorControls = document.createElement('div');
        Object.assign(colorControls.style, { display: 'flex', flexDirection: 'column', gap: '6px' });

        const createColorRow = (text, gmKey, defVal, isSelect = false) => {
            const row = document.createElement('div');
            Object.assign(row.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center' });
            const lbl = document.createElement('span');
            lbl.textContent = text;
            Object.assign(lbl.style, { color: '#ccc', fontSize: '13px' });

            let input;
            if (isSelect) {
                input = document.createElement('select');
                Object.assign(input.style, { backgroundColor: '#2C2C2C', color: '#E0E0E0', border: '1px solid #444', borderRadius: '4px', padding: '2px', fontSize: '12px', outline: 'none' });
                input.innerHTML = `<option value="normal">Bambolê</option><option value="filled">Bola</option><option value="radial">Degradê</option>`;
                input.value = GM_getValue(gmKey, defVal);
            } else {
                input = document.createElement('input');
                input.type = 'color';
                input.value = GM_getValue(gmKey, defVal);
                Object.assign(input.style, { width: '32px', height: '24px', border: 'none', cursor: 'pointer', backgroundColor: 'transparent', padding: '0' });
            }

            row.appendChild(lbl);
            row.appendChild(input);
            return { row, input };
        };

        const arrowControl = createColorRow('Cor das Setas:', 'lichess_custom_arrow_color', '#39FF14');
        const circleControl = createColorRow('Cor da marcação:', 'lichess_custom_circle_color', '#00D2FE');
        const styleControl = createColorRow('Estilo da marcação:', 'lichess_custom_circle_style', 'radial', true);

        const updateColors = () => {
            const aColor = arrowControl.input.value;
            const cColor = circleControl.input.value;
            const cStyle = styleControl.input.value;
            GM_setValue('lichess_custom_arrow_color', aColor);
            GM_setValue('lichess_custom_circle_color', cColor);
            GM_setValue('lichess_custom_circle_style', cStyle);
            applyColors(aColor, cColor, cStyle);
        };

        arrowControl.input.addEventListener('input', updateColors);
        circleControl.input.addEventListener('input', updateColors);
        styleControl.input.addEventListener('change', updateColors);

        colorControls.appendChild(arrowControl.row);
        colorControls.appendChild(circleControl.row);
        colorControls.appendChild(styleControl.row);

        const resetColorBtn = document.createElement('button');
        resetColorBtn.textContent = 'Resetar Tudo';
        Object.assign(resetColorBtn.style, {
            backgroundColor: '#4A90E2', color: 'white', border: 'none', borderRadius: '6px',
            padding: '6px', fontSize: '12px', fontWeight: 'bold', cursor: 'pointer', marginTop: '5px'
        });
        resetColorBtn.addEventListener('click', () => {
            GM_setValue('lichess_custom_arrow_color', '#39FF14');
            GM_setValue('lichess_custom_circle_color', '#00D2FE');
            GM_setValue('lichess_custom_circle_style', 'radial');
            GM_setValue('lichess_custom_brightness', 100);
            GM_setValue('lichess_custom_saturation', 100);
            GM_setValue('lichess_custom_contrast', 100);
            GM_setValue('lichess_custom_hue', 0);
            GM_setValue('lichess_custom_ds_opacity', 0);
            GM_setValue('lichess_custom_ds_color', '#000000');
            GM_setValue('lichess_custom_neon_enabled', false);

            arrowControl.input.value = '#39FF14';
            circleControl.input.value = '#00D2FE';
            styleControl.input.value = 'radial';

            const sliders = panel.querySelectorAll('input[type="range"]');
            sliders[0].value = 0;
            sliders[1].value = 100; sliders[2].value = 100; sliders[3].value = 100; sliders[4].value = 0;

            neonCheckbox.checked = false;
            updateNeon();

            applyDarkSquares(0, '#000000');
            applyColors('#39FF14', '#00D2FE', 'radial');
            applyFilters(100, 100, 100, 0);
        });
        colorControls.appendChild(resetColorBtn);

        panel.appendChild(extrasContainer);
        panel.appendChild(dividerTop);
        panel.appendChild(title);
        panel.appendChild(fileInput);
        panel.appendChild(galleryContainer);
        panel.appendChild(resetBoardBtn);
        panel.appendChild(divider1);
        panel.appendChild(filterControls);
        panel.appendChild(divider2);
        panel.appendChild(colorControls);

        document.body.appendChild(panel);
        document.body.appendChild(btn);

        btn.addEventListener('click', () => {
            panel.style.display = panel.style.display === 'none' ? 'flex' : 'none';
        });

        renderGallery();
    }

    // 6. Inicializa o script puxando as infos
    function init() {
        injectPermanentStyles();

        const savedImg = GM_getValue('lichess_custom_board_img', null);
        if (savedImg) applyBoardImage(savedImg);

        const dsOpacity = GM_getValue('lichess_custom_ds_opacity', 0);
        const dsColor = GM_getValue('lichess_custom_ds_color', '#000000');
        applyDarkSquares(dsOpacity, dsColor);

        const b = GM_getValue('lichess_custom_brightness', 100);
        const s = GM_getValue('lichess_custom_saturation', 100);
        const c = GM_getValue('lichess_custom_contrast', 100);
        const h = GM_getValue('lichess_custom_hue', 0);
        applyFilters(b, s, c, h);

        const savedArrow = GM_getValue('lichess_custom_arrow_color', '#39FF14');
        const savedCircle = GM_getValue('lichess_custom_circle_color', '#00D2FE');
        const savedStyle = GM_getValue('lichess_custom_circle_style', 'radial');
        applyColors(savedArrow, savedCircle, savedStyle);

        updateNeonCursorManager();
        createGUI();
        initShapesSplitter(); // Inicia o motorzinho que separa perfeitamente a visão
    }

    init();

})();