Greasy Fork is available in English.
🎨 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)
// ==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();
})();