// ==UserScript==
// @name ♔ Red King ♔
// @name:en ♔ Red King ♔
// @name:es ♔ Red King ♔
// @name:fr ♔ Red King ♔
// @name:de ♔ Red King ♔
// @name:it ♔ Red King ♔
// @name:pt ♔ Red King ♔
// @name:ru ♔ Красный Король ♔
// @name:zh-CN ♔ 红王 ♔
// @name:ja ♔ レッドキング ♔
// @name:ko ♔ 레드 킹 ♔
// @name:ar ♔ الملك الأحمر ♔
// @name:hi ♔ रेड किंग ♔
// @name:tr ♔ Kırmızı Kral ♔
// @name:pl ♔ Czerwony Król ♔
// @name:nl ♔ Rode Koning ♔
// @name:uk ♔ Червоний Король ♔
// @name:ca ♔ Rei Vermell ♔
// @name:sv ♔ Röda Kungen ♔
// @name:th ♔ ราชาแดง ♔
// @name:el ♔ Κόκκινος Βασιλιάς ♔
// @name:hu ♔ Vörös Király ♔
// @name:cs ♔ Červený Král ♔
// @name:ro ♔ Regele Roșu ♔
// @name:fi ♔ Punainen Kuningas ♔
// @name:no ♔ Røde Kongen ♔
// @name:da ♔ Røde Konge ♔
// @name:sk ♔ Červený Kráľ ♔
// @name:bg ♔ Червеният крал ♔
// @namespace https://lichess.org/@/IamGi4nx
// @version 2.4.1.4
// @description ¡RED KING ♔! El cliente número 1 en personalización y comodidad.
// @description:en RED KING ♔! The #1 client for customization and comfort.
// @description:es ¡RED KING ♔! El cliente número uno en personalización y comodidad.
// @description:fr RED KING ♔ ! Le client n°1 pour la personnalisation et le confort.
// @description:de RED KING ♔! Der #1-Client für Anpassung und Komfort.
// @description:it RED KING ♔! Il client numero 1 per personalizzazione e comfort.
// @description:pt RED KING ♔! O cliente nº1 em personalização e conforto.
// @description:ru RED KING ♔! Клиент №1 по персонализации и удобству.
// @description:zh-CN RED KING ♔!定制与舒适度排名第一的客户端。
// @description:ja RED KING ♔!カスタマイズ性と快適さでNo.1のクライアント。
// @description:ko RED KING ♔! 맞춤화와 편안함에서 최고의 클라이언트입니다.
// @description:ar RED KING ♔! العميل رقم 1 في التخصيص والراحة.
// @description:hi RED KING ♔! कस्टमाइज़ेशन और आराम के लिए नंबर 1 क्लाइंट।
// @description:tr RED KING ♔! Özelleştirme ve konfor konusunda 1 numaralı istemci.
// @description:pl RED KING ♔! Numer 1 w personalizacji i komforcie.
// @description:nl RED KING ♔! De #1 klant voor maatwerk en comfort.
// @description:uk RED KING ♔! Клієнт №1 з персоналізації та комфорту.
// @description:qu RED KING ♔! Ñawpaqmi kikinmanta ruwasqa hina kuska ima hina kanki!
// @description:ca RED KING ♔! El client número 1 en personalització i comoditat.
// @description:sv RED KING ♔! Den bästa klienten för anpassning och komfort.
// @description:th RED KING ♔! ไคลเอนต์อันดับ 1 ด้านการปรับแต่งและความสะดวกสบาย
// @description:el RED KING ♔! Ο νούμερο 1 πελάτης για προσαρμογή και άνεση.
// @description:hu RED KING ♔! Az első számú kliens testreszabáshoz és kényelemhez.
// @description:cs RED KING ♔! Klient číslo 1 pro přizpůsobení a pohodlí.
// @description:ro RED KING ♔! Clientul nr.1 pentru personalizare și confort.
// @description:fi RED KING ♔! Ykköskäyttäjä mukautettavuudessa ja mukavuudessa.
// @description:no RED KING ♔! Den #1 klienten for tilpasning og komfort.
// @description:da RED KING ♔! Den bedste klient til tilpasning og komfort.
// @description:sk RED KING ♔! Klient číslo 1 pre prispôsobenie a pohodlie.
// @description:bg RED KING ♔! Клиент №1 за персонализиране и комфорт.
// @author IamGi4nx
// @match https://lichess.org/*
// @match https://*.lichess.org/*
// @icon https://openclipart.org/image/2000px/275290
// @license Copyright (c) 2025 IamGi4nx. All rights reserved.
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_openInTab
// @grant GM_notification
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// === ESTILOS ROJIZOS PERSONALIZADOS ===
GM_addStyle(`
#redKingPanel {
background: linear-gradient(145deg, #1a0a0a 0%, #2b0e0e 100%) !important;
border: 2px solid #b22222 !important;
border-radius: 15px !important;
box-shadow: 0 15px 35px rgba(0,0,0,0.9), 0 0 20px rgba(178,34,34,0.5) !important;
overflow: hidden !important;
backdrop-filter: blur(10px) !important;
}
#redKingPanel #panelHeader {
background: linear-gradient(135deg, #8B0000 0%, #DC143C 100%) !important;
color: #FFD7D7 !important;
font-weight: bold !important;
}
#redKingPanel .tab-btn {
flex: 1;
padding: 10px 6px;
text-align: center;
font-size: 11px;
cursor: pointer;
background: #3a1f1f !important;
color: #ddd !important;
transition: all 0.3s ease;
}
#redKingPanel .tab-btn.active {
background: #b22222 !important;
color: #FFD700 !important;
}
#redKingPanel .action-btn, #redKingPanel .social-btn {
background: linear-gradient(135deg, #a83232 0%, #7a0e0e 100%) !important;
color: #fff !important;
border: none !important;
border-radius: 6px !important;
padding: 8px 12px !important;
font-size: 12px !important;
cursor: pointer !important;
box-shadow: 0 4px 10px rgba(0,0,0,0.3) !important;
transition: all 0.25s ease;
}
#redKingPanel .action-btn:hover, #redKingPanel .social-btn:hover {
background: linear-gradient(135deg, #c0392b 0%, #922b21 100%) !important;
transform: translateY(-2px);
}
.toggle-switch input:checked + .toggle-slider {
background-color: #b22222 !important;
}
#redKingTopNotification {
background: linear-gradient(90deg, #a10c0c 0%, #b22222 100%) !important;
color: #fff !important;
text-shadow: 0 1px 2px rgba(0,0,0,0.5) !important;
}
#redKingMusicPlayer {
background: linear-gradient(145deg, #2a0e0e 0%, #1a0a0a 100%) !important;
border: 2px solid #b22222 !important;
border-radius: 10px !important;
}
#redKingMusicPlayer button {
background: #b22222 !important;
color: #fff !important;
border: none !important;
border-radius: 4px !important;
padding: 4px 8px !important;
cursor: pointer !important;
}
#redKingMusicPlayer button:hover {
background: #c0392b !important;
}
.sprite-upload-btn {
background: #8B0000 !important;
}
.sprite-reset-btn {
background: #a83232 !important;
}
#redKingPanel h4, #redKingPanel h5 {
color: #ffaaaa !important;
}
#redKingPanel input[type="color"] {
border: 2px solid #b22222 !important;
}
`);
console.log('🔴♔ Red King v2.4.1 iniciando...');
// Configuración por defecto mejorada
const defaultConfig = {
theme: 'red-king',
soundEnabled: false,
autoRotate: false,
highlightMoves: false,
showStats: false,
autoAnalysis: false,
customSounds: false,
boardEffects: false,
chatVisible: true,
boardMoveable: false,
// Nuevas opciones CORREGIDAS
musicPlayer: {
enabled: false,
youtubeUrl: '',
volume: 0.5,
autoplay: false
},
customSprites: {
enabled: false,
pieces: {
'white-king': '',
'white-queen': '',
'white-rook': '',
'white-bishop': '',
'white-knight': '',
'white-pawn': '',
'black-king': '',
'black-queen': '',
'black-rook': '',
'black-bishop': '',
'black-knight': '',
'black-pawn': ''
}
},
customColors: {
primary: '#8B0000',
secondary: '#DC143C',
accent: '#FFD700',
background: '#1a1a1a',
text: '#ffffff',
chatText: '#ffffff'
},
buttonColors: {
enabled: false,
buttonPrimary: '#8B0000',
buttonSecondary: '#DC143C',
hoverPrimary: '#DC143C',
hoverSecondary: '#8B0000',
textColor: '#ffffff'
},
shortcuts: {
flipBoard: 'f',
resign: 'r',
takeback: 't',
analysis: 'a',
panel: 'p',
toggleChat: 'c'
},
userLinks: {
discord: 'https://discord.gg/2mqdDJAZdq',
github: 'https://github.com/gi4nxdepelover'
}
};
// Variables globales
let config = GM_getValue('redKingConfig241', defaultConfig);
let panelVisible = true;
let currentTab = 'general';
let boardMoved = false;
let originalBoardPosition = null;
let boardDragListeners = [];
let musicPlayerInstance = null;
let customSpritesApplied = false;
let youtubeAPIReady = false;
// === SISTEMA DE AVISOS SUPERIORES ===
function showTopNotification(message, type = 'success', duration = 3000) {
const notification = document.createElement('div');
notification.id = 'redKingTopNotification';
const typeStyles = {
success: { bg: '#4CAF50', icon: '✅' },
error: { bg: '#f44336', icon: '❌' },
warning: { bg: '#ff9800', icon: '⚠️' },
info: { bg: '#2196F3', icon: 'ℹ️' },
saved: { bg: '#8BC34A', icon: '💾' }
};
const style = typeStyles[type] || typeStyles.success;
notification.style.cssText = `
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
background: ${style.bg} !important;
color: white !important;
padding: 12px 20px !important;
text-align: center !important;
z-index: 999999999 !important;
font-family: 'Segoe UI', sans-serif !important;
font-size: 14px !important;
font-weight: 600 !important;
box-shadow: 0 2px 10px rgba(0,0,0,0.3) !important;
transform: translateY(-100%) !important;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
`;
notification.innerHTML = `
<span style="margin-right: 10px; font-size: 16px;">${style.icon}</span>
${message}
`;
document.body.appendChild(notification);
// Animación de entrada
setTimeout(() => {
notification.style.transform = 'translateY(0)';
}, 50);
// Animación de salida
setTimeout(() => {
notification.style.transform = 'translateY(-100%)';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 400);
}, duration);
}
// === REPRODUCTOR DE MÚSICA YOUTUBE CORREGIDO ===
class YouTubeMusicPlayer {
constructor() {
this.player = null;
this.playerReady = false;
this.currentVideoId = '';
this.isPlaying = false;
this.volume = 0.5;
this.playerContainer = null;
this.intervalId = null;
}
async initPlayer(videoUrl) {
try {
const videoId = this.extractVideoId(videoUrl);
if (!videoId) {
showTopNotification('❌ URL de YouTube inválido', 'error');
return false;
}
console.log('🎵 Inicializando reproductor con video ID:', videoId);
this.currentVideoId = videoId;
// Crear contenedor del reproductor
this.createPlayerContainer();
// Cargar API de YouTube si no está cargada
await this.loadYouTubeAPI();
// Esperar a que la API esté lista
await this.waitForYouTubeAPI();
// Crear reproductor
this.player = new YT.Player('redKingYouTubePlayer', {
height: '0',
width: '0',
videoId: videoId,
playerVars: {
'autoplay': config.musicPlayer.autoplay ? 1 : 0,
'controls': 0,
'disablekb': 1,
'fs': 0,
'modestbranding': 1,
'rel': 0,
'loop': 1,
'playlist': videoId
},
events: {
'onReady': this.onPlayerReady.bind(this),
'onStateChange': this.onPlayerStateChange.bind(this),
'onError': this.onPlayerError.bind(this)
}
});
return true;
} catch (error) {
console.error('Error inicializando reproductor:', error);
showTopNotification('❌ Error al inicializar reproductor de música', 'error');
return false;
}
}
extractVideoId(url) {
const regexes = [
/(?:youtube\.com\/watch\?v=)([^&\n?#]+)/,
/(?:youtube\.com\/embed\/)([^&\n?#]+)/,
/(?:youtu\.be\/)([^&\n?#]+)/,
/(?:youtube\.com\/v\/)([^&\n?#]+)/,
/(?:youtube\.com\/.*v=)([^&\n?#]+)/
];
for (const regex of regexes) {
const match = url.match(regex);
if (match && match[1]) return match[1];
}
return null;
}
async loadYouTubeAPI() {
return new Promise((resolve) => {
if (window.YT && window.YT.Player) {
console.log('🎵 API de YouTube ya cargada');
youtubeAPIReady = true;
resolve();
return;
}
console.log('🎵 Cargando API de YouTube...');
// Crear callback global
window.onYouTubeIframeAPIReady = function() {
console.log('🎵 API de YouTube lista');
youtubeAPIReady = true;
resolve();
};
if (!document.querySelector('script[src*="youtube.com/iframe_api"]')) {
const script = document.createElement('script');
script.src = 'https://www.youtube.com/iframe_api';
script.async = true;
document.head.appendChild(script);
}
});
}
async waitForYouTubeAPI() {
return new Promise((resolve) => {
if (window.YT && window.YT.Player) {
resolve();
return;
}
const checkAPI = () => {
if (window.YT && window.YT.Player) {
resolve();
} else {
setTimeout(checkAPI, 100);
}
};
checkAPI();
});
}
createPlayerContainer() {
if (this.playerContainer) {
this.playerContainer.remove();
}
this.playerContainer = document.createElement('div');
this.playerContainer.id = 'redKingMusicPlayer';
this.playerContainer.style.cssText = `
position: fixed !important;
bottom: 20px !important;
left: 20px !important;
width: 320px !important;
height: auto !important;
background: linear-gradient(145deg, #1a1a1a 0%, #2d2d2d 100%) !important;
border: 2px solid #8B0000 !important;
border-radius: 10px !important;
padding: 15px !important;
color: white !important;
font-family: 'Segoe UI', sans-serif !important;
z-index: 999998 !important;
box-shadow: 0 10px 25px rgba(0,0,0,0.7) !important;
backdrop-filter: blur(10px) !important;
`;
this.playerContainer.innerHTML = `
<div style="margin-bottom: 10px; font-size: 14px; color: #FFD700; font-weight: bold;">
🎵 Reproductor de Música YouTube
</div>
<div id="redKingYouTubePlayer"></div>
<div id="musicControls" style="margin-top: 15px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<button id="playPauseBtn" class="music-btn">▶️</button>
<button id="prevBtn" class="music-btn">⏮️</button>
<button id="nextBtn" class="music-btn">⏭️</button>
<button id="stopBtn" class="music-btn">⏹️</button>
</div>
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<span style="font-size: 12px; margin-right: 10px;">🔊</span>
<input type="range" id="volumeSlider" min="0" max="100" value="50" style="flex: 1; margin-right: 10px;">
<span id="volumeValue" style="font-size: 12px; color: #FFD700; width: 35px;">50%</span>
</div>
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<span style="font-size: 12px; margin-right: 10px;">⏳</span>
<input type="range" id="progressSlider" min="0" max="100" value="0" style="flex: 1; margin-right: 10px;">
<span id="timeDisplay" style="font-size: 12px; color: #ccc; width: 80px;">0:00 / 0:00</span>
</div>
<div style="text-align: center;">
<button id="closeMusicPlayer" style="background: #ff4444; border: none; color: white; padding: 6px 12px; border-radius: 4px; font-size: 12px; cursor: pointer;">❌ Cerrar</button>
</div>
</div>
`;
document.body.appendChild(this.playerContainer);
this.setupMusicControls();
}
setupMusicControls() {
const playPauseBtn = document.getElementById('playPauseBtn');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const stopBtn = document.getElementById('stopBtn');
const volumeSlider = document.getElementById('volumeSlider');
const progressSlider = document.getElementById('progressSlider');
const closeMusicPlayer = document.getElementById('closeMusicPlayer');
playPauseBtn?.addEventListener('click', () => this.togglePlayPause());
prevBtn?.addEventListener('click', () => this.seekRelative(-10));
nextBtn?.addEventListener('click', () => this.seekRelative(10));
stopBtn?.addEventListener('click', () => this.stop());
volumeSlider?.addEventListener('input', (e) => {
const volume = e.target.value / 100;
this.setVolume(volume);
document.getElementById('volumeValue').textContent = e.target.value + '%';
});
progressSlider?.addEventListener('change', (e) => {
if (this.player && this.playerReady) {
const duration = this.player.getDuration();
const newTime = (e.target.value / 100) * duration;
this.player.seekTo(newTime, true);
}
});
closeMusicPlayer?.addEventListener('click', () => this.destroy());
// Actualizar progreso cada segundo
this.startProgressUpdate();
}
startProgressUpdate() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
this.intervalId = setInterval(() => this.updateProgress(), 1000);
}
onPlayerReady(event) {
console.log('🎵 Reproductor listo');
this.playerReady = true;
this.player.setVolume(this.volume * 100);
showTopNotification('🎵 Reproductor de música listo', 'success');
}
onPlayerStateChange(event) {
const playPauseBtn = document.getElementById('playPauseBtn');
if (event.data === YT.PlayerState.PLAYING) {
this.isPlaying = true;
if (playPauseBtn) playPauseBtn.textContent = '⏸️';
} else if (event.data === YT.PlayerState.PAUSED || event.data === YT.PlayerState.ENDED) {
this.isPlaying = false;
if (playPauseBtn) playPauseBtn.textContent = '▶️';
}
}
onPlayerError(event) {
console.error('Error en reproductor de YouTube:', event.data);
showTopNotification('❌ Error en el reproductor de música', 'error');
}
togglePlayPause() {
if (!this.player || !this.playerReady) return;
if (this.isPlaying) {
this.player.pauseVideo();
} else {
this.player.playVideo();
}
}
seekRelative(seconds) {
if (!this.player || !this.playerReady) return;
const currentTime = this.player.getCurrentTime();
const newTime = Math.max(0, currentTime + seconds);
this.player.seekTo(newTime, true);
}
setVolume(volume) {
this.volume = Math.max(0, Math.min(1, volume));
if (this.player && this.playerReady) {
this.player.setVolume(this.volume * 100);
}
}
stop() {
if (this.player && this.playerReady) {
this.player.stopVideo();
}
}
updateProgress() {
if (!this.player || !this.playerReady || !document.getElementById('progressSlider')) return;
try {
const currentTime = this.player.getCurrentTime();
const duration = this.player.getDuration();
if (duration > 0) {
const progress = (currentTime / duration) * 100;
document.getElementById('progressSlider').value = progress;
const timeDisplay = document.getElementById('timeDisplay');
if (timeDisplay) {
timeDisplay.textContent = `${this.formatTime(currentTime)} / ${this.formatTime(duration)}`;
}
}
} catch (error) {
// Silenciar errores de actualización
}
}
formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
destroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
if (this.player) {
try {
this.player.destroy();
} catch (error) {
console.warn('Error destroying player:', error);
}
this.player = null;
}
if (this.playerContainer) {
this.playerContainer.remove();
this.playerContainer = null;
}
this.playerReady = false;
this.isPlaying = false;
showTopNotification('🎵 Reproductor de música cerrado', 'info');
}
}
// === SISTEMA DE SPRITES PERSONALIZADOS CORREGIDO ===
class CustomSpritesManager {
constructor() {
this.customStyleSheet = null;
this.applied = false;
}
initCustomSprites() {
console.log('🎨 Inicializando sprites personalizados...');
if (!this.customStyleSheet) {
this.customStyleSheet = document.createElement('style');
this.customStyleSheet.id = 'redKingCustomSprites';
document.head.appendChild(this.customStyleSheet);
}
this.applyCustomSprites();
this.applied = true;
}
applyCustomSprites() {
if (!config.customSprites.enabled) {
this.removeCustomSprites();
return;
}
console.log('🎨 Aplicando sprites personalizados...');
let cssRules = '';
// Selectores CSS más específicos y compatibles con Lichess
const pieceSelectors = {
'white-king': 'piece.white.king, .cg-board piece[data-color="white"][data-role="king"]',
'white-queen': 'piece.white.queen, .cg-board piece[data-color="white"][data-role="queen"]',
'white-rook': 'piece.white.rook, .cg-board piece[data-color="white"][data-role="rook"]',
'white-bishop': 'piece.white.bishop, .cg-board piece[data-color="white"][data-role="bishop"]',
'white-knight': 'piece.white.knight, .cg-board piece[data-color="white"][data-role="knight"]',
'white-pawn': 'piece.white.pawn, .cg-board piece[data-color="white"][data-role="pawn"]',
'black-king': 'piece.black.king, .cg-board piece[data-color="black"][data-role="king"]',
'black-queen': 'piece.black.queen, .cg-board piece[data-color="black"][data-role="queen"]',
'black-rook': 'piece.black.rook, .cg-board piece[data-color="black"][data-role="rook"]',
'black-bishop': 'piece.black.bishop, .cg-board piece[data-color="black"][data-role="bishop"]',
'black-knight': 'piece.black.knight, .cg-board piece[data-color="black"][data-role="knight"]',
'black-pawn': 'piece.black.pawn, .cg-board piece[data-color="black"][data-role="pawn"]'
};
let hasCustomSprites = false;
for (const [piece, selector] of Object.entries(pieceSelectors)) {
const customSprite = config.customSprites.pieces[piece];
if (customSprite && customSprite.trim()) {
cssRules += `
${selector} {
background-image: url('${customSprite}') !important;
background-size: cover !important;
background-repeat: no-repeat !important;
background-position: center !important;
}
`;
hasCustomSprites = true;
console.log('🎨 Sprite aplicado para:', piece);
}
}
if (this.customStyleSheet) {
this.customStyleSheet.textContent = cssRules;
console.log('🎨 CSS de sprites actualizado:', hasCustomSprites ? 'Con sprites' : 'Sin sprites');
}
}
removeCustomSprites() {
console.log('🎨 Removiendo sprites personalizados...');
if (this.customStyleSheet) {
this.customStyleSheet.textContent = '';
}
}
uploadSprite(pieceType) {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) return;
console.log('🎨 Subiendo sprite para:', pieceType);
if (!file.type.startsWith('image/')) {
showTopNotification('❌ Por favor selecciona un archivo de imagen', 'error');
return;
}
// Verificar tamaño del archivo (máximo 5MB)
if (file.size > 5 * 1024 * 1024) {
showTopNotification('❌ La imagen es muy grande (máximo 5MB)', 'error');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const dataUrl = e.target.result;
config.customSprites.pieces[pieceType] = dataUrl;
GM_setValue('redKingConfig241', config);
console.log('🎨 Sprite guardado para:', pieceType);
// Actualizar vista previa
const preview = document.getElementById(`preview_${pieceType.replace('-', '_')}`);
if (preview) {
preview.src = dataUrl;
preview.style.display = 'block';
}
// Aplicar sprites inmediatamente
this.applyCustomSprites();
showTopNotification(`✅ Sprite de ${pieceType.replace('-', ' ')} guardado`, 'saved');
};
reader.onerror = () => {
showTopNotification('❌ Error al leer el archivo', 'error');
};
reader.readAsDataURL(file);
});
input.click();
}
resetSprite(pieceType) {
config.customSprites.pieces[pieceType] = '';
GM_setValue('redKingConfig241', config);
const preview = document.getElementById(`preview_${pieceType.replace('-', '_')}`);
if (preview) {
preview.style.display = 'none';
preview.src = '';
}
this.applyCustomSprites();
showTopNotification(`🔄 Sprite de ${pieceType.replace('-', ' ')} restablecido`, 'info');
}
}
// Instancias globales
const spritesManager = new CustomSpritesManager();
// === FUNCIÓN DE VOLTEAR TABLERO SÚPER MEJORADA ===
function flipBoardAction() {
console.log('🔄 Iniciando función de voltear tablero...');
try {
let success = false;
// MÉTODO 1: Detectar botón de flip de Lichess
const flipButtons = [
'.game .game__buttons .fbt',
'.analyse__tools .fbt',
'button.fbt',
'.flip',
'button[title*="flip"]',
'button[title*="Flip"]',
'.game__menu button[data-icon="B"]',
'.lpv__fbt'
];
console.log('🔍 Buscando botones de flip...');
for (const selector of flipButtons) {
const button = document.querySelector(selector);
if (button && button.offsetParent !== null) {
console.log('✅ Encontrado botón flip:', selector);
button.click();
success = true;
showTopNotification('🔄 Tablero volteado', 'success');
break;
}
}
// MÉTODO 2: Si no encontró botón, usar eventos de teclado
if (!success) {
console.log('🎹 Intentando con eventos de teclado...');
// Enfocar el tablero primero
const boardElement = document.querySelector('.cg-wrap, .game, .analyse');
if (boardElement) {
boardElement.focus();
}
// Crear y disparar evento keydown más completo
const keyEvent = new KeyboardEvent('keydown', {
key: 'f',
code: 'KeyF',
keyCode: 70,
which: 70,
charCode: 0,
bubbles: true,
cancelable: true,
composed: true,
view: window,
detail: 0
});
// Disparar en múltiples elementos
const targets = [
document,
document.body,
document.querySelector('.cg-wrap'),
document.querySelector('.main-wrap'),
document.querySelector('.game'),
document.querySelector('.analyse')
].filter(el => el);
targets.forEach(target => {
try {
target.dispatchEvent(keyEvent);
} catch (e) {
console.warn('Error dispatching to:', target, e);
}
});
// También keyup
const keyUpEvent = new KeyboardEvent('keyup', {
key: 'f',
code: 'KeyF',
keyCode: 70,
which: 70,
bubbles: true,
cancelable: true
});
targets.forEach(target => {
try {
target.dispatchEvent(keyUpEvent);
} catch (e) {
console.warn('Error dispatching keyup to:', target, e);
}
});
showTopNotification('🔄 Comando de volteo enviado', 'info');
success = true;
}
// MÉTODO 3: Como último recurso, CSS flip
if (!success) {
console.log('🎨 Intentando flip CSS...');
const board = document.querySelector('.cg-board, .cg-wrap');
if (board) {
const currentTransform = board.style.transform || '';
if (currentTransform.includes('rotate(180deg)')) {
board.style.transform = currentTransform.replace('rotate(180deg)', '').trim();
showTopNotification('🔄 Tablero normal (CSS)', 'success');
} else {
board.style.transform = (currentTransform + ' rotate(180deg)').trim();
showTopNotification('🔄 Tablero volteado (CSS)', 'success');
}
success = true;
}
}
if (!success) {
console.warn('⚠️ No se pudo voltear el tablero');
showTopNotification('⚠️ No se pudo voltear automáticamente - Intenta presionar "F"', 'warning');
}
} catch (error) {
console.error('❌ Error crítico al voltear tablero:', error);
showTopNotification('❌ Error al voltear tablero', 'error');
}
}
// Crear panel principal mejorado
function createPanel() {
console.log('Creando panel Red King v2.4.1...');
if (document.getElementById('redKingPanel')) {
document.getElementById('redKingPanel').remove();
}
const panel = document.createElement('div');
panel.id = 'redKingPanel';
panel.style.cssText = `
position: fixed !important;
top: 20px !important;
right: 20px !important;
width: 380px !important;
max-height: 700px !important;
background: linear-gradient(145deg, #1a1a1a 0%, #2d2d2d 100%) !important;
border: 2px solid #8B0000 !important;
border-radius: 15px !important;
padding: 0 !important;
color: white !important;
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif !important;
box-shadow: 0 15px 35px rgba(0,0,0,0.9), 0 0 20px rgba(139,0,0,0.3) !important;
z-index: 999999 !important;
overflow: hidden !important;
backdrop-filter: blur(10px) !important;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
`;
panel.innerHTML = createPanelHTML();
document.body.appendChild(panel);
setupPanelEvents();
makePanelDraggable(panel);
showTopNotification('♔ Red King v2.4.1 activado! - Gracias por descargar RedKing <3', 'success', 3000);
return panel;
}
// HTML del panel con nuevas opciones
function createPanelHTML() {
return `
<div id="panelHeader" style="
background: linear-gradient(135deg, #8B0000 0%, #DC143C 100%);
padding: 15px;
text-align: center;
font-size: 18px;
color: #FFD700;
cursor: move;
user-select: none;
border-radius: 15px 15px 0 0;
position: relative;
">
<span style="font-weight: bold;">♔ Red King v2.4.1</span>
<div style="position: absolute; top: 50%; right: 15px; transform: translateY(-50%); cursor: pointer; font-size: 16px;" id="minimizeBtn">−</div>
</div>
<div id="panelContent" style="padding: 0;">
<div id="tabNavigation" style="
display: flex;
background: #2a2a2a;
border-bottom: 1px solid #444;
">
<div class="tab-btn active" data-tab="general">⚙️ General</div>
<div class="tab-btn" data-tab="board">🏁 Tablero</div>
<div class="tab-btn" data-tab="music">🎵 Música</div>
<div class="tab-btn" data-tab="sprites">🎨 Sprites</div>
<div class="tab-btn" data-tab="theme">🌈 Tema</div>
</div>
<div id="tabContent" style="padding: 20px; max-height: 480px; overflow-y: auto;">
${createTabContent()}
</div>
</div>
<div style="
background: #2a2a2a;
padding: 12px;
text-align: center;
font-size: 11px;
color: #888;
border-radius: 0 0 15px 15px;
border-top: 1px solid #444;
">
<div style="margin-bottom: 8px;">
<button id="restoreNormal" style="
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
border: none;
color: white;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 11px;
font-weight: 500;
margin-bottom: 8px;
">🔄 Restaurar Normalidad</button>
</div>
<div style="margin-bottom: 5px;">
<span id="closePanel" style="cursor: pointer; color: #DC143C; margin-right: 15px;">❌ Cerrar</span>
<span id="resetConfig" style="cursor: pointer; color: #FFD700;">🔄 Reset Config</span>
</div>
<div>v2.4.1 - Hecho por Gi4nx con ❤️</div>
</div>
`;
}
// Contenido de pestañas mejorado
function createTabContent() {
return `
<!-- Pestaña General -->
<div id="tab-general" class="tab-content" style="display: block;">
<div class="option-group">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🎮 Opciones Básicas</h4>
${createToggleOption('soundEnabled', 'Sonidos mejorados', '🔊')}
${createToggleOption('highlightMoves', 'Resaltar movimientos', '✨')}
${createToggleOption('boardEffects', 'Efectos de tablero', '💫')}
</div>
<div class="option-group" style="margin-top: 20px;">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">⚡ Acciones Rápidas</h4>
<div style="display: grid; gap: 8px;">
<button id="flipBoard" class="action-btn">🔄 Girar Tablero</button>
<button id="quickAnalysis" class="action-btn">📊 Análisis Rápido</button>
<button id="exportPGN" class="action-btn">📄 Exportar PGN</button>
</div>
</div>
<div class="option-group" style="margin-top: 15px;">
<h4 style="color: #FFD700; margin: 0 0 10px 0; font-size: 12px;">⌨️ Atajos</h4>
<div style="font-size: 11px; color: #ccc; line-height: 1.4;">
<kbd>F</kbd> Girar • <kbd>A</kbd> Análisis • <kbd>P</kbd> Panel • <kbd>C</kbd> Chat
</div>
</div>
</div>
<!-- Pestaña Tablero -->
<div id="tab-board" class="tab-content" style="display: none;">
<div class="option-group">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🏁 Controles de Tablero</h4>
<div style="margin-bottom: 15px; padding: 10px; background: rgba(255,215,0,0.1); border-radius: 6px; border: 1px solid #FFD700;">
<div style="font-size: 12px; color: #FFD700; margin-bottom: 8px;">📐 Estado del Tablero:</div>
<div id="boardStatus" style="font-size: 11px; color: #ccc;">Normal (no movible)</div>
</div>
<div style="display: grid; gap: 8px;">
<button id="toggleBoardMove" class="action-btn">📐 Activar/Desactivar Mover</button>
<button id="resetBoardPosition" class="action-btn">🎯 Restaurar Posición</button>
<button id="toggleChat" class="action-btn">💬 Toggle Chat</button>
<button id="hideChat" class="action-btn">👁️ Ocultar Chat</button>
<button id="showChat" class="action-btn">👁️🗨️ Mostrar Chat</button>
</div>
</div>
<div class="option-group" style="margin-top: 20px;">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🎨 Colores de Texto</h4>
${createColorPicker('customColors.text', '📄 Texto General')}
${createColorPicker('customColors.chatText', '💬 Texto Chat')}
</div>
<div class="option-group" style="margin-top: 15px;">
<button id="applyBoardTheme" class="action-btn">✨ Aplicar Cambios</button>
</div>
</div>
<!-- Pestaña Música -->
<div id="tab-music" class="tab-content" style="display: none;">
<div class="option-group">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🎵 Reproductor de Música<br> (¡OBSOLETO! Por favor leer el aviso mas abajo.)</h4>
${createToggleOption('musicPlayer.enabled', 'Activar reproductor', '🎵')}
<div style="margin: 15px 0;">
<label style="display: block; margin-bottom: 5px; font-size: 12px; color: #FFD700;">🔗 URL de YouTube:</label>
<input type="text" id="youtubeUrl" value="${config.musicPlayer.youtubeUrl}" placeholder="https://www.youtube.com/watch?v=..." style="
width: 100%;
padding: 8px;
border: 1px solid #FFD700;
border-radius: 4px;
background: #333;
color: white;
font-size: 12px;
">
</div>
<div style="margin: 15px 0;">
<label style="display: block; margin-bottom: 5px; font-size: 12px; color: #FFD700;">🔊 Volumen:</label>
<input type="range" id="musicVolume" min="0" max="100" value="${Math.round(config.musicPlayer.volume * 100)}" style="width: 100%;">
<div style="text-align: center; font-size: 11px; color: #ccc; margin-top: 5px;" id="volumeDisplay">${Math.round(config.musicPlayer.volume * 100)}%</div>
</div>
${createToggleOption('musicPlayer.autoplay', 'Reproducir automáticamente', '▶️')}
<div style="margin-top: 15px; display: grid; gap: 8px;">
<button id="startMusicPlayer" class="action-btn">🎵 Iniciar Reproductor</button>
<button id="stopMusicPlayer" class="action-btn">⏹️ Detener Reproductor</button>
</div>
</div>
<div class="option-group" style="margin-top: 20px;">
<h4 style="color: #FFD700; margin: 0 0 10px 0; font-size: 14px;">ℹ️ Instrucciones</h4>
<div style="font-size: 11px; color: #ccc; line-height: 1.4;">
1. Pega el enlace de YouTube<br>
2. Ajusta el volumen deseado<br>
3. Activa el reproductor y presiona "Iniciar"<br>
4. Usa los controles para pausar, adelantar, etc.<br>
<br>
ADVERTENCIA, La sección de musica esta completamente obsoleta. No funciona y tiene varios errores, La nueva
actualización que resuelva este problema "2.4.2" no llegará muy pronto. Mientras lo solucionamos puedes probar
otras opciones. Gracias por leer<br>
- Gi4nx
</div>
</div>
</div>
<!-- Pestaña Sprites -->
<div id="tab-sprites" class="tab-content" style="display: none;">
<div class="option-group">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🎨 Sprites Personalizados</h4>
${createToggleOption('customSprites.enabled', 'Activar sprites personalizados', '🎨')}
<div style="margin-top: 15px;">
<h5 style="color: #FFD700; margin: 0 0 10px 0; font-size: 13px;">⚪ Piezas Blancas</h5>
${createSpriteUploader('white-king', '♔ Rey')}
${createSpriteUploader('white-queen', '♕ Reina')}
${createSpriteUploader('white-rook', '♖ Torre')}
${createSpriteUploader('white-bishop', '♗ Alfil')}
${createSpriteUploader('white-knight', '♘ Caballo')}
${createSpriteUploader('white-pawn', '♙ Peón')}
</div>
<div style="margin-top: 15px;">
<h5 style="color: #FFD700; margin: 0 0 10px 0; font-size: 13px;">⚫ Piezas Negras</h5>
${createSpriteUploader('black-king', '♚ Rey')}
${createSpriteUploader('black-queen', '♛ Reina')}
${createSpriteUploader('black-rook', '♜ Torre')}
${createSpriteUploader('black-bishop', '♝ Alfil')}
${createSpriteUploader('black-knight', '♞ Caballo')}
${createSpriteUploader('black-pawn', '♟ Peón')}
</div>
<div style="margin-top: 15px; display: grid; gap: 8px;">
<button id="applySprites" class="action-btn">✨ Aplicar Sprites</button>
<button id="resetAllSprites" class="action-btn">🔄 Restablecer Todos</button>
</div>
</div>
<div class="option-group" style="margin-top: 20px;">
<h4 style="color: #FFD700; margin: 0 0 10px 0; font-size: 14px;">ℹ️ Instrucciones</h4>
<div style="font-size: 11px; color: #ccc; line-height: 1.4;">
• Sube archivos PNG, JPG o GIF<br>
• Recomendado: imágenes cuadradas<br>
• Los cambios se aplican al instante<br>
• Ejemplo: Rey = Patata frita 🍟
</div>
</div>
</div>
<!-- Pestaña Tema -->
<div id="tab-theme" class="tab-content" style="display: none;">
<div class="option-group">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🎨 Personalización</h4>
${createToggleOption('buttonColors.enabled', 'Botones rojos', '🎯')}
</div>
<div class="option-group" style="margin-top: 20px;">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🌈 Colores de Botones</h4>
${createColorPicker('buttonColors.buttonPrimary', '🔴 Primario')}
${createColorPicker('buttonColors.buttonSecondary', '🟥 Secundario')}
${createColorPicker('buttonColors.hoverPrimary', '🔺 Hover 1')}
${createColorPicker('buttonColors.hoverSecondary', '🔻 Hover 2')}
</div>
<div style="margin-top: 15px;">
<button id="applyTheme" class="action-btn">✨ Aplicar Tema</button>
<button id="exportConfig" class="action-btn" style="margin-top: 8px;">💾 Exportar Config</button>
</div>
<div class="option-group" style="margin-top: 15px;">
<h4 style="color: #FFD700; margin: 0 0 15px 0; font-size: 14px;">🌐 Social</h4>
<div style="display: grid; gap: 8px;">
<button id="joinDiscord" class="social-btn discord-btn">💬 Discord</button>
<button id="visitGitHub" class="social-btn github-btn">🐱 GitHub</button>
</div>
</div>
</div>
`;
}
// Crear uploader de sprites
function createSpriteUploader(pieceType, pieceName) {
const currentSprite = config.customSprites.pieces[pieceType];
const previewId = `preview_${pieceType.replace('-', '_')}`;
return `
<div style="display: flex; justify-content: space-between; align-items: center; margin: 8px 0; padding: 8px; background: rgba(255,255,255,0.05); border-radius: 4px;">
<span style="font-size: 12px; flex: 1;">${pieceName}:</span>
<div style="display: flex; align-items: center; gap: 8px;">
<img id="${previewId}" src="${currentSprite}" style="width: 24px; height: 24px; display: ${currentSprite ? 'block' : 'none'}; border: 1px solid #FFD700; border-radius: 2px;">
<button class="sprite-upload-btn" data-piece="${pieceType}" style="background: #4CAF50; border: none; color: white; padding: 4px 8px; border-radius: 3px; font-size: 10px; cursor: pointer;">📁</button>
<button class="sprite-reset-btn" data-piece="${pieceType}" style="background: #f44336; border: none; color: white; padding: 4px 8px; border-radius: 3px; font-size: 10px; cursor: pointer;">🗑️</button>
</div>
</div>
`;
}
// Crear opción toggle
function createToggleOption(configPath, label, icon) {
const value = getNestedConfig(configPath);
const id = configPath.replace(/\./g, '_');
return `
<div style="margin: 12px 0; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 13px;">
<span style="margin-right: 8px;">${icon}</span>${label}
</span>
<label class="toggle-switch">
<input type="checkbox" id="${id}" ${value ? 'checked' : ''}>
<span class="toggle-slider"></span>
</label>
</div>
`;
}
// Crear selector de color
function createColorPicker(configPath, label) {
const value = getNestedConfig(configPath);
const id = configPath.replace(/\./g, '_');
return `
<div style="display: flex; justify-content: space-between; align-items: center; margin: 10px 0; font-size: 12px;">
<span style="flex: 1;">${label}:</span>
<input type="color" id="${id}" value="${value}" style="
width: 40px;
height: 30px;
border-radius: 6px;
border: 2px solid #FFD700;
cursor: pointer;
background: none;
margin-left: 10px;
">
</div>
`;
}
// Funciones de utilidad
function getNestedConfig(path) {
return path.split('.').reduce((obj, key) => obj && obj[key], config);
}
function setNestedConfig(path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => obj[key], config);
target[lastKey] = value;
}
// === FUNCIONES DE TABLERO (mantenidas del código original que funciona) ===
// Toggle movimiento de tablero - MANTENER COMO ESTÁ
function toggleBoardMoveable() {
console.log('🔧 Toggle board moveable iniciado, boardMoved:', boardMoved);
const boardSelectors = [
'.cg-wrap',
'.board-wrap',
'main.game .cg-wrap',
'.game .cg-wrap',
'.analyse .cg-wrap',
'.lpv .cg-wrap'
];
let board = null;
for (const selector of boardSelectors) {
board = document.querySelector(selector);
if (board) {
console.log('✅ Tablero encontrado con selector:', selector);
break;
}
}
if (!board) {
showTopNotification('❌ No se encontró el tablero', 'error');
console.error('❌ No se encontró ningún tablero con los selectores disponibles');
return;
}
if (!boardMoved) {
console.log('🔧 Activando modo arrastrable...');
// Guardar posición original usando getBoundingClientRect() para mayor precisión
const rect = board.getBoundingClientRect();
const computedStyle = window.getComputedStyle(board);
originalBoardPosition = {
// Estilos directos del elemento
position: board.style.position,
left: board.style.left,
top: board.style.top,
right: board.style.right,
bottom: board.style.bottom,
transform: board.style.transform,
zIndex: board.style.zIndex,
cursor: board.style.cursor,
// Posición computada como respaldo
computedPosition: computedStyle.position,
computedLeft: computedStyle.left,
computedTop: computedStyle.top,
computedRight: computedStyle.right,
computedBottom: computedStyle.bottom,
// Posición absoluta en viewport
rect: {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height
}
};
console.log('💾 Posición original guardada:', originalBoardPosition);
// Aplicar estilos para hacer el tablero movible
const currentPosition = computedStyle.position;
if (currentPosition === 'static') {
board.style.position = 'relative';
} else {
// Si ya tiene position, convertir a absolute para mayor control
board.style.position = 'absolute';
board.style.left = rect.left + 'px';
board.style.top = rect.top + 'px';
}
board.style.cursor = 'grab';
board.style.zIndex = '1000';
console.log('✅ Estilos aplicados - tablero debe mantenerse visible');
makeBoardDraggable(board);
boardMoved = true;
config.boardMoveable = true;
GM_setValue('redKingConfig241', config);
showTopNotification('📐 Tablero ahora es movible', 'success');
updateBoardStatus();
console.log('✅ Modo arrastrable activado exitosamente');
} else {
console.log('🔧 Desactivando modo arrastrable...');
// Desactivar modo arrastrable
removeBoardDragListeners();
// Restaurar posición original con mayor precisión
if (originalBoardPosition) {
console.log('🔄 Restaurando posición original...');
// Restaurar estilos directos
board.style.position = originalBoardPosition.position;
board.style.left = originalBoardPosition.left;
board.style.top = originalBoardPosition.top;
board.style.right = originalBoardPosition.right;
board.style.bottom = originalBoardPosition.bottom;
board.style.transform = originalBoardPosition.transform;
board.style.zIndex = originalBoardPosition.zIndex;
board.style.cursor = originalBoardPosition.cursor;
// Si los estilos directos están vacíos, limpiar completamente
if (!originalBoardPosition.position) {
board.style.removeProperty('position');
}
if (!originalBoardPosition.left) {
board.style.removeProperty('left');
}
if (!originalBoardPosition.top) {
board.style.removeProperty('top');
}
if (!originalBoardPosition.transform) {
board.style.removeProperty('transform');
}
if (!originalBoardPosition.zIndex) {
board.style.removeProperty('z-index');
}
if (!originalBoardPosition.cursor) {
board.style.removeProperty('cursor');
}
} else {
console.log('⚠️ No hay posición original guardada, usando reset básico');
// Fallback: limpiar todos los estilos de posicionamiento
['position', 'left', 'top', 'right', 'bottom', 'transform', 'z-index', 'cursor'].forEach(prop => {
board.style.removeProperty(prop);
});
}
boardMoved = false;
config.boardMoveable = false;
GM_setValue('redKingConfig241', config);
showTopNotification('🎯 Tablero fijo (no movible)', 'info');
updateBoardStatus();
console.log('✅ Modo arrastrable desactivado exitosamente');
}
}
// Hacer tablero arrastrable - MANTENER COMO ESTÁ
function makeBoardDraggable(board) {
console.log('🔧 Configurando tablero arrastrable...');
let isDragging = false;
let startX, startY, initialLeft, initialTop;
const startDrag = (e) => {
if (e.button !== 0) return;
if (e.target.classList.contains('cg-board') ||
e.target.classList.contains('cg-wrap') ||
e.target.tagName === 'CG-CONTAINER') {
console.log('🎯 Iniciando arrastre del tablero...');
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const computedStyle = window.getComputedStyle(board);
initialLeft = parseInt(computedStyle.left) || 0;
initialTop = parseInt(computedStyle.top) || 0;
console.log('📍 Posición inicial para arrastre:', { initialLeft, initialTop });
board.style.cursor = 'grabbing';
board.style.userSelect = 'none';
e.preventDefault();
e.stopPropagation();
}
};
const drag = (e) => {
if (!isDragging) return;
e.preventDefault();
e.stopPropagation();
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
const newLeft = initialLeft + deltaX;
const newTop = initialTop + deltaY;
const margin = 50;
const maxLeft = window.innerWidth - board.offsetWidth - margin;
const maxTop = window.innerHeight - board.offsetHeight - margin;
const boundedLeft = Math.max(-margin, Math.min(newLeft, maxLeft));
const boundedTop = Math.max(-margin, Math.min(newTop, maxTop));
board.style.left = boundedLeft + 'px';
board.style.top = boundedTop + 'px';
if (board.style.position !== 'absolute') {
board.style.position = 'absolute';
}
};
const stopDrag = (e) => {
if (!isDragging) return;
console.log('🎯 Finalizando arrastre del tablero...');
isDragging = false;
board.style.cursor = 'grab';
board.style.userSelect = '';
};
board.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
boardDragListeners = [
{ element: board, event: 'mousedown', listener: startDrag },
{ element: document, event: 'mousemove', listener: drag },
{ element: document, event: 'mouseup', listener: stopDrag }
];
console.log('✅ Tablero arrastrable configurado exitosamente');
}
// Remover listeners del tablero
function removeBoardDragListeners() {
console.log('🧹 Removiendo listeners de arrastre...');
boardDragListeners.forEach(({ element, event, listener }) => {
try {
element.removeEventListener(event, listener);
} catch (error) {
console.warn('⚠️ Error removiendo listener:', error);
}
});
boardDragListeners = [];
console.log('✅ Listeners removidos');
}
// Solo resetear posición del tablero
function resetBoardPositionOnly() {
const boardSelectors = [
'.cg-wrap', '.board-wrap', 'main.game .cg-wrap',
'.game .cg-wrap', '.analyse .cg-wrap', '.lpv .cg-wrap'
];
let board = null;
for (const selector of boardSelectors) {
board = document.querySelector(selector);
if (board) break;
}
if (!board) {
showTopNotification('❌ No se encontró el tablero', 'error');
return;
}
if (originalBoardPosition) {
if (boardMoved) {
const rect = originalBoardPosition.rect;
board.style.left = rect.left + 'px';
board.style.top = rect.top + 'px';
} else {
board.style.left = originalBoardPosition.left;
board.style.top = originalBoardPosition.top;
board.style.transform = originalBoardPosition.transform;
}
} else {
const rect = board.getBoundingClientRect();
const centerX = (window.innerWidth - rect.width) / 2;
const centerY = (window.innerHeight - rect.height) / 2;
if (boardMoved) {
board.style.left = centerX + 'px';
board.style.top = centerY + 'px';
}
}
showTopNotification('🎯 Posición del tablero restaurada', 'success');
}
// Actualizar estado del tablero en la UI
function updateBoardStatus() {
const statusElement = document.getElementById('boardStatus');
if (statusElement) {
if (boardMoved) {
statusElement.innerHTML = '<span style="color: #4CAF50;">📐 Movible (No funcional)</span> - Arrastra desde áreas vacías del tablero';
statusElement.style.fontSize = '10px';
} else {
statusElement.innerHTML = '<span style="color: #ccc;">🔒 Normal (no movible, No funcional)</span>';
statusElement.style.fontSize = '11px';
}
}
}
// Configurar eventos del panel
function setupPanelEvents() {
console.log('Configurando eventos v2.4.1...');
// Navegación por pestañas
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const targetTab = e.target.dataset.tab;
switchTab(targetTab);
});
});
// Toggle switches
document.querySelectorAll('input[type="checkbox"]').forEach(toggle => {
toggle.addEventListener('change', (e) => {
const configPath = e.target.id.replace(/_/g, '.');
const value = e.target.checked;
setNestedConfig(configPath, value);
GM_setValue('redKingConfig241', config);
handleConfigChange(configPath, value);
showAdvancedNotification(`${getOptionName(configPath)} ${value ? 'activado' : 'desactivado'}`, 'info', 1500);
});
});
// Color pickers
document.querySelectorAll('input[type="color"]').forEach(picker => {
picker.addEventListener('change', (e) => {
const configPath = e.target.id.replace(/_/g, '.');
const value = e.target.value;
setNestedConfig(configPath, value);
GM_setValue('redKingConfig241', config);
if (configPath.includes('buttonColors')) {
updateButtonStyles();
} else if (configPath.includes('customColors')) {
applyTextColors();
}
showAdvancedNotification('Color actualizado ✨', 'success', 1000);
});
});
setupActionButtons();
setupMusicPlayerEvents();
setupSpritesEvents();
setupPanelControls();
}
// Configurar eventos del reproductor de música
function setupMusicPlayerEvents() {
const youtubeUrlInput = document.getElementById('youtubeUrl');
if (youtubeUrlInput) {
youtubeUrlInput.addEventListener('change', (e) => {
config.musicPlayer.youtubeUrl = e.target.value;
GM_setValue('redKingConfig241', config);
showTopNotification('🔗 URL de YouTube guardada', 'saved');
});
}
const musicVolumeSlider = document.getElementById('musicVolume');
const volumeDisplay = document.getElementById('volumeDisplay');
if (musicVolumeSlider && volumeDisplay) {
musicVolumeSlider.addEventListener('input', (e) => {
const volume = parseInt(e.target.value) / 100;
config.musicPlayer.volume = volume;
volumeDisplay.textContent = e.target.value + '%';
GM_setValue('redKingConfig241', config);
if (musicPlayerInstance) {
musicPlayerInstance.setVolume(volume);
}
});
}
const startMusicBtn = document.getElementById('startMusicPlayer');
if (startMusicBtn) {
startMusicBtn.addEventListener('click', startMusicPlayer);
}
const stopMusicBtn = document.getElementById('stopMusicPlayer');
if (stopMusicBtn) {
stopMusicBtn.addEventListener('click', stopMusicPlayer);
}
}
// Configurar eventos de sprites
function setupSpritesEvents() {
// Botones de upload
document.querySelectorAll('.sprite-upload-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const pieceType = e.target.dataset.piece;
spritesManager.uploadSprite(pieceType);
});
});
// Botones de reset individual
document.querySelectorAll('.sprite-reset-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const pieceType = e.target.dataset.piece;
spritesManager.resetSprite(pieceType);
});
});
// Aplicar sprites
const applySpritesBtn = document.getElementById('applySprites');
if (applySpritesBtn) {
applySpritesBtn.addEventListener('click', () => {
spritesManager.applyCustomSprites();
showTopNotification('✨ Sprites aplicados manualmente', 'success');
});
}
// Resetear todos los sprites
const resetAllSpritesBtn = document.getElementById('resetAllSprites');
if (resetAllSpritesBtn) {
resetAllSpritesBtn.addEventListener('click', () => {
if (confirm('¿Restablecer todos los sprites personalizados?')) {
Object.keys(config.customSprites.pieces).forEach(piece => {
config.customSprites.pieces[piece] = '';
});
GM_setValue('redKingConfig241', config);
document.querySelectorAll('[id^="preview_"]').forEach(preview => {
preview.style.display = 'none';
});
spritesManager.removeCustomSprites();
showTopNotification('🔄 Todos los sprites restablecidos', 'info');
}
});
}
}
// Funciones del reproductor de música CORREGIDAS
async function startMusicPlayer() {
const youtubeUrl = config.musicPlayer.youtubeUrl.trim();
if (!youtubeUrl) {
showTopNotification('❌ Ingresa una URL de YouTube válida', 'error');
return;
}
if (!config.musicPlayer.enabled) {
showTopNotification('❌ Activa el reproductor de música primero', 'warning');
return;
}
try {
showTopNotification('🎵 Iniciando reproductor...', 'info');
if (musicPlayerInstance) {
musicPlayerInstance.destroy();
musicPlayerInstance = null;
}
musicPlayerInstance = new YouTubeMusicPlayer();
const success = await musicPlayerInstance.initPlayer(youtubeUrl);
if (!success) {
musicPlayerInstance = null;
}
} catch (error) {
console.error('Error iniciando reproductor:', error);
showTopNotification('❌ Error al iniciar reproductor', 'error');
musicPlayerInstance = null;
}
}
function stopMusicPlayer() {
if (musicPlayerInstance) {
musicPlayerInstance.destroy();
musicPlayerInstance = null;
} else {
showTopNotification('ℹ️ No hay reproductor activo', 'info');
}
}
// Configurar botones de acción
function setupActionButtons() {
// Botón girar tablero CORREGIDO
const flipBoard = document.getElementById('flipBoard');
if (flipBoard) {
flipBoard.addEventListener('click', flipBoardAction);
}
// Botón análisis rápido
const quickAnalysis = document.getElementById('quickAnalysis');
if (quickAnalysis) {
quickAnalysis.addEventListener('click', openAnalysis);
}
// Botón exportar PGN
const exportPGN = document.getElementById('exportPGN');
if (exportPGN) {
exportPGN.addEventListener('click', exportPGNAction);
}
// Botón mover tablero
const toggleBoardMove = document.getElementById('toggleBoardMove');
if (toggleBoardMove) {
toggleBoardMove.addEventListener('click', toggleBoardMoveable);
}
// Reset posición del tablero
const resetBoardPosition = document.getElementById('resetBoardPosition');
if (resetBoardPosition) {
resetBoardPosition.addEventListener('click', resetBoardPositionOnly);
}
// Controles de chat
const toggleChat = document.getElementById('toggleChat');
if (toggleChat) {
toggleChat.addEventListener('click', toggleChatVisibility);
}
const hideChat = document.getElementById('hideChat');
if (hideChat) {
hideChat.addEventListener('click', () => hideChatFunction());
}
const showChat = document.getElementById('showChat');
if (showChat) {
showChat.addEventListener('click', () => showChatFunction());
}
// Aplicar tema tablero
const applyBoardTheme = document.getElementById('applyBoardTheme');
if (applyBoardTheme) {
applyBoardTheme.addEventListener('click', () => {
applyTextColors();
showTopNotification('✨ Cambios de tablero aplicados', 'success');
});
}
// Aplicar tema general
const applyTheme = document.getElementById('applyTheme');
if (applyTheme) {
applyTheme.addEventListener('click', () => {
applyCustomTheme();
showTopNotification('✨ Tema aplicado', 'success');
});
}
// Exportar configuración
const exportConfig = document.getElementById('exportConfig');
if (exportConfig) {
exportConfig.addEventListener('click', exportConfiguration);
}
// Botones sociales
const joinDiscord = document.getElementById('joinDiscord');
if (joinDiscord) {
joinDiscord.addEventListener('click', () => {
GM_openInTab(config.userLinks.discord, false);
showTopNotification('💬 Abriendo Discord...', 'info');
});
}
const visitGitHub = document.getElementById('visitGitHub');
if (visitGitHub) {
visitGitHub.addEventListener('click', () => {
GM_openInTab(config.userLinks.github, false);
showTopNotification('🐱 Abriendo GitHub...', 'info');
});
}
}
// Configurar controles del panel
function setupPanelControls() {
const minimizeBtn = document.getElementById('minimizeBtn');
if (minimizeBtn) {
minimizeBtn.addEventListener('click', togglePanelSize);
}
const closePanel = document.getElementById('closePanel');
if (closePanel) {
closePanel.addEventListener('click', () => {
const panel = document.getElementById('redKingPanel');
if (panel) {
panel.style.transform = 'scale(0)';
setTimeout(() => panel.remove(), 300);
}
});
}
const resetConfig = document.getElementById('resetConfig');
if (resetConfig) {
resetConfig.addEventListener('click', resetConfiguration);
}
const restoreNormal = document.getElementById('restoreNormal');
if (restoreNormal) {
restoreNormal.addEventListener('click', restoreToNormal);
}
}
// Cambiar pestañas
function switchTab(tabName) {
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
btn.style.cssText = `
flex: 1;
padding: 12px 6px;
text-align: center;
cursor: pointer;
font-size: 10px;
border-right: 1px solid #444;
transition: all 0.3s;
background: #3a3a3a;
color: #ccc;
`;
});
const activeBtn = document.querySelector(`.tab-btn[data-tab="${tabName}"]`);
if (activeBtn) {
activeBtn.classList.add('active');
activeBtn.style.cssText = `
flex: 1;
padding: 12px 6px;
text-align: center;
cursor: pointer;
font-size: 10px;
border-right: 1px solid #444;
transition: all 0.3s;
background: #8B0000;
color: #FFD700;
`;
}
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = 'none';
});
const activeContent = document.getElementById(`tab-${tabName}`);
if (activeContent) {
activeContent.style.display = 'block';
}
currentTab = tabName;
if (tabName === 'board') {
updateBoardStatus();
}
}
// Manejar cambios de configuración
function handleConfigChange(configPath, value) {
switch (configPath) {
case 'buttonColors.enabled':
updateButtonStyles();
break;
case 'boardEffects':
toggleBoardEffects(value);
break;
case 'highlightMoves':
toggleHighlightMoves(value);
break;
case 'soundEnabled':
toggleSounds(value);
break;
case 'customSprites.enabled':
if (value) {
spritesManager.initCustomSprites();
} else {
spritesManager.removeCustomSprites();
}
break;
case 'musicPlayer.enabled':
if (!value && musicPlayerInstance) {
stopMusicPlayer();
}
break;
}
}
// Obtener nombre legible de la opción
function getOptionName(configPath) {
const names = {
'soundEnabled': 'Sonidos',
'highlightMoves': 'Resaltado',
'boardEffects': 'Efectos',
'buttonColors.enabled': 'Botones rojos',
'musicPlayer.enabled': 'Reproductor de música',
'musicPlayer.autoplay': 'Reproducción automática',
'customSprites.enabled': 'Sprites personalizados'
};
return names[configPath] || configPath;
}
// === FUNCIONES DE ACCIÓN ===
// Análisis rápido
function openAnalysis() {
try {
const gameId = extractGameId();
const currentUrl = window.location.href;
if (gameId && gameId.length >= 8) {
const analysisUrl = `https://lichess.org/analysis/${gameId}`;
GM_openInTab(analysisUrl, false);
showTopNotification('📊 Abriendo análisis de partida', 'success');
} else if (currentUrl.includes('/game/')) {
const gameMatch = currentUrl.match(/\/game\/([a-zA-Z0-9]{8,})/);
if (gameMatch) {
const analysisUrl = `https://lichess.org/analysis/${gameMatch[1]}`;
GM_openInTab(analysisUrl, false);
showTopNotification('📊 Abriendo análisis', 'success');
} else {
GM_openInTab('https://lichess.org/analysis', false);
showTopNotification('📊 Abriendo tablero de análisis', 'info');
}
} else {
GM_openInTab('https://lichess.org/analysis', false);
showTopNotification('📊 Abriendo tablero de análisis', 'info');
}
} catch (error) {
console.error('Error al abrir análisis:', error);
showTopNotification('❌ Error al abrir análisis', 'error');
}
}
// Exportar PGN
function exportPGNAction() {
try {
const gameId = extractGameId();
if (gameId && gameId.length >= 8) {
const pgnUrl = `https://lichess.org/game/export/${gameId}.pgn`;
fetch(pgnUrl)
.then(response => response.text())
.then(pgnData => {
const blob = new Blob([pgnData], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `lichess_${gameId}.pgn`;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showTopNotification('📄 PGN descargado', 'success');
})
.catch(() => {
const link = document.createElement('a');
link.href = pgnUrl;
link.download = `lichess_${gameId}.pgn`;
link.click();
showTopNotification('📄 Descargando PGN...', 'success');
});
} else {
showTopNotification('❌ No se encontró ID de partida válido', 'error');
}
} catch (error) {
console.error('Error al exportar PGN:', error);
showTopNotification('❌ Error al exportar PGN', 'error');
}
}
// === FUNCIONES DE CHAT ===
function toggleChatVisibility() {
const chatSelectors = ['.mchat', '.chat', '.game__chat', '#chat', '.lpv__chat', '.chat-wrap'];
let chatFound = false;
for (const selector of chatSelectors) {
const chatElement = document.querySelector(selector);
if (chatElement) {
if (chatElement.style.display === 'none') {
chatElement.style.display = '';
config.chatVisible = true;
showTopNotification('👁️🗨️ Chat mostrado', 'success');
} else {
chatElement.style.display = 'none';
config.chatVisible = false;
showTopNotification('👁️ Chat oculto', 'info');
}
GM_setValue('redKingConfig241', config);
chatFound = true;
break;
}
}
if (!chatFound) {
showTopNotification('❌ No se encontró el chat', 'error');
}
}
function hideChatFunction() {
const chatSelectors = ['.mchat', '.chat', '.game__chat', '#chat', '.lpv__chat', '.chat-wrap'];
let hidden = false;
for (const selector of chatSelectors) {
const chatElement = document.querySelector(selector);
if (chatElement) {
chatElement.style.display = 'none';
config.chatVisible = false;
GM_setValue('redKingConfig241', config);
hidden = true;
}
}
if (hidden) {
showTopNotification('👁️ Chat oculto', 'info');
} else {
showTopNotification('❌ No se encontró el chat', 'error');
}
}
function showChatFunction() {
const chatSelectors = ['.mchat', '.chat', '.game__chat', '#chat', '.lpv__chat', '.chat-wrap'];
let shown = false;
for (const selector of chatSelectors) {
const chatElement = document.querySelector(selector);
if (chatElement) {
chatElement.style.display = '';
config.chatVisible = true;
GM_setValue('redKingConfig241', config);
shown = true;
}
}
if (shown) {
showTopNotification('👁️🗨️ Chat mostrado', 'success');
} else {
showTopNotification('❌ No se encontró el chat', 'error');
}
}
// Extraer ID de partida
function extractGameId() {
const url = window.location.href;
const pathname = window.location.pathname;
const patterns = [
/\/([a-zA-Z0-9]{8})(?:\/|$|\?|#)/,
/\/([a-zA-Z0-9]{12})(?:\/|$|\?|#)/,
/\/game\/export\/([a-zA-Z0-9]{8,})/,
/\/analysis\/([a-zA-Z0-9]{8,})/
];
for (const pattern of patterns) {
const match = pathname.match(pattern) || url.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return null;
}
// === FUNCIONES DE TEMA ===
function toggleBoardEffects(enabled) {
const effectsStyle = document.getElementById('redKingBoardEffectsStyle') || document.createElement('style');
effectsStyle.id = 'redKingBoardEffectsStyle';
if (enabled) {
effectsStyle.textContent = `
.cg-board square.last-move {
background-color: rgba(220, 20, 60, 0.3) !important;
box-shadow: inset 0 0 10px rgba(220, 20, 60, 0.5) !important;
}
.cg-board square.selected {
background-color: rgba(255, 215, 0, 0.5) !important;
box-shadow: inset 0 0 15px rgba(255, 215, 0, 0.7) !important;
}
.cg-board square.move-dest {
background: radial-gradient(circle, rgba(255,215,0,0.3) 0%, transparent 60%) !important;
}
.cg-board square.check {
background-color: rgba(220, 20, 60, 0.6) !important;
animation: redKingCheckPulse 1s ease-in-out infinite alternate !important;
}
@keyframes redKingCheckPulse {
0% { background-color: rgba(220, 20, 60, 0.6) !important; }
100% { background-color: rgba(220, 20, 60, 0.8) !important; }
}
`;
} else {
effectsStyle.textContent = '';
}
if (!effectsStyle.parentNode) {
document.head.appendChild(effectsStyle);
}
}
function toggleHighlightMoves(enabled) {
const highlightStyle = document.getElementById('redKingHighlightStyle') || document.createElement('style');
highlightStyle.id = 'redKingHighlightStyle';
if (enabled) {
highlightStyle.textContent = `
.cg-board square.move-dest, .cg-board square.premove-dest {
background: radial-gradient(circle, rgba(255,215,0,0.4) 20%, transparent 50%) !important;
border: 2px solid rgba(255,215,0,0.6) !important;
}
.cg-board square.oc.move-dest {
background: radial-gradient(circle, rgba(220,20,60,0.4) 20%, transparent 50%) !important;
border: 2px solid rgba(220,20,60,0.6) !important;
}
`;
} else {
highlightStyle.textContent = '';
}
if (!highlightStyle.parentNode) {
document.head.appendChild(highlightStyle);
}
}
function toggleSounds(enabled) {
const soundStyle = document.getElementById('redKingSoundStyle') || document.createElement('style');
soundStyle.id = 'redKingSoundStyle';
if (enabled) {
soundStyle.textContent = `
.sound-enabled {
transition: all 0.2s ease !important;
}
`;
} else {
soundStyle.textContent = '';
}
if (!soundStyle.parentNode) {
document.head.appendChild(soundStyle);
}
}
function applyTextColors() {
const textColorStyle = document.getElementById('redKingTextColorStyle') || document.createElement('style');
textColorStyle.id = 'redKingTextColorStyle';
textColorStyle.textContent = `
.mchat, .mchat .messages, .chat, .game__chat {
color: ${config.customColors.chatText} !important;
}
.mchat .message, .chat .message {
color: ${config.customColors.chatText} !important;
}
`;
if (!textColorStyle.parentNode) {
document.head.appendChild(textColorStyle);
}
}
function updateButtonStyles() {
if (!config.buttonColors.enabled) {
const existingStyle = document.getElementById('redKingButtonStyles');
if (existingStyle) existingStyle.remove();
return;
}
const buttonStyle = document.getElementById('redKingButtonStyles') || document.createElement('style');
buttonStyle.id = 'redKingButtonStyles';
buttonStyle.textContent = `
.button, .form3-submit, .lobby__box .button, .lobby__start .button,
.game__menu .button, .analyse__tools .button, .board-wrap .button,
button.button, input[type="submit"], .game .button {
background: linear-gradient(135deg, ${config.buttonColors.buttonPrimary} 0%, ${config.buttonColors.buttonSecondary} 100%) !important;
border: none !important;
color: ${config.buttonColors.textColor} !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
box-shadow: 0 4px 15px rgba(0,0,0,0.3) !important;
}
.button:hover, .form3-submit:hover, .lobby__box .button:hover, .lobby__start .button:hover,
.game__menu .button:hover, .analyse__tools .button:hover, .board-wrap .button:hover,
button.button:hover, input[type="submit"]:hover, .game .button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(0,0,0,0.4) !important;
background: linear-gradient(135deg, ${config.buttonColors.hoverPrimary} 0%, ${config.buttonColors.hoverSecondary} 100%) !important;
}
`;
if (!buttonStyle.parentNode) {
document.head.appendChild(buttonStyle);
}
}
function applyCustomTheme() {
toggleBoardEffects(config.boardEffects);
toggleHighlightMoves(config.highlightMoves);
toggleSounds(config.soundEnabled);
applyTextColors();
updateButtonStyles();
if (config.customSprites.enabled) {
spritesManager.initCustomSprites();
}
}
// === RESTAURAR NORMALIDAD ===
function restoreToNormal() {
if (!confirm('¿Restaurar Lichess a su estado completamente normal?\n\nEsto desactivará TODOS los cambios de Red King incluyendo música y sprites.')) {
return;
}
try {
// 1. Detener reproductor de música
if (musicPlayerInstance) {
musicPlayerInstance.destroy();
musicPlayerInstance = null;
}
// 2. Desactivar sprites personalizados
spritesManager.removeCustomSprites();
// 3. Desactivar movimiento de tablero
if (boardMoved) {
const boardSelectors = [
'.cg-wrap', '.board-wrap', 'main.game .cg-wrap',
'.game .cg-wrap', '.analyse .cg-wrap', '.lpv .cg-wrap'
];
let board = null;
for (const selector of boardSelectors) {
board = document.querySelector(selector);
if (board) break;
}
if (board && originalBoardPosition) {
removeBoardDragListeners();
['position', 'left', 'top', 'right', 'bottom', 'transform', 'z-index', 'cursor'].forEach(prop => {
board.style.removeProperty(prop);
});
}
boardMoved = false;
}
// 4. Remover todos los estilos personalizados
const stylesToRemove = [
'redKingBoardEffectsStyle',
'redKingHighlightStyle',
'redKingSoundStyle',
'redKingTextColorStyle',
'redKingButtonStyles',
'redKingCustomSprites'
];
stylesToRemove.forEach(styleId => {
const style = document.getElementById(styleId);
if (style) style.remove();
});
// 5. Mostrar todo el chat
const chatSelectors = ['.mchat', '.chat', '.game__chat', '#chat', '.lpv__chat', '.chat-wrap'];
chatSelectors.forEach(selector => {
const chatElement = document.querySelector(selector);
if (chatElement) {
chatElement.style.display = '';
}
});
// 6. Resetear configuración completa
config = JSON.parse(JSON.stringify(defaultConfig));
GM_setValue('redKingConfig241', config);
// 7. Actualizar UI del panel
document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
const configPath = checkbox.id.replace(/_/g, '.');
checkbox.checked = getNestedConfig(configPath);
});
document.querySelectorAll('input[type="color"]').forEach(colorPicker => {
const configPath = colorPicker.id.replace(/_/g, '.');
colorPicker.value = getNestedConfig(configPath);
});
const youtubeUrlInput = document.getElementById('youtubeUrl');
if (youtubeUrlInput) youtubeUrlInput.value = '';
const musicVolumeSlider = document.getElementById('musicVolume');
if (musicVolumeSlider) musicVolumeSlider.value = 50;
const volumeDisplay = document.getElementById('volumeDisplay');
if (volumeDisplay) volumeDisplay.textContent = '50%';
updateBoardStatus();
showTopNotification('🔄 ¡Lichess restaurado a normalidad completa!', 'success', 4000);
} catch (error) {
console.error('Error al restaurar normalidad:', error);
showTopNotification('❌ Error al restaurar normalidad', 'error');
}
}
// === FUNCIONES UTILITARIAS ===
function togglePanelSize() {
const panel = document.getElementById('redKingPanel');
const content = document.getElementById('panelContent');
const btn = document.getElementById('minimizeBtn');
if (panelVisible) {
content.style.display = 'none';
panel.style.height = 'auto';
btn.textContent = '+';
panelVisible = false;
} else {
content.style.display = 'block';
btn.textContent = '−';
panelVisible = true;
}
}
function makePanelDraggable(panel) {
let isDragging = false;
let startX, startY, startLeft, startTop;
const header = document.getElementById('panelHeader');
if (!header) return;
header.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
function startDrag(e) {
if (e.target.id === 'minimizeBtn') return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = panel.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
header.style.cursor = 'grabbing';
panel.style.zIndex = '9999999';
panel.style.transition = 'none';
}
function drag(e) {
if (!isDragging) return;
e.preventDefault();
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
let newLeft = startLeft + deltaX;
let newTop = startTop + deltaY;
const margin = 10;
newLeft = Math.max(margin, Math.min(newLeft, window.innerWidth - panel.offsetWidth - margin));
newTop = Math.max(margin, Math.min(newTop, window.innerHeight - panel.offsetHeight - margin));
panel.style.left = newLeft + 'px';
panel.style.top = newTop + 'px';
panel.style.right = 'auto';
}
function stopDrag() {
if (!isDragging) return;
isDragging = false;
header.style.cursor = 'move';
panel.style.zIndex = '999999';
panel.style.transition = 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)';
}
}
function exportConfiguration() {
try {
const configData = JSON.stringify(config, null, 2);
const blob = new Blob([configData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `red_king_v241_config_${new Date().toISOString().slice(0, 10)}.json`;
link.click();
URL.revokeObjectURL(url);
showTopNotification('💾 Configuración exportada', 'success');
} catch (error) {
console.error('Error al exportar configuración:', error);
showTopNotification('❌ Error al exportar', 'error');
}
}
function resetConfiguration() {
if (confirm('¿Restaurar configuración por defecto?\n\nEsto eliminará todas las personalizaciones incluyendo sprites y música.')) {
if (musicPlayerInstance) {
musicPlayerInstance.destroy();
musicPlayerInstance = null;
}
spritesManager.removeCustomSprites();
config = JSON.parse(JSON.stringify(defaultConfig));
GM_setValue('redKingConfig241', config);
showTopNotification('🔄 Configuración restaurada', 'success');
setTimeout(() => location.reload(), 1500);
}
}
function showAdvancedNotification(message, type = 'info', duration = 3000) {
const notification = document.createElement('div');
const typeStyles = {
success: { bg: 'linear-gradient(135deg, #4CAF50 0%, #45a049 100%)', icon: '✅' },
error: { bg: 'linear-gradient(135deg, #f44336 0%, #d32f2f 100%)', icon: '❌' },
warning: { bg: 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)', icon: '⚠️' },
info: { bg: 'linear-gradient(135deg, #8B0000 0%, #DC143C 100%)', icon: 'ℹ️' }
};
const style = typeStyles[type];
notification.style.cssText = `
position: fixed !important;
top: 20px !important;
left: 50% !important;
transform: translateX(-50%) translateY(-100px) !important;
background: ${style.bg} !important;
color: white !important;
padding: 12px 20px !important;
border-radius: 8px !important;
z-index: 10000000 !important;
font-size: 13px !important;
font-weight: 500 !important;
box-shadow: 0 10px 30px rgba(0,0,0,0.5) !important;
backdrop-filter: blur(10px) !important;
max-width: 350px !important;
text-align: center !important;
animation: slideIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;
`;
notification.innerHTML = `
<span style="margin-right: 8px; font-size: 14px;">${style.icon}</span>
${message}
`;
if (!document.getElementById('redKingNotificationAnimations')) {
const animations = document.createElement('style');
animations.id = 'redKingNotificationAnimations';
animations.textContent = `
@keyframes slideIn {
to { transform: translateX(-50%) translateY(0); }
}
@keyframes slideOut {
to { transform: translateX(-50%) translateY(-100px); opacity: 0; }
}
`;
document.head.appendChild(animations);
}
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards';
setTimeout(() => notification.remove(), 500);
}, duration);
}
function setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
return;
}
const key = e.key.toLowerCase();
if (e.ctrlKey || e.altKey || e.metaKey) {
return;
}
switch (key) {
case 'f':
e.preventDefault();
flipBoardAction();
break;
case 'a':
e.preventDefault();
openAnalysis();
break;
case 'p':
e.preventDefault();
togglePanel();
break;
case 'c':
e.preventDefault();
toggleChatVisibility();
break;
}
});
}
function togglePanel() {
const panel = document.getElementById('redKingPanel');
if (panel) {
if (panel.style.display === 'none') {
panel.style.display = 'block';
showTopNotification('♔ Panel mostrado', 'info');
} else {
panel.style.display = 'none';
showTopNotification('♔ Panel oculto', 'info');
}
} else {
createPanel();
}
}
function addCustomStyles() {
GM_addStyle(`
.tab-btn {
flex: 1;
padding: 12px 6px;
text-align: center;
cursor: pointer;
font-size: 10px;
border-right: 1px solid #444;
transition: all 0.3s;
background: #3a3a3a;
color: #ccc;
}
.tab-btn:last-child {
border-right: none;
}
.action-btn, .music-btn {
background: linear-gradient(135deg, #8B0000 0%, #DC143C 100%) !important;
border: none !important;
color: white !important;
padding: 10px !important;
border-radius: 6px !important;
cursor: pointer !important;
font-size: 12px !important;
font-weight: 500 !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
width: 100% !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
}
.music-btn {
width: auto !important;
padding: 8px 12px !important;
font-size: 14px !important;
margin: 2px !important;
}
.action-btn:hover, .music-btn:hover {
transform: translateY(-1px) !important;
box-shadow: 0 4px 12px rgba(0,0,0,0.4) !important;
background: linear-gradient(135deg, #DC143C 0%, #8B0000 100%) !important;
}
.social-btn {
background: linear-gradient(135deg, #4a4a4a 0%, #6a6a6a 100%) !important;
border: none !important;
color: white !important;
padding: 12px 20px !important;
border-radius: 6px !important;
cursor: pointer !important;
font-size: 13px !important;
font-weight: 500 !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
width: 100% !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
margin: 4px 0 !important;
}
.discord-btn:hover {
background: linear-gradient(135deg, #5865F2 0%, #4752C4 100%) !important;
transform: translateY(-1px) !important;
}
.github-btn:hover {
background: linear-gradient(135deg, #333 0%, #24292e 100%) !important;
transform: translateY(-1px) !important;
}
.toggle-switch {
position: relative !important;
display: inline-block !important;
width: 44px !important;
height: 22px !important;
}
.toggle-switch input {
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
}
.toggle-slider {
position: absolute !important;
cursor: pointer !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
background-color: #333 !important;
transition: 0.3s !important;
border-radius: 22px !important;
}
.toggle-slider:before {
position: absolute !important;
content: "" !important;
height: 16px !important;
width: 16px !important;
left: 3px !important;
bottom: 3px !important;
background-color: white !important;
transition: 0.3s !important;
border-radius: 50% !important;
}
input:checked + .toggle-slider {
background: linear-gradient(135deg, #8B0000 0%, #DC143C 100%) !important;
}
input:checked + .toggle-slider:before {
transform: translateX(22px) !important;
}
#redKingPanel *::-webkit-scrollbar {
width: 6px !important;
}
#redKingPanel *::-webkit-scrollbar-track {
background: #2a2a2a !important;
}
#redKingPanel *::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #8B0000 0%, #DC143C 100%) !important;
border-radius: 3px !important;
}
kbd {
background: #333 !important;
border: 1px solid #555 !important;
border-radius: 3px !important;
color: #FFD700 !important;
padding: 1px 4px !important;
font-size: 10px !important;
font-family: monospace !important;
margin: 0 1px !important;
}
`);
}
// Función de inicialización principal
function initialize() {
console.log('🔴♔ Inicializando Red King v2.4.1...');
addCustomStyles();
setTimeout(() => {
try {
const panel = createPanel();
if (panel) {
console.log('✅ Panel v2.4.1 creado exitosamente');
setTimeout(() => {
setupKeyboardShortcuts();
// Aplicar tema si hay opciones activadas
if (config.boardEffects || config.highlightMoves || config.buttonColors.enabled || config.customSprites.enabled) {
applyCustomTheme();
}
console.log('✅ Red King v2.4.1 completamente iniciado');
}, 500);
}
} catch (error) {
console.error('Error al inicializar Red King v2.4.1:', error);
showTopNotification('❌ Error al iniciar Red King', 'error');
}
}, 1000);
}
// Manejar cambios de página
let currentUrl = window.location.href;
function handlePageChange() {
if (window.location.href !== currentUrl) {
currentUrl = window.location.href;
console.log('Consolelog no disponible en tu país');
setTimeout(() => {
if (config.boardEffects || config.highlightMoves || config.buttonColors.enabled || config.customSprites.enabled) {
applyCustomTheme();
}
}, 1000);
}
}
// Observer para cambios en la página
const observer = new MutationObserver(handlePageChange);
observer.observe(document.body, { childList: true, subtree: true });
// Inicializar
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
// Backup de inicialización
setTimeout(() => {
if (!document.getElementById('redKingPanel')) {
console.log('Backup: Re-inicializando Red King v2.4.1');
initialize();
}
}, 5000);
console.log('Red King v2.4.1 Iniciado');
})();