Drawaria Avatar Physics Platformer🏃

This will allow you to move your avatar in the room like a videogame, you will need to see it other screen.

// ==UserScript==
// @name         Drawaria Avatar Physics Platformer🏃
// @namespace    http://tampermonkey.net/
// @version      6.0
// @description  This will allow you to move your avatar in the room like a videogame, you will need to see it other screen.
// @author       YouTubeDrawaria
// @include      https://drawaria.online*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Global variables for canvas and socket.
    let drawariaCanvas = null;
    let drawariaCtx = null;
    let drawariaSocket = null;
    let menuControllerInstance = null;

    // Hook into WebSocket to capture the game's socket at the earliest opportunity
    const originalWebSocketSend = WebSocket.prototype.send;
    WebSocket.prototype.send = function (...args) {
        if (!drawariaSocket && this.url && this.url.includes('drawaria')) {
            drawariaSocket = this;
            console.log('🔗 Drawaria WebSocket captured by early hook.');
        }
        return originalWebSocketSend.apply(this, args);
    };

    // === Utility Functions ===
    function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

    // === Drawing Functions (now checks for drawariaCanvas internally) ===
    function sendDrawCommand(x1, y1, x2, y2, color, thickness) {
        // Ensure socket and canvas are ready for drawing commands
        if (!drawariaSocket || drawariaSocket.readyState !== WebSocket.OPEN || !drawariaCanvas) {
            return;
        }
        const normX1 = (x1 / drawariaCanvas.width).toFixed(4);
        const normY1 = (y1 / drawariaCanvas.height).toFixed(4);
        const normX2 = (x2 / drawariaCanvas.width).toFixed(4);
        const normY2 = (y2 / drawariaCanvas.height).toFixed(4);
        const command = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${0 - thickness},"${color}",0,0,{}]]`;
        drawariaSocket.send(command);
    }

    async function drawLine(startX, startY, endX, endY, color = '#000000', thickness = 2) {
        sendDrawCommand(startX, startY, endX, endY, color, thickness);
        await sleep(10);
    }

    async function drawRectangle(x, y, width, height, color = '#000000', thickness = 2) {
        await drawLine(x, y, x + width, y, color, thickness);
        await drawLine(x + width, y, x + width, y + height, color, thickness);
        await drawLine(x + width, y + height, x, y + height, color, thickness);
        await drawLine(x, y + height, x, y, color, thickness);
    }

    async function drawCircle(centerX, centerY, radius, color = '#000000', thickness = 2, segments = 24) {
        const angleStep = (Math.PI * 2) / segments;
        for (let i = 0; i < segments; i++) {
            const angle1 = i * angleStep;
            const angle2 = (i + 1) * angleStep;
            const x1 = centerX + Math.cos(angle1) * radius;
            const y1 = centerY + Math.sin(angle1) * radius;
            const x2 = centerX + Math.cos(angle2) * radius;
            const y2 = centerY + Math.sin(angle2) * radius;
            await drawLine(x1, y1, x2, y2, color, thickness);
        }
    }

    // === Nuevas Definiciones Globales de Colores y Hitbox Types ===

    // Colores estándar en formato HEX y RGB
    const STANDARD_COLORS = {
        black: { hex: '#000000', r: 0, g: 0, b: 0 },
        white: { hex: '#ffffff', r: 255, g: 255, b: 255 },
        gray: { hex: '#7F7F7F', r: 127, g: 127, b: 127 },
        red: { hex: '#ff0000', r: 255, g: 0, b: 0 },
        green: { hex: '#00ff00', r: 0, g: 255, b: 0 },
        blue: { hex: '#0000ff', r: 0, g: 0, b: 255 },
        celeste: { hex: '#93cfff', r: 147, g: 207, b: 255 },
        yellow: { hex: '#ffff00', r: 255, g: 255, b: 0 },
        orange: { hex: '#ff9300', r: 255, g: 147, b: 0 },
        violet: { hex: '#7f007f', r: 127, g: 0, b: 127 },
        light_pink: { hex: '#ffbfff', r: 255, g: 191, b: 223 }, // Rosa claro
        brown: { hex: '#7f3f00', r: 127, g: 63, b: 0 }
    };

    // Definición de los tipos de hitbox y sus propiedades por defecto
    const DEFAULT_HITBOX_TYPES = {
        solid: { name: 'Tocable', colorKey: 'black', type: 'solid', description: 'Plataformas sólidas' },
        bounce: { name: 'Rebote', colorKey: 'yellow', type: 'bounce', description: 'Te hace rebotar' },
        speed_boost: { name: 'Más Velocidad', colorKey: 'red', type: 'speed_boost', description: 'Aumenta tu velocidad' },
        water: { name: 'Nadar', colorKey: 'blue', type: 'water', description: 'Permite nadar' },
        anti_gravity: { name: 'Gravedad Arriba', colorKey: 'green', type: 'anti_gravity', description: 'Invierte la gravedad' },
        normal_gravity: { name: 'Gravedad Normal', colorKey: 'light_pink', type: 'normal_gravity', description: 'Restablece la gravedad' },
        push_back: { name: 'Empuje Atrás', colorKey: 'violet', type: 'push_back', description: 'Te empuja en la dirección opuesta' }
    };

    // Función de ayuda para convertir HEX a RGB
    function hexToRgb(hex) {
        const bigint = parseInt(hex.slice(1), 16);
        const r = (bigint >> 16) & 255;
        const g = (bigint >> 8) & 255;
        const b = bigint & 255;
        return { r, g, b };
    }


    // ===============================
    // SISTEMA DE FÍSICA Y AVATAR
    // ===============================
    class Vector2D {
        constructor(x = 0, y = 0) { this.x = x; this.y = y; }
        add(vector) { return new Vector2D(this.x + vector.x, this.y + vector.y); }
        subtract(vector) { return new Vector2D(this.x - vector.x, this.y - vector.y); }
        multiply(scalar) { return new Vector2D(this.x * scalar, this.y * scalar); }
        magnitude() { return Math.sqrt(this.x * this.x + this.y * this.y); }
        normalize() { const mag = this.magnitude(); return mag > 0 ? new Vector2D(this.x / mag, this.y / mag) : new Vector2D(0, 0); }
        copy() { return new Vector2D(this.x, this.y); }
    }

    class PhysicsAvatar {
        constructor() {
            // position.y represents the BOTTOM of the avatar in 0-100 game coords (100 is floor)
            this.position = new Vector2D(50, 100);
            this.velocity = new Vector2D(0, 0);
            this.mass = 1.0;
            this.friction = 0.1;
            this.jumpForce = 20;
            this.moveForce = 15;
            this.gravity = 0.8;
            this.onGround = false;
            this.keys = {};

            // Dynamic avatar dimensions in game units (0-100 scale)
            this.avatarDimensions = { width: 0, height: 0 };

            // Physics states
            this.isPhysicsActive = false; // Controlled by menu
            this.isSwimming = false;
            this.lastBounceSurface = 0; // To prevent infinite bounces

            // Specific Drawaria DOM selectors for avatar and game area
            this.gameAreaSelectors = [
                "#drawing-assistant-overlay", // Priorizar el canvas overlay
                "#gamearea",
                "#canvas",
                "body"
            ];
            this.myAvatarSelector = "body > div.spawnedavatar.spawnedavatar-self";
            this.myAvatarElement = null; // Actual DOM element of the avatar
            this.gameAreaElement = null; // Actual DOM element of the game area
            this.overlayCanvas = null; // Referencia específica al canvas overlay

            this.lastSentPosition = { x: -1, y: -1 };

            // Altura de la barra de UI inferior (en píxeles)
            this.bottomUIOffsetPx = 0;
            // Offset de píxeles visual controlado por el usuario para el suelo
            this.visualFloorOffsetPixels = 45; // PREDETERMINADO A 5 PÍXELES MÁS ARRIBA

            // NUEVO: Sistema de límites inteligente
            this.boundaryManager = null;

            // Reinicializar Interaction colors (RGB) con los nuevos tipos y colores por defecto
            this.surfaceColors = {}; // Se llenará en el constructor de PhysicsMenuController o con setHitboxColors

            // NUEVAS PROPIEDADES PARA EFECTOS DE FÍSICA
            this.originalGravity = this.gravity; // Guarda la gravedad inicial
            this.isAntiGravityActive = false;
            this.isSpeedBoostActive = false;
            this.currentSpeedBoostFactor = 1.0;
            this.pushBackForce = 20; // Fuerza del empuje hacia atrás
            this.speedBoostDuration = 1000; // Duración del speed boost en ms
            this.speedBoostTimeout = null;

            // AGREGAR AnimationShield
            this.animationShield = null; // Se inicializa al detectar el avatar

            this.setupInput();
            this.startAvatarDetection(); // Continuously detect avatar DOM element & sync position (passively)
            console.log('🎮 PhysicsAvatar created. Physics are DEACTIVATED by default. Click "Iniciar Física".');
        }

        setupInput() {
            // Specific controls: Alt+Z (Left), Alt+C (Right), Alt+X (Jump)
            document.addEventListener('keydown', (e) => {
                if (!this.isPhysicsActive) return;

                if (e.altKey && e.code === 'KeyZ') {
                    e.preventDefault();
                    this.keys['left'] = true;
                }
                if (e.altKey && e.code === 'KeyC') {
                    e.preventDefault();
                    this.keys['right'] = true;
                }
                if (e.altKey && e.code === 'KeyX') {
                    e.preventDefault();
                    this.keys['jump'] = true;
                }
            });

            document.addEventListener('keyup', (e) => {
                if (!this.isPhysicsActive) return;

                if (e.altKey && e.code === 'KeyZ') {
                    this.keys['left'] = false;
                }
                if (e.altKey && e.code === 'KeyC') {
                    this.keys['right'] = false;
                }
                if (e.altKey && e.code === 'KeyX') {
                    this.keys['jump'] = false;
                }
            });
        }

        // Nueva función para detectar específicamente el canvas overlay
        detectOverlayCanvas() {
            const overlayCanvas = document.getElementById('drawing-assistant-overlay');
            if (overlayCanvas) {
                this.overlayCanvas = overlayCanvas;
                console.log('🎯 Canvas overlay detectado:', {
                    width: overlayCanvas.width,
                    height: overlayCanvas.height,
                    styleWidth: overlayCanvas.style.width,
                    styleHeight: overlayCanvas.style.height
                });
                return true;
            }
            return false;
        }

        // Contenido del bucle de detección (extraído para claridad)
        _detectionLoopContent() {
            // Try to find avatar element
            if (!this.myAvatarElement) {
                const selectors = [
                    "body > div.spawnedavatar.spawnedavatar-self",
                    ".spawnedavatar-self",
                    "[class*='avatar'][class*='self']",
                    ".avatar.self"
                ];

                for (const selector of selectors) {
                    const element = document.querySelector(selector);
                    if (element) {
                        this.myAvatarElement = element;
                        console.log(`🎯 Avatar encontrado con selector: ${selector}`);
                        // Inicializar AnimationShield cuando se encuentra el avatar
                        if (!this.animationShield) {
                            this.animationShield = new AnimationShield(this);
                            console.log('🛡️ AnimationShield inicializado');
                        }
                        break;
                    }
                }
            }

            // Detectar canvas overlay específicamente
            if (!this.overlayCanvas) {
                this.detectOverlayCanvas();
            }

            // Try to find the best game area reference element (PRIORIZAR OVERLAY)
            if (!this.gameAreaElement) {
                for (const selector of this.gameAreaSelectors) {
                    const element = document.querySelector(selector);
                    if (element) {
                        this.gameAreaElement = element;
                        console.log(`🎮 Game area detectada: ${selector}`);
                        break; // Salir después de encontrar el mejor elemento
                    }
                }
            }

            // Si todos los elementos críticos están listos, inicializar BoundaryManager
            if (this.myAvatarElement && this.gameAreaElement && this.overlayCanvas && !this.boundaryManager) {
                this.boundaryManager = new BoundaryManager(this);
                console.log('📐 BoundaryManager inicializado');
            }


            // Detectar la altura de la UI inferior
            if (this.gameAreaElement) {
                // Selectores más específicos para barras de UI comunes
                const hotbarElement = document.querySelector('.hotbar, #colorpickercontainer, .inputcontainer, [class*="chat-input-row"], [class*="drawing-hotbar"]');
                if (hotbarElement) {
                    this.bottomUIOffsetPx = hotbarElement.offsetHeight > 0 ? hotbarElement.offsetHeight : 0;
                } else {
                    this.bottomUIOffsetPx = 0;
                }
            }


            // If elements are found and physics is NOT active, update position from DOM passively
            if (this.myAvatarElement && this.gameAreaElement && !this.isPhysicsActive) {
                this.updatePositionFromDOM(true);
            }
        }

        // Continuously try to find the avatar element and game area reference
        startAvatarDetection() {
            this.detectionInterval = setInterval(() => this._detectionLoopContent(), 50); // Llama al contenido del bucle
        }

        // Update internal physics position from DOM's avatar element
        updatePositionFromDOM(passiveSync = false) {
            if (!this.myAvatarElement || !this.gameAreaElement || !drawariaCanvas) return false;

            try {
                const avatarRect = this.myAvatarElement.getBoundingClientRect();
                const gameRect = this.gameAreaElement.getBoundingClientRect();

                // Update dynamic avatar dimensions (in game units 0-100)
                this.avatarDimensions.width = (avatarRect.width / gameRect.width) * 100;
                this.avatarDimensions.height = (avatarRect.height / gameRect.height) * 100;

                // Calculate current X based on center of avatar
                const currentX = ((avatarRect.left + avatarRect.width / 2 - gameRect.left) / gameRect.width) * 100;
                // Calculate current Y based on the BOTTOM of the avatar
                const currentY = ((avatarRect.bottom - gameRect.top) / gameRect.height) * 100;

                // Only update internal position if it's a significant change or active sync
                if (!passiveSync ||
                    (Math.abs(this.position.x - currentX) > 0.5 || Math.abs(this.position.y - currentY) > 0.5)) {
                    this.position.x = currentX;
                    this.position.y = currentY;

                    if (passiveSync) {
                        this.velocity = new Vector2D(0, 0); // Reset velocity if not active
                        this.onGround = (currentY >= 99.5); // Check if near ground
                    }
                }
                return true;
            } catch (e) {
                console.warn("Error reading avatar DOM position:", e);
                return false;
            }
        }

        // Activate physics: snap avatar to floor, reset velocity
        activatePhysics() {
            if (this.isPhysicsActive) return;

            this.isPhysicsActive = true;
            this.updatePositionFromDOM(); // Get current DOM position and dimensions

            // Force position to the ground (100) and reset motion for a stable start
            this.position.y = 100; // Snap avatar to the floor (bottom = 100)
            this.velocity = new Vector2D(0, 0);
            this.onGround = true; // Mark as grounded
            this.isSwimming = false; // Not swimming initially
            console.log('✅ Physics ACTIVATED. Avatar snapped to floor and motion reset.');
            console.log(`🎮 Controls: Alt+Z (left), Alt+C (right), Alt+X (jump).`);
        }

        // Deactivate physics: stop motion, clear keys
        deactivatePhysics() {
            if (!this.isPhysicsActive) return;

            this.isPhysicsActive = false;
            this.keys = {};
            this.velocity = new Vector2D(0, 0);
            this.onGround = false;
            this.isSwimming = false;
            // Al desactivar, restaurar gravedad a original
            this.gravity = this.originalGravity;
            this.isAntiGravityActive = false;
            this.isSpeedBoostActive = false;
            this.currentSpeedBoostFactor = 1.0;
            if(this.speedBoostTimeout) clearTimeout(this.speedBoostTimeout);
            console.log('❌ Physics DEACTIVATED. Avatar released from control.');
        }

        // Main physics update loop (called continuously by menuController)
        update() {
            if (!this.isPhysicsActive) return;

            // Apply gravity (reduced in water)
            if (this.isSwimming) {
                this.velocity.y += this.gravity * 0.3;
            } else {
                this.velocity.y += this.gravity;
            }

            // Handle input
            const currentMoveForce = this.moveForce * this.currentSpeedBoostFactor;
            if (this.keys['left']) this.velocity.x -= currentMoveForce * (this.isSwimming ? 0.15 : 0.1);
            if (this.keys['right']) this.velocity.x += currentMoveForce * (this.isSwimming ? 0.15 : 0.1);

            if (this.keys['jump'] && (this.onGround || this.isSwimming)) {
                this.velocity.y = -this.jumpForce * (this.isSwimming ? 0.7 : 1.0);
                this.onGround = false;
            }

            // Apply friction (increased in water)
            this.velocity.x *= (1 - (this.isSwimming ? 0.15 : this.friction));
            if (Math.abs(this.velocity.x) < 0.01) this.velocity.x = 0;

            // Calculate next position
            let nextX = this.position.x + (this.velocity.x * 0.1);
            let nextY = this.position.y + (this.velocity.y * 0.1);

            // **APLICAR LÍMITES INTELIGENTES ANTES DE RESOLVER COLISIONES**
            if (this.boundaryManager) {
                this.boundaryManager.updateCanvasBounds(); // Asegurar que los límites estén actualizados
                const bounds = this.boundaryManager.realTimeBounds;

                // Restringir X con límites inteligentes
                nextX = Math.max(bounds.minX, Math.min(bounds.maxX, nextX));

                // Restringir Y con límites inteligentes
                nextY = Math.max(bounds.minY, Math.min(bounds.maxY, nextY));
            }

            // Check and resolve collisions
            this.handleCollisions(nextX, nextY);

            // Send new position to Drawaria if changed significantly
            if (this.hasPositionChanged()) {
                this.sendPositionToDrawaria();
            }
        }

        // Handle all types of collisions based on pixel checks
        handleCollisions(nextX, nextY) {
            const collisions = this.checkAvatarCollisions();

            // Obtener el tipo de superficie en el centro o en la parte inferior si no hay centro específico
            // Prioridad: centro, luego bottom
            let primaryCollision = null;
            if (collisions.center && this.surfaceColors[collisions.center.type]) {
                primaryCollision = collisions.center;
            } else if (collisions.bottom && this.surfaceColors[collisions.bottom.type]) {
                primaryCollision = collisions.bottom;
            }

            // Reiniciar efectos que no son persistentes
            if (!primaryCollision || primaryCollision.type !== 'speed_boost') {
                if (this.isSpeedBoostActive) {
                    clearTimeout(this.speedBoostTimeout);
                    this.isSpeedBoostActive = false;
                    this.currentSpeedBoostFactor = 1.0;
                    console.log('💨 Speed Boost terminado/reseteado.');
                }
            }
            if (!primaryCollision || primaryCollision.type !== 'anti_gravity') {
                if (this.isAntiGravityActive) {
                    this.gravity = this.originalGravity;
                    this.isAntiGravityActive = false;
                    console.log('⬇️ Gravedad normal restablecida.');
                }
            }
            if (!primaryCollision || primaryCollision.type !== 'water') {
                if (this.isSwimming) {
                    console.log('🏊‍♂️ Exited water.');
                    this.isSwimming = false;
                }
            }

            if (primaryCollision) {
                switch (primaryCollision.type) {
                    case 'solid':
                        // Comportamiento por defecto, ya manejado
                        break;
                    case 'bounce':
                        // Comportamiento por defecto, ya manejado en resolvedY
                        break;
                    case 'speed_boost':
                        if (!this.isSpeedBoostActive) {
                            this.isSpeedBoostActive = true;
                            this.currentSpeedBoostFactor = 1.8; // Aumenta la velocidad en un 80%
                            console.log('⚡ Speed Boost activado!');
                            // Reiniciar speed boost si se pisa de nuevo antes de que termine
                            clearTimeout(this.speedBoostTimeout);
                            this.speedBoostTimeout = setTimeout(() => {
                                this.isSpeedBoostActive = false;
                                this.currentSpeedBoostFactor = 1.0;
                                console.log('💨 Speed Boost terminado.');
                            }, this.speedBoostDuration);
                        }
                        break;
                    case 'water':
                        if (!this.isSwimming) console.log('🏊‍♂️ Entered water.');
                        this.isSwimming = true;
                        break;
                    case 'anti_gravity':
                        if (!this.isAntiGravityActive) {
                            this.gravity = -0.8; // Gravedad invertida para ir hacia arriba
                            this.isAntiGravityActive = true;
                            this.velocity.y -= 5; // Un pequeño empuje inicial hacia arriba
                            console.log('⬆️ Gravedad invertida activada!');
                        }
                        break;
                    case 'normal_gravity':
                        if (this.isAntiGravityActive) {
                            this.gravity = this.originalGravity;
                            this.isAntiGravityActive = false;
                            console.log('⬇️ Gravedad normal restablecida.');
                        }
                        break;
                    case 'push_back':
                        if (this.onGround) { // Solo empujar si está en el suelo o moviéndose
                            if (this.velocity.x === 0) { // Si está quieto, empujar en una dirección aleatoria
                                this.velocity.x = (Math.random() > 0.5 ? 1 : -1) * this.pushBackForce;
                            } else { // Si se mueve, empujar en la dirección opuesta
                                this.velocity.x = -Math.sign(this.velocity.x) * this.pushBackForce;
                            }
                            this.velocity.y = -5; // Un pequeño salto al ser empujado
                            this.onGround = false;
                            console.log('↩️ Empuje hacia atrás activado!');
                        }
                        break;
                    case 'death':
                        // Ya manejado al principio, pero por si acaso
                        console.log('💀 Avatar tocó zona de MUERTE - ¡Reseteando!');
                        this.reset();
                        return;
                    default:
                        // Para cualquier hitbox personalizado sin lógica específica aún
                        console.log(`ℹ️ Colisión con hitbox tipo '${primaryCollision.type}'`);
                        break;
                }
            }

            // Resolve X-axis movement
            let resolvedX = nextX;
            if ((this.keys['left'] && collisions.left && collisions.left.type === 'solid') ||
                (this.keys['right'] && collisions.right && collisions.right.type === 'solid')) {
                this.velocity.x = 0; // Stop horizontal movement
                resolvedX = this.position.x; // Stay at current X
            }
            this.position.x = resolvedX; // Ya está limitado por boundaries del update()

            // Resolve Y-axis movement
            let resolvedY = nextY;
            if (this.velocity.y > 0) { // Moving downwards (falling or landing)
                if (collisions.bottom && (collisions.bottom.type === 'solid' || collisions.bottom.type === 'bounce')) {
                    this.velocity.y = 0; // Stop vertical motion
                    resolvedY = this.position.y; // Snap to current Y if hit (avoid falling through)
                    this.onGround = true; // Mark as grounded

                    // Handle bounce surface
                    if (collisions.bottom.type === 'bounce' && Date.now() - this.lastBounceSurface > 200) { // Small cooldown
                        console.log('🟡 Hit bounce pad!');
                        this.velocity.y = -this.jumpForce * 1.5; // Stronger bounce
                        this.onGround = false;
                        this.lastBounceSurface = Date.now();
                    }
                }
            } else if (this.velocity.y < 0) { // Moving upwards (jumping)
                if (collisions.top && collisions.top.type === 'solid') {
                    this.velocity.y = 0; // Stop vertical motion (hit ceiling)
                    resolvedY = this.position.y; // Snap to current Y
                }
            }
            this.position.y = resolvedY; // Ya está limitado por boundaries del update()

            // **VERIFICACIÓN FINAL DE SUELO INTELIGENTE**
            if (this.boundaryManager) {
                const maxY = this.boundaryManager.realTimeBounds.maxY;
                if (this.position.y >= maxY - this.boundaryManager.gameUnitFloorTolerance) { // Tolerancia para asegurar el snap
                    this.position.y = maxY;
                    this.velocity.y = 0;
                    this.onGround = true;
                } else if (this.position.y < maxY - this.boundaryManager.gameUnitFloorTolerance && collisions.bottom && collisions.bottom.type !== 'solid' && primaryCollision?.type !== 'water') {
                    // Si no está en el suelo y no hay colisión sólida debajo, y no está en agua, no está en el suelo
                    this.onGround = false;
                }
            } else { // Fallback si boundaryManager no está listo
                 if (this.position.y >= 99.5) { // Si muy cerca de 100 (suelo)
                    this.position.y = 100; // Snap precisamente al suelo
                    this.velocity.y = 0;
                    this.onGround = true;
                } else if (!collisions.bottom || collisions.bottom.type === 'water') {
                    this.onGround = false;
                }
            }
        }

        // Checks a pixel color at a specific game coordinate (0-100) with optional game offsets
        checkPixelCollision(gameX, gameY, gameOffsetX = 0, gameOffsetY = 0) {
            if (!drawariaCanvas || !drawariaCtx || !this.gameAreaElement) return null;

            try {
                const effectiveGameX = gameX + gameOffsetX;
                const effectiveGameY = gameY + gameOffsetY;

                const canvasWidthPx = drawariaCanvas.width;
                const canvasHeightPx = drawariaCanvas.height;

                // Convert combined game coordinates to canvas pixel coordinates
                const pixelX = Math.floor((effectiveGameX / 100) * canvasWidthPx);
                const pixelY = Math.floor((effectiveGameY / 100) * canvasHeightPx);

                // Ensure within canvas bounds
                if (pixelX < 0 || pixelX >= canvasWidthPx || pixelY < 0 || pixelY >= canvasHeightPx) {
                    return null;
                }

                const imageData = drawariaCtx.getImageData(pixelX, pixelY, 1, 1);
                const pixelData = imageData.data;
                const r = pixelData[0];
                const g = pixelData[1];
                const b = pixelData[2];
                const a = pixelData[3];

                if (a < 100) return null; // Treat mostly transparent pixels as no collision

                for (const typeKey in this.surfaceColors) {
                    const colorData = this.surfaceColors[typeKey];
                    const tolerance = 40; // Increased tolerance for color variations
                    if (Math.abs(r - colorData.r) < tolerance &&
                        Math.abs(g - colorData.g) < tolerance &&
                        Math.abs(b - colorData.b) < tolerance) {
                        return { color: colorData.hex, type: colorData.type, r, g, b };
                    }
                }
                return null; // No special color found (treat as non-collidable or background)
            } catch (error) {
                // This can happen if canvas is not yet ready or cross-origin
                // console.warn('Error in pixel detection:', error);
                return null;
            }
        }

        // Checks multiple points around the avatar for collisions
        checkAvatarCollisions() {
            const collisions = { bottom: null, top: null, left: null, right: null, center: null };

            const halfWidth = this.avatarDimensions.width / 2;
            const height = this.avatarDimensions.height;
            const checkOffset = 0.5; // Small offset to check just outside the avatar boundary

            // Bottom-center (for ground and bounce pads)
            collisions.bottom = this.checkPixelCollision(this.position.x, this.position.y + checkOffset);

            // Top-center (for ceiling collision)
            collisions.top = this.checkPixelCollision(this.position.x, this.position.y - height - checkOffset);

            // Mid-height left/right (for side walls)
            const midY = this.position.y - (height / 2);
            collisions.left = this.checkPixelCollision(this.position.x - halfWidth - checkOffset, midY);
            collisions.right = this.checkPixelCollision(this.position.x + halfWidth + checkOffset, midY);

            // Center (for water/death zones that affect the whole avatar)
            collisions.center = this.checkPixelCollision(this.position.x, midY);

            return collisions;
        }

        hasPositionChanged() {
            const threshold = 0.1; // Minimum change threshold to send command, reducido para más frecuencia
            return Math.abs(this.position.x - this.lastSentPosition.x) > threshold ||
                   Math.abs(this.position.y - this.lastSentPosition.y) > threshold;
        }

        // Sends the physics position to Drawaria's avatar system
        sendPositionToDrawaria() {
            const targetX = this.position.x;
            const targetYBottom = this.position.y; // This is the BOTTOM-Y in our physics model

            let adjustedDisplayY;
            if (this.avatarDimensions.height > 0) {
                // Adjust Y to be the CENTER of the avatar, as Drawaria usually expects center or top-left
                // (Bottom Y - Half Avatar Height)
                adjustedDisplayY = targetYBottom - (this.avatarDimensions.height / 2);
            } else {
                // Fallback estimate if dimensions not yet acquired
                adjustedDisplayY = targetYBottom - 6; // Rough estimate for center if avatar is 12 units high
            }
            // Ensure adjusted Y stays within 0-100 game unit bounds
            adjustedDisplayY = Math.max(0, Math.min(100, adjustedDisplayY));

            // SIEMPRE actualizar elementos visuales PRIMERO (no como fallback)
            this.updateVisualElements(targetX, adjustedDisplayY); // Pasa adjustedDisplayY que es el centro

            const socket = this.getAnyAvailableSocket();

            // Try window.game.sendMove first
            if (window.game && typeof window.game.sendMove === "function") {
                try {
                    window.game.sendMove(targetX, adjustedDisplayY);
                    this.lastSentPosition = { x: targetX, y: targetYBottom }; // Store physics position
                    return;
                } catch (error) {
                    console.warn('Error with game.sendMove, trying WebSocket fallback:', error);
                }
            }

            // Fallback to WebSocket command
            if (socket) {
                try {
                    const encodedPos = 1e4 * Math.floor((targetX / 100) * 1e4) + Math.floor((adjustedDisplayY / 100) * 1e4);
                    socket.send(`42["clientcmd",103,[${encodedPos},false]]`);
                    this.lastSentPosition = { x: targetX, y: targetYBottom };
                    return;
                } catch (error) {
                    console.warn('Error sending movement command via WebSocket:', error);
                }
            }
        }

        getAnyAvailableSocket() {
            if (drawariaSocket && drawariaSocket.readyState === WebSocket.OPEN) { return drawariaSocket; }
            if (window._io && window._io.socket && window._io.socket.readyState === WebSocket.OPEN) {
                drawariaSocket = window._io.socket; return drawariaSocket;
            }
            for (const prop in window) {
                try { if (window[prop] instanceof WebSocket && window[prop].readyState === WebSocket.OPEN && window[prop].url.includes('drawaria')) {
                        drawariaSocket = window[prop]; return drawariaSocket; }
                } catch (e) { /* Access denied */ }
            }
            return null;
        }

        // Update local visual elements (brush cursor, avatar DOM element)
        updateVisualElements(targetX, targetYCenter) { // targetYCenter es la posición Y del centro del avatar
            // PRIORIZAR el canvas overlay si está disponible, sino gameAreaElement
            const effectiveGameArea = this.overlayCanvas || this.gameAreaElement;

            if (!effectiveGameArea) {
                console.warn('No game area or overlay canvas available for visual updates');
                return;
            }

            const gameRect = effectiveGameArea.getBoundingClientRect();
            const displayWidth = gameRect.width;
            const displayHeight = gameRect.height; // Altura total del área de visualización

            // Calcula la altura base del área jugable (altura total - offset UI inferior detectado)
            const basePlayableHeightPx = Math.max(1, displayHeight - this.bottomUIOffsetPx);

            // Aplica el offset visual de píxeles controlado por el usuario para el suelo
            const finalPlayableHeightPx = basePlayableHeightPx - this.visualFloorOffsetPixels;

            // Usa dimensiones estimadas si las reales no están disponibles
            const effectiveAvatarGameHeight = this.avatarDimensions.height > 0 ? this.avatarDimensions.height : 12; // Fallback a 12 unidades de juego
            const effectiveAvatarGameWidth = this.avatarDimensions.width > 0 ? this.avatarDimensions.width : 8; // Fallback a 8 unidades de juego

            // Calcula las dimensiones del avatar en píxeles, basado en la altura total de visualización
            const avatarPixelWidth = (effectiveAvatarGameWidth / 100) * displayWidth;
            const avatarPixelHeight = (effectiveAvatarGameHeight / 100) * displayHeight;

            // Mapea la unidad Y del juego (targetYCenter, 0-100) al centro en píxeles del área jugable final.
            let avatarCenterPixelY = (targetYCenter / 100) * finalPlayableHeightPx;

            // Luego, calcula la posición en píxeles para la PARTE SUPERIOR del avatar.
            let pixelYTop = avatarCenterPixelY - (avatarPixelHeight / 2);

            // Cálculo de la posición X
            const pixelX = (targetX / 100) * displayWidth - (avatarPixelWidth / 2); // targetX es el centro

            // Aplicar límites para los elementos visuales (asegura que el avatar está dentro de finalPlayableHeightPx)
            const constrainedPixelX = Math.max(0, Math.min(pixelX, displayWidth - avatarPixelWidth));
            const constrainedPixelYTop = Math.max(0, Math.min(pixelYTop, finalPlayableHeightPx - avatarPixelHeight));

            // Actualizar brush cursor
            const brushCursor = document.querySelector('.brushcursor');
            if (brushCursor) {
                brushCursor.classList.remove('brushcursor-hidden');
                brushCursor.style.transform = `translate(${constrainedPixelX}px, ${constrainedPixelYTop}px)`;
                brushCursor.style.display = 'block';
                brushCursor.style.transition = 'transform 0.05s ease-out';
            }

            if (this.myAvatarElement) {
                this.myAvatarElement.style.transform = `translate(${constrainedPixelX}px, ${constrainedPixelYTop}px)`;
                this.myAvatarElement.style.position = 'absolute';
                this.myAvatarElement.style.zIndex = '100';
                this.myAvatarElement.style.transition = 'transform 0.05s ease-out';
                this.myAvatarElement.offsetHeight; // Forzar un repaint
            }
        }

        reset() {
            // Reset to current X, but force Y to ground (100) and zero velocity
            this.position.x = this.position.x; // Keep current X
            this.position.y = 100; // Force to ground
            this.velocity = new Vector2D(0, 0);
            this.onGround = true;
            this.isSwimming = false;
            // Al resetear, restaurar gravedad a original
            this.gravity = this.originalGravity;
            this.isAntiGravityActive = false;
            this.isSpeedBoostActive = false;
            this.currentSpeedBoostFactor = 1.0;
            if(this.speedBoostTimeout) clearTimeout(this.speedBoostTimeout);
            console.log('🔄 Avatar reseteado a posición actual y suelo.');
        }

        boost() {
            if (!this.isPhysicsActive) return;
            this.velocity.y = -this.jumpForce * 2;
            this.onGround = false;
            console.log('🚀 Boost aplicado');
        }

        destroy() {
            if (this.detectionInterval) {
                clearInterval(this.detectionInterval);
            }
            if (this.animationShield) { // Destruir AnimationShield
                this.animationShield.destroy();
            }
            if (this.boundaryManager) { // Destruir BoundaryManager
                this.boundaryManager.destroy();
            }
        }
    }

    // ======================================
    // CLASE PARA PROTEGER CONTROLES LOCALES DE ANIMACIONES DE SERVIDOR
    // ======================================
    class AnimationShield {
        constructor(avatar) {
            this.avatar = avatar;
            this.observer = null;
            this.reapplyInterval = null; // Para re-aplicar constantemente si es necesario

            this.setupProtection();
            console.log('🛡️ AnimationShield activado.');
        }

        setupProtection() {
            // Monitorear continuamente el avatar element para protegerlo
            this.startMonitoringAvatar();
        }

        startMonitoringAvatar() {
            if (this.avatar.myAvatarElement) {
                this.protectElement(this.avatar.myAvatarElement);
            }

            // Si el avatar element no está disponible al principio, inténtalo periódicamente
            this.reapplyInterval = setInterval(() => {
                if (this.avatar.myAvatarElement && !this.observer) {
                    this.protectElement(this.avatar.myAvatarElement);
                }
            }, 500); // Comprobar cada 0.5 segundos
        }

        protectElement(element) {
            if (this.observer) this.observer.disconnect(); // Desconectar si ya está observando

            this.observer = new MutationObserver((mutations) => {
                if (!this.avatar.isPhysicsActive) return; // Solo proteger si la física está activa

                mutations.forEach((mutation) => {
                    if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                        const style = element.style;

                        // Si detectamos una animación o transición que no es nuestra, la forzamos a detenerse
                        if (style.animation || style.transition !== 'transform 0.05s ease-out') {
                            this.observer.disconnect(); // Desconectar temporalmente para evitar loops
                            style.animation = '';
                            style.transition = 'transform 0.05s ease-out'; // Restaurar nuestra transición

                            // Reconectar observer después de un breve momento
                            setTimeout(() => {
                                if (this.observer) {
                                    this.observer.observe(element, { attributes: true, attributeFilter: ['style'] });
                                }
                            }, 0);
                        }

                        // Asegurar propiedades de posicionamiento
                        if (style.position !== 'absolute') style.position = 'absolute';
                        if (style.zIndex !== '100') style.zIndex = '100';
                        if (style.pointerEvents !== 'auto') style.pointerEvents = 'auto';
                    }
                });
            });

            this.observer.observe(element, { attributes: true, attributeFilter: ['style'] });
            console.log('👁️ Monitoring avatar element style for interference.');
        }

        destroy() {
            if (this.observer) {
                this.observer.disconnect();
                this.observer = null;
            }
            if (this.reapplyInterval) {
                clearInterval(this.reapplyInterval);
                this.reapplyInterval = null;
            }
            console.log('🛡️ AnimationShield desactivado y destruido.');
        }
    }


    // ======================================
    // CLASE PARA GESTIONAR LÍMITES DEL CANVAS Y COLISIONES
    // ======================================
    class BoundaryManager {
        constructor(avatar) {
            this.avatar = avatar;
            this.canvasBounds = null;
            this.realTimeBounds = {
                minX: 0,
                maxX: 100,
                minY: 0,
                maxY: 100
            };
            this.gameUnitFloorTolerance = 0.5; // Pequeña tolerancia para la detección del suelo de la física (unidades de juego 0-100)
            this.updateInterval = null;
            this.startRealTimeUpdates();
        }

        startRealTimeUpdates() {
            // Actualización continua de límites en tiempo real
            this.updateInterval = setInterval(() => {
                this.updateCanvasBounds();
                // La aplicación de los límites se hace en PhysicsAvatar.update() y handleCollisions()
            }, 16); // ~60 FPS para detección suave
        }

        updateCanvasBounds() {
            // Detectar canvas overlay principal, mainCanvas o gameArea
            const overlayCanvas = document.getElementById('drawing-assistant-overlay');
            const mainCanvas = document.getElementById('canvas');
            const gameArea = document.getElementById('gamearea');

            const activeElement = overlayCanvas || mainCanvas || gameArea;

            if (activeElement) {
                const rect = activeElement.getBoundingClientRect();
                const style = window.getComputedStyle(activeElement);

                // Obtener dimensiones reales considerando CSS y atributos
                const realWidth = overlayCanvas ?
                    (overlayCanvas.width || parseFloat(style.width)) :
                    (activeElement.offsetWidth || rect.width);

                const realHeight = overlayCanvas ?
                    (overlayCanvas.height || parseFloat(style.height)) :
                    (activeElement.offsetHeight || rect.height);

                this.canvasBounds = {
                    element: activeElement,
                    rect: rect,
                    width: realWidth,
                    height: realHeight,
                    top: rect.top,
                    left: rect.left
                };

                // Calcular límites en coordenadas del juego (0-100)
                this.calculateGameBounds();
            }
        }

        calculateGameBounds() {
            if (!this.canvasBounds || !this.avatar.myAvatarElement) return;

            try {
                // Dimensiones del avatar en unidades de juego (0-100)
                // Usamos avatarDimensions del PhysicsAvatar que ya están en unidades de juego
                const avatarWidthGameUnits = this.avatar.avatarDimensions.width > 0 ? this.avatar.avatarDimensions.width : 8;
                const avatarHeightGameUnits = this.avatar.avatarDimensions.height > 0 ? this.avatar.avatarDimensions.height : 12;

                // Límites inteligentes para el sistema de física (0-100 game units)
                // minX/maxX para el CENTRO del avatar
                this.realTimeBounds = {
                    minX: avatarWidthGameUnits / 2, // Centro no se salga por la izquierda
                    maxX: 100 - (avatarWidthGameUnits / 2), // Centro no se salga por la derecha
                    minY: avatarHeightGameUnits, // Para que el TOP del avatar no se salga por arriba
                    maxY: 100 - this.gameUnitFloorTolerance // El "suelo" de la física, ligeramente por encima de 100 para estabilidad
                };

            } catch (error) {
                console.warn('Error calculando límites del juego:', error);
            }
        }

        // Debugging visual
        debugBounds() {
            console.log('🔍 Información de límites:');
            console.log('Canvas bounds:', this.canvasBounds);
            console.log('Game bounds (0-100):', this.realTimeBounds);
            console.log('Avatar current position (0-100):', this.avatar.position);
            console.log('Avatar dimensions (0-100):', this.avatar.avatarDimensions);
            console.log('Bottom UI Offset (px):', this.avatar.bottomUIOffsetPx);
            console.log('Visual Floor Offset (px):', this.avatar.visualFloorOffsetPixels);
        }

        destroy() {
            if (this.updateInterval) {
                clearInterval(this.updateInterval);
                this.updateInterval = null;
            }
            console.log('📐 BoundaryManager desactivado y destruido.');
        }
    }


    // ======================================
    // MENÚ PRINCIPAL CON PESTAÑAS
    // ======================================
    class PhysicsMenuController {
        constructor() {
            this.avatar = null;
            this.isRunning = false; // Controlled by menu buttons
            this.environmentDrawn = false;
            this.autoMovement = false;
            this.autoJump = false;
            this.boundaryBounce = false;

            // Nuevas propiedades para la sección de Hitbox Colors
            this.hitboxColorMap = {}; // Almacena los colores HEX actuales para cada tipo
            this.customHitboxTypes = []; // Para los tipos de hitbox personalizados
            this.nextCustomHitboxId = 0;
            this.selectedHitboxType = 'solid'; // Tipo de hitbox actualmente seleccionado para cambiar su color

            this.initHitboxColors(); // Inicializar el mapa de colores al inicio

            this.setupEventListeners();
            this.startStatusUpdates();
            this.startUpdateLoop(); // Physics loop always running, but avatar.update() is guarded by isPhysicsActive
        }

        // NUEVO: Inicializar los colores de los hitboxes
        initHitboxColors() {
            for (const key in DEFAULT_HITBOX_TYPES) {
                const typeInfo = DEFAULT_HITBOX_TYPES[key];
                this.hitboxColorMap[key] = STANDARD_COLORS[typeInfo.colorKey].hex;
            }
        }

        // NUEVO: Aplicar los colores actuales al PhysicsAvatar
        applyHitboxColorsToAvatar() {
            if (!this.avatar) return;
            this.avatar.surfaceColors = {}; // Limpiar los anteriores
            for (const typeKey in this.hitboxColorMap) {
                const hex = this.hitboxColorMap[typeKey];
                const rgb = hexToRgb(hex);
                this.avatar.surfaceColors[typeKey] = {
                    r: rgb.r, g: rgb.g, b: rgb.b,
                    type: typeKey, // El tipo de interacción es la misma key
                    hex: hex
                };
            }
            console.log('🎨 Colores de Hitbox aplicados al avatar:', this.avatar.surfaceColors);
        }

        // MODIFICAR initPhysicsSystem para aplicar los colores iniciales
        initPhysicsSystem() {
            if (this.avatar) return;

            this.avatar = new PhysicsAvatar();
            this.applyHitboxColorsToAvatar(); // Aplicar colores al avatar recién creado
            console.log('🎮 Physics Avatar system initialized. Physics are currently DEACTIVATED.');
            this.updateSliders();
        }

        // NUEVO: Método para actualizar la UI del color seleccionado
        updateSelectedColorInput() {
            const colorInput = document.getElementById('hitbox-color-input');
            if (colorInput && this.hitboxColorMap[this.selectedHitboxType]) {
                colorInput.value = this.hitboxColorMap[this.selectedHitboxType];
            }
            const selectedTypeDisplay = document.getElementById('selected-hitbox-type-display');
            if (selectedTypeDisplay) {
                selectedTypeDisplay.textContent = DEFAULT_HITBOX_TYPES[this.selectedHitboxType]?.name ||
                                                (this.customHitboxTypes.find(t => t.key === this.selectedHitboxType)?.name) ||
                                                this.selectedHitboxType;
            }
        }


        setupEventListeners() {
            // Tab switching
            document.querySelectorAll('#physics-menu .tab-btn').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const targetTab = e.target.dataset.tab;
                    this.switchTab(targetTab);
                });
            });

            // Control buttons
            document.getElementById('start-physics').addEventListener('click', () => {
                if (this.avatar) {
                    this.avatar.activatePhysics();
                    this.isRunning = true;
                    document.getElementById('physics-status').textContent = 'Activo';
                    console.log('🚀 Physics system started.');
                } else { console.warn('Physics Avatar not initialized yet. Cannot start.'); }
            });

            document.getElementById('stop-physics').addEventListener('click', () => {
                if (this.avatar) {
                    this.avatar.deactivatePhysics();
                    this.isRunning = false;
                    document.getElementById('physics-status').textContent = 'Detenido';
                    console.log('⏹️ Physics system stopped.');
                } else { console.warn('Physics Avatar not initialized yet. Cannot stop.'); }
            });

            document.getElementById('reset-avatar').addEventListener('click', () => {
                if (this.avatar) this.avatar.reset(); else console.warn('Physics Avatar not initialized yet. Cannot reset.');
            });
            document.getElementById('avatar-boost').addEventListener('click', () => {
                if (this.avatar) this.avatar.boost(); else console.warn('Physics Avatar not initialized yet. Cannot boost.');
            });

            // Physics sliders - update if avatar is initialized
            document.getElementById('gravity-slider').addEventListener('input', (e) => {
                if (this.avatar) {
                    this.avatar.gravity = parseFloat(e.target.value);
                    this.avatar.originalGravity = parseFloat(e.target.value); // También actualizar la gravedad original
                }
                document.getElementById('gravity-display').textContent = parseFloat(e.target.value);
            });
            document.getElementById('move-force-slider').addEventListener('input', (e) => {
                if (this.avatar) this.avatar.moveForce = parseFloat(e.target.value);
                document.getElementById('move-force-display').textContent = parseFloat(e.target.value);
            });
            document.getElementById('jump-force-slider').addEventListener('input', (e) => {
                if (this.avatar) this.avatar.jumpForce = parseFloat(e.target.value);
                document.getElementById('jump-force-display').textContent = parseFloat(e.target.value);
            });
            document.getElementById('friction-slider').addEventListener('input', (e) => {
                if (this.avatar) this.avatar.friction = parseFloat(e.target.value);
                document.getElementById('friction-display').textContent = parseFloat(e.target.value);
            });
            document.getElementById('mass-slider').addEventListener('input', (e) => {
                if (this.avatar) this.avatar.mass = parseFloat(e.target.value);
                document.getElementById('mass-display').textContent = parseFloat(e.target.value);
            });

            // Preset buttons - apply if avatar is initialized
            document.getElementById('preset-normal').addEventListener('click', () => { if (this.avatar) this.setPreset('normal'); });
            document.getElementById('preset-heavy').addEventListener('click', () => { if (this.avatar) this.setPreset('heavy'); });
            document.getElementById('preset-light').addEventListener('click', () => { if (this.avatar) this.setPreset('light'); });
            document.getElementById('preset-bouncy').addEventListener('click', () => { if (this.avatar) this.setPreset('bouncy'); });

            // NUEVO: Control de offset visual del suelo (ahora en píxeles)
            document.getElementById('floor-offset-slider').addEventListener('input', (e) => {
                const newOffset = parseFloat(e.target.value); // Este es un valor en píxeles para ajustar visualmente
                if (this.avatar) {
                    this.avatar.visualFloorOffsetPixels = newOffset;
                }
                document.getElementById('floor-offset-display').textContent = newOffset;
            });

            // NUEVO: Botón de debug
            document.getElementById('debug-boundaries').addEventListener('click', () => {
                if (this.avatar && this.avatar.boundaryManager) {
                    this.avatar.boundaryManager.debugBounds();
                } else {
                    console.warn('BoundaryManager no está inicializado para debug.');
                }
            });

            // Automation toggles
            document.getElementById('auto-movement').addEventListener('change', (e) => {
                this.autoMovement = e.target.checked;
            });
            document.getElementById('auto-jump').addEventListener('change', (e) => {
                this.autoJump = e.target.checked;
            });
            document.getElementById('boundary-bounce').addEventListener('change', (e) => {
                this.boundaryBounce = e.target.checked;
            });

            // NUEVO: Event listeners para Hitbox Colors
            document.getElementById('hitbox-color-input').addEventListener('input', (e) => {
                const newHex = e.target.value;
                this.hitboxColorMap[this.selectedHitboxType] = newHex;
                this.updateHitboxColorSwatch(this.selectedHitboxType, newHex); // Actualizar el swatch
                this.applyHitboxColorsToAvatar(); // Aplicar al avatar inmediatamente
            });

            // Event listeners para los botones de selección de hitbox (estos se crean dinámicamente)
            // Usamos delegación de eventos para los botones de selección
            document.getElementById('tab-hitbox-colors').addEventListener('click', (e) => {
                if (e.target.closest('.select-hitbox-type')) { // Usar closest para el botón padre
                    const btn = e.target.closest('.select-hitbox-type');
                    const type = btn.dataset.hitboxType;
                    this.selectedHitboxType = type;
                    this.updateSelectedColorInput();
                    document.querySelectorAll('#tab-hitbox-colors .select-hitbox-type').forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                }
            });


            document.getElementById('stop-automation').addEventListener('click', () => this.stopAutomation());
        }

        switchTab(targetTab) {
            document.querySelectorAll('#physics-menu .tab-btn').forEach(btn => btn.classList.remove('active'));
            document.querySelectorAll('#physics-menu .tab-panel').forEach(panel => panel.classList.remove('active'));

            document.querySelector(`#physics-menu [data-tab="${targetTab}"]`).classList.add('active');
            document.getElementById(`tab-${targetTab}`).classList.add('active');

            // Si cambiamos a la pestaña de hitboxes, actualizar su UI
            if (targetTab === 'hitbox-colors') {
                this.populateHitboxColorsUI();
            }
        }

        startPhysics() { // This method is now called by the menu button click, and activates Avatar physics
            if (this.isRunning) return;
            if (!this.avatar) {
                console.warn('Physics Avatar not yet available to start physics. Please wait for initialization.');
                return;
            }
            this.avatar.activatePhysics();
            this.isRunning = true;
            document.getElementById('physics-status').textContent = 'Activo';
            console.log('🚀 Physics system started via menu button.');
        }

        stopPhysics() { // This method is now called by the menu button click, and deactivates Avatar physics
            if (!this.isRunning) return;
            if (!this.avatar) {
                console.warn('Physics Avatar not available. Cannot stop.');
                return;
            }
            this.avatar.deactivatePhysics();
            this.isRunning = false;
            document.getElementById('physics-status').textContent = 'Detenido';
            console.log('⏹️ Physics system stopped via menu button.');
        }

        setPreset(preset) {
            if (!this.avatar) return;
            switch(preset) {
                case 'normal':
                    this.avatar.gravity = 0.8; this.avatar.moveForce = 15; this.avatar.jumpForce = 20;
                    this.avatar.friction = 0.1; this.avatar.mass = 1.0; break;
                case 'heavy':
                    this.avatar.gravity = 1.2; this.avatar.moveForce = 8; this.avatar.jumpForce = 15;
                    this.avatar.friction = 0.15; this.avatar.mass = 3.0; break;
                case 'light':
                    this.avatar.gravity = 0.3; this.avatar.moveForce = 25; this.avatar.jumpForce = 30;
                    this.avatar.friction = 0.05; this.avatar.mass = 0.3; break;
                case 'bouncy':
                    this.avatar.gravity = 0.6; this.avatar.moveForce = 20; this.avatar.jumpForce = 35;
                    this.avatar.friction = 0.02; this.avatar.mass = 0.8; break;
            }
            this.avatar.originalGravity = this.avatar.gravity; // Asegurar que la gravedad original también se actualice
            this.updateSliders();
            console.log(`🎯 Preset applied: ${preset}`);
        }

        updateSliders() {
            if (!this.avatar) return;
            document.getElementById('gravity-slider').value = this.avatar.gravity;
            document.getElementById('gravity-display').textContent = this.avatar.gravity;
            document.getElementById('move-force-slider').value = this.avatar.moveForce;
            document.getElementById('move-force-display').textContent = this.avatar.moveForce;
            document.getElementById('jump-force-slider').value = this.avatar.jumpForce;
            document.getElementById('jump-force-display').textContent = this.avatar.jumpForce;
            document.getElementById('friction-slider').value = this.avatar.friction;
            document.getElementById('friction-display').textContent = this.avatar.friction;
            document.getElementById('mass-slider').value = this.avatar.mass;
            document.getElementById('mass-display').textContent = this.avatar.mass;
        }

        async drawPlatform() {
            if (!drawariaCanvas) { console.warn('Cannot draw: Drawaria canvas not detected yet.'); return; }
            const x = 100 + Math.random() * (drawariaCanvas.width - 300);
            const y = 200 + Math.random() * (drawariaCanvas.height - 400);
            await drawRectangle(x, y, 150, 15, this.selectedColor, this.selectedThickness);
            console.log(`🏗️ Plataforma dibujada en (${x}, ${y})`);
        }

        async drawCircleObstacle() {
            if (!drawariaCanvas) { console.warn('Cannot draw: Drawaria canvas not detected yet.'); return; }
            const x = 100 + Math.random() * (drawariaCanvas.width - 200);
            const y = 100 + Math.random() * (drawariaCanvas.height - 200);
            const radius = 20 + Math.random() * 30;
            await drawCircle(x, y, radius, this.selectedColor, this.selectedThickness);
            console.log(`⭕ Círculo dibujado en (${x}, ${y}) con radio ${radius}`);
        }

        async drawEnvironment() {
            if (this.environmentDrawn) { console.log('🌍 Entorno ya dibujado. Ignorando.'); return; }
            if (!drawariaCanvas) { console.warn('Cannot draw: Drawaria canvas not detected yet for environment.'); return; }

            console.log('🌍 Dibujando entorno completo...');
            await drawRectangle(50, drawariaCanvas.height - 30, drawariaCanvas.width - 100, 20, '#4a4a4a', 3);
            await drawRectangle(100, drawariaCanvas.height - 150, 150, 15, '#6a6a6a', 2);
            await drawRectangle(300, drawariaCanvas.height - 250, 120, 15, '#6a6a6a', 2);
            await drawRectangle(500, drawariaCanvas.height - 180, 100, 15, '#6a6a6a', 2);
            await drawCircle(200, drawariaCanvas.height - 100, 25, '#8a4a4a', 2);
            await drawCircle(450, drawariaCanvas.height - 120, 20, '#8a4a4a', 2);
            this.environmentDrawn = true;
            console.log('✅ Entorno dibujado completamente');
        }

        clearCanvas() {
            if (drawariaSocket && drawariaSocket.readyState === WebSocket.OPEN) {
                drawariaSocket.send('42["drawcmd",1,[]]'); // Command to clear canvas on Drawaria server
                this.environmentDrawn = false;
                console.log('🗑️ Canvas cleared.');
            } else {
                console.warn('❌ WebSocket not open. Cannot send clear canvas command.');
            }
        }

        startDemoMode() {
            this.autoMovement = true;
            document.getElementById('auto-movement').checked = true;
            if (this.avatar) this.startPhysics();
            console.log('🎪 Demo Mode Activated.');
        }

        startChaosMode() {
            if (!this.avatar) return;
            this.avatar.gravity = Math.random() * 2; this.avatar.moveForce = 10 + Math.random() * 20;
            this.avatar.jumpForce = 15 + Math.random() * 25;
            this.autoMovement = true;
            document.getElementById('auto-movement').checked = true;
            this.startPhysics();
            this.updateSliders();
            console.log('🌪️ Chaos Mode Activated.');
        }

        stopAutomation() {
            this.autoMovement = false; this.autoJump = false; this.boundaryBounce = false;

            document.getElementById('auto-movement').checked = false;
            document.getElementById('auto-jump').checked = false;
            document.getElementById('boundary-bounce').checked = false;

            // Restablecer el offset visual del suelo
            if (this.avatar) {
                this.avatar.visualFloorOffsetPixels = 45; // Valor predeterminado de 5
            }
            document.getElementById('floor-offset-slider').value = 5;
            document.getElementById('floor-offset-display').textContent = 5;

            console.log('⏹️ Automation stopped.');
        }

        startUpdateLoop() {
            const update = () => {
                // This requestAnimationFrame loop is continuous
                if (this.isRunning && this.avatar) {
                    this.avatar.update();

                    if (this.autoMovement) {
                        if (Math.random() < 0.02) {
                            this.avatar.keys['left'] = false; this.avatar.keys['right'] = false;
                            const randomMove = Math.random();
                            if (randomMove < 0.4) this.avatar.keys['left'] = true;
                            else if (randomMove < 0.8) this.avatar.keys['right'] = true;
                        }
                    }

                    if (this.autoJump && this.avatar.onGround && Math.random() < 0.05) {
                        this.avatar.keys['jump'] = true;
                        setTimeout(() => this.avatar.keys['jump'] = false, 100);
                    }
                }
                requestAnimationFrame(update);
            };
            update(); // Initial call to start the loop
        }

        startStatusUpdates() {
            setInterval(() => {
                const avatarPositionElem = document.getElementById('avatar-position');
                const avatarGroundedElem = document.getElementById('avatar-grounded');
                const socketStatusElem = document.getElementById('socket-status');
                const velocityXElem = document.getElementById('velocity-x');
                const velocityYElem = document.getElementById('velocity-y');
                const canvasStatusElem = document.getElementById('canvas-status');

                // Update UI based on avatar's state (even if not fully initialized, show N/A)
                if (avatarPositionElem) avatarPositionElem.textContent = this.avatar ? `${this.avatar.position.x.toFixed(1)}, ${this.avatar.position.y.toFixed(1)}` : 'N/A';
                if (avatarGroundedElem) {
                    let status = this.avatar ? (this.avatar.onGround ? 'Sí' : 'No') : 'N/A';
                    if (this.avatar && this.avatar.isSwimming) status += ' (Nadando)';
                    avatarGroundedElem.textContent = status;
                } else if (avatarGroundedElem) {
                    avatarGroundedElem.textContent = 'N/A';
                }

                if (socketStatusElem) socketStatusElem.textContent = (drawariaSocket && drawariaSocket.readyState === WebSocket.OPEN) ? '✅ Conectado' : '❌ Desconectado';
                if (velocityXElem) velocityXElem.textContent = this.avatar ? this.avatar.velocity.x.toFixed(2) : 'N/A';
                if (velocityYElem) velocityYElem.textContent = this.avatar ? this.avatar.velocity.y.toFixed(2) : 'N/A';
                if (canvasStatusElem) canvasStatusElem.textContent = drawariaCanvas ? '✅ Sí' : '❌ No';

            }, 100);
        }

        // NUEVO: Método para poblar la UI de los colores de hitbox
        populateHitboxColorsUI() {
            const defaultHitboxesContainer = document.getElementById('default-hitboxes-container');
            const customHitboxesContainer = document.getElementById('custom-hitboxes-container');
            if (!defaultHitboxesContainer || !customHitboxesContainer) {
                console.warn('Contenedores de hitbox no encontrados en el DOM.');
                return;
            }
            defaultHitboxesContainer.innerHTML = '';
            customHitboxesContainer.innerHTML = '';

            for (const typeKey in DEFAULT_HITBOX_TYPES) {
                const typeInfo = DEFAULT_HITBOX_TYPES[typeKey];
                const currentHex = this.hitboxColorMap[typeKey];
                defaultHitboxesContainer.innerHTML += `
                    <div class="hitbox-item">
                        <button class="select-hitbox-type ${this.selectedHitboxType === typeKey ? 'active' : ''}" data-hitbox-type="${typeKey}">
                            <span class="hitbox-swatch" style="background-color: ${currentHex};"></span>
                            <span class="hitbox-name">${typeInfo.name}</span>
                            <span class="hitbox-description">${typeInfo.description}</span>
                        </button>
                    </div>
                `;
            }

            this.customHitboxTypes.forEach(customType => {
                const currentHex = this.hitboxColorMap[customType.key];
                customHitboxesContainer.innerHTML += `
                    <div class="hitbox-item">
                         <button class="select-hitbox-type ${this.selectedHitboxType === customType.key ? 'active' : ''}" data-hitbox-type="${customType.key}">
                            <span class="hitbox-swatch" style="background-color: ${currentHex};"></span>
                            <input type="text" class="custom-hitbox-name" value="${customType.name}" data-hitbox-key="${customType.key}" placeholder="Nombre de Hitbox">
                            <button class="remove-hitbox-type danger-btn" data-hitbox-key="${customType.key}">×</button>
                        </button>
                    </div>
                `;
            });

            // Asignar event listeners para la gestión de custom hitboxes
            this.addCustomHitboxEventListeners();
            this.updateSelectedColorInput(); // Asegurar que el input de color refleje el tipo seleccionado
        }

        // NUEVO: Método para añadir un hitbox personalizado a la UI
        addCustomHitboxUI() {
            const newKey = `custom_${this.nextCustomHitboxId++}`;
            const newName = `Custom ${this.nextCustomHitboxId}`;
            const defaultColor = STANDARD_COLORS.gray.hex;

            this.hitboxColorMap[newKey] = defaultColor;
            this.customHitboxTypes.push({ key: newKey, name: newName, type: 'solid' }); // Por defecto como 'solid'
            this.selectedHitboxType = newKey; // Seleccionar el nuevo hitbox

            this.populateHitboxColorsUI(); // Regenerar la UI
            this.applyHitboxColorsToAvatar(); // Aplicar cambios al avatar

            console.log(`➕ Hitbox personalizado '${newName}' agregado.`);
        }

        // NUEVO: Método para manejar eventos de custom hitboxes
        addCustomHitboxEventListeners() {
            const customHitboxesContainer = document.getElementById('custom-hitboxes-container');
            if (!customHitboxesContainer) return;

            customHitboxesContainer.querySelectorAll('.remove-hitbox-type').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    e.stopPropagation(); // Prevenir que el clic se propague al botón padre .select-hitbox-type
                    const keyToRemove = e.target.dataset.hitboxKey;
                    this.removeCustomHitbox(keyToRemove);
                });
            });

            customHitboxesContainer.querySelectorAll('.custom-hitbox-name').forEach(input => {
                input.addEventListener('change', (e) => {
                    const keyToUpdate = e.target.dataset.hitboxKey;
                    const newName = e.target.value;
                    const customType = this.customHitboxTypes.find(t => t.key === keyToUpdate);
                    if (customType) {
                        customType.name = newName;
                    }
                });
            });
        }

        // NUEVO: Método para remover un hitbox personalizado
        removeCustomHitbox(key) {
            delete this.hitboxColorMap[key];
            this.customHitboxTypes = this.customHitboxTypes.filter(type => type.key !== key);
            this.populateHitboxColorsUI(); // Regenerar la UI

            // Si el hitbox seleccionado fue el eliminado, seleccionar 'solid' por defecto
            if (this.selectedHitboxType === key) {
                this.selectedHitboxType = 'solid';
            }
            this.updateSelectedColorInput();
            this.applyHitboxColorsToAvatar(); // Aplicar cambios al avatar

            console.log(`🗑️ Hitbox '${key}' eliminado.`);
        }

        // NUEVO: Actualizar el color del "swatch"
        updateHitboxColorSwatch(typeKey, hex) {
            const swatch = document.querySelector(`.select-hitbox-type[data-hitbox-type="${typeKey}"] .hitbox-swatch`);
            if (swatch) {
                swatch.style.backgroundColor = hex;
            }
        }
    }

    // --- Core Logic for Menu and Physics Initialization ---

    // 1. Create and show the menu container immediately (DOM only)
    function createAndShowMenuContainer() {
        if (document.getElementById('physics-menu')) {
            console.log('✅ Menu container already exists in DOM. Skipping recreation.');
            return;
        }

        const menu = document.createElement('div');
        menu.id = 'physics-menu';
        menu.innerHTML = `
            <div class="menu-header">
                <div class="menu-title">
                    <span class="menu-icon">🚀</span>
                    <span>Avatar Physics Control</span>
                </div>
                <div class="menu-controls">
                    <button id="menu-minimize" class="menu-btn">−</button>
                    <button id="menu-close" class="menu-btn">×</button>
                </div>
            </div>

            <div class="menu-content">
                <!-- Navigation Tabs -->
                <div class="menu-tabs">
                    <button class="tab-btn active" data-tab="control">🎮 Control</button>
                    <button class="tab-btn" data-tab="physics">⚡ Física</button>
                    <button class="tab-btn" data-tab="hitbox-colors">🎨 Hitbox Colors</button>
                    <button class="tab-btn" data-tab="automation">🤖 Auto</button>
                </div>

                <!-- Tab Content -->
                <div class="tab-panel active" id="tab-control">
                        <div class="control-section">
                            <h4>🎯 Control del Avatar</h4>
                            <div class="button-grid">
                                <button id="start-physics" class="action-btn primary">
                                    <span class="btn-icon">▶️</span> Iniciar Física
                                </button>
                                <button id="stop-physics" class="action-btn secondary">
                                    <span class="btn-icon">⏹️</span> Detener
                                </button>
                                <button id="reset-avatar" class="action-btn warning">
                                    <span class="btn-icon">🔄</span> Reset Posición
                                </button>
                                <button id="avatar-boost" class="action-btn special">
                                    <span class="btn-icon">🚀</span> Super Salto
                                </button>
                            </div>

                            <div class="status-display">
                                <div class="status-item">
                                    <span class="status-label">Estado:</span>
                                    <span id="physics-status" class="status-value">Detenido</span>
                                </div>
                                <div class="status-item">
                                    <span class="status-label">Posición:</span>
                                    <span id="avatar-position" class="status-value">N/A</span>
                                </div>
                                <div class="status-item">
                                    <span class="status-label">En suelo:</span>
                                    <span id="avatar-grounded" class="status-value">N/A</span>
                                </div>
                            </div>
                        </div>

                        <div class="controls-info">
                            <h4>🎯 Controles del Teclado</h4>
                            <div class="controls-list">
                                <div class="control-item">
                                    <span class="key">Alt+Z</span>
                                    <span class="desc">Mover izquierda</span>
                                </div>
                                <div class="control-item">
                                    <span class="key">Alt+C</span>
                                    <span class="desc">Mover derecha</span>
                                </div>
                                <div class="control-item">
                                    <span class="key">Alt+X</span>
                                    <span class="desc">Saltar</span>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- Physics Tab -->
                    <div class="tab-panel" id="tab-physics">
                        <div class="physics-section">
                            <h4>⚡ Configuración Física</h4>

                            <div class="slider-group">
                                <label>Gravedad: <span id="gravity-display">0.8</span></label>
                                <input type="range" id="gravity-slider" min="0" max="2" step="0.1" value="0.8">
                            </div>

                            <div class="slider-group">
                                <label>Fuerza de Movimiento: <span id="move-force-display">15</span></label>
                                <input type="range" id="move-force-slider" min="5" max="30" step="1" value="15">
                            </div>

                            <div class="slider-group">
                                <label>Fuerza de Salto: <span id="jump-force-display">20</span></label>
                                <input type="range" id="jump-force-slider" min="10" max="40" step="1" value="20">
                            </div>

                            <div class="slider-group">
                                <label>Fricción: <span id="friction-display">0.1</span></label>
                                <input type="range" id="friction-slider" min="0" max="0.5" step="0.01" value="0.1">
                            </div>

                            <div class="slider-group">
                                <label>Masa: <span id="mass-display">1.0</span></label>
                                <input type="range" id="mass-slider" min="0.1" max="5" step="0.1" value="1.0">
                            </div>

                            <div class="preset-buttons">
                                <button id="preset-normal" class="preset-btn">🔧 Normal</button>
                                <button id="preset-heavy" class="preset-btn">🪨 Pesado</button>
                                <button id="preset-light" class="preset-btn">🪶 Liviano</button>
                                <button id="preset-bouncy" class="preset-btn">🏀 Saltarín</button>
                            </div>

                            <!-- NUEVO: Control de offset visual del suelo -->
                            <div class="slider-group">
                                <label>Offset Suelo Visual (px): <span id="floor-offset-display">5</span></label>
                                <input type="range" id="floor-offset-slider" min="-20" max="20" step="1" value="5">
                            </div>

                            <!-- NUEVO: Botón de debug -->
                            <div class="debug-controls">
                                <button id="debug-boundaries" class="preset-btn">🔍 Debug Límites</button>
                            </div>
                        </div>
                    </div>

                <!-- Hitbox Colors Tab -->
                <div class="tab-panel" id="tab-hitbox-colors">
                    <div class="hitbox-colors-section">
                        <h4>🎨 Colores y Efectos de Hitbox</h4>

                        <div class="color-picker-control">
                            <label>Editar Color para: <span id="selected-hitbox-type-display">Tocable</span></label>
                            <input type="color" id="hitbox-color-input" value="#000000">
                        </div>

                        <h5>Hitbox Types por Defecto</h5>
                        <div id="default-hitboxes-container" class="hitbox-grid">
                            <!-- Los hitboxes por defecto se generarán aquí -->
                        </div>

                    </div>
                </div>

                    <!-- Automation Tab -->
                    <div class="tab-panel" id="tab-automation">
                        <div class="automation-section">
                            <h4>🤖 Automatización</h4>

                            <div class="automation-controls">
                                <div class="toggle-group">
                                    <label class="toggle-label">
                                        <input type="checkbox" id="auto-movement">
                                        <span class="toggle-slider"></span>
                                        <span class="toggle-text">Movimiento Automático</span>
                                    </label>
                                </div>

                                <div class="toggle-group">
                                    <label class="toggle-label">
                                        <input type="checkbox" id="auto-jump">
                                        <span class="toggle-slider"></span>
                                        <span class="toggle-text">Salto Automático</span>
                                    </label>
                                </div>

                                <div class="toggle-group">
                                    <label class="toggle-label">
                                        <input type="checkbox" id="boundary-bounce">
                                        <span class="toggle-slider"></span>
                                        <span class="toggle-text">Rebote en Bordes</span>
                                    </label>
                                </div>
                            </div>

                            <div class="automation-presets">
                                <button id="stop-automation" class="auto-btn secondary">
                                    <span class="btn-icon">⏹️</span> Detener Auto
                                </button>
                            </div>
                        </div>
                    </div>

                    <!-- Info Tab -->
        `;

        styleMenu(menu);
        document.body.appendChild(menu);
        console.log('✅ Menu DOM structure appended to body.');

        // Setup immediate menu controls (minimize/close)
        document.getElementById('menu-minimize').addEventListener('click', () => {
            const content = document.querySelector('#physics-menu .menu-content');
            const isHidden = content.style.display === 'none';
            content.style.display = isHidden ? 'block' : 'none';
            document.getElementById('menu-minimize').textContent = isHidden ? '−' : '+';
        });

        document.getElementById('menu-close').addEventListener('click', () => {
            document.getElementById('physics-menu').style.display = 'none';
        });

        makeDraggable(menu);
    }

    // Apply CSS styles to the menu (with !important for overriding game styles)
    function styleMenu(menu) {
        const style = document.createElement('style');
        style.textContent = `
            #physics-menu {
                position: fixed !important;
                top: 20px !important;
                right: 20px !important;
                left: auto !important;
                bottom: auto !important;
                width: 420px !important;
                max-height: 85vh !important;
                background: linear-gradient(145deg, #1e1e2e, #2d3748) !important;
                border: 2px solid #4299e1 !important;
                border-radius: 16px !important;
                box-shadow: 0 20px 40px rgba(0,0,0,0.4) !important;
                color: white !important;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
                z-index: 2147483647 !important; /* Max z-index */
                backdrop-filter: blur(10px) !important;
                overflow: hidden !important;
                display: block !important; /* Ensure it's visible */
                opacity: 1 !important; /* Ensure full opacity */
                visibility: visible !important; /* Ensure visibility */
                pointer-events: auto !important; /* Ensure clicks are not blocked */
                transform: none !important; /* Prevent any default transforms */
            }

            .menu-header {
                display: flex; justify-content: space-between; align-items: center; padding: 16px 20px;
                background: linear-gradient(90deg, #4299e1, #3182ce); cursor: grab; user-select: none !important;
            } .menu-title { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: bold; }
            .menu-icon { font-size: 20px; } .menu-controls { display: flex; gap: 8px; }
            .menu-btn { background: rgba(255,255,255,0.2); border: none; color: white; width: 28px; height: 28px;
                border-radius: 6px; cursor: pointer; font-weight: bold; transition: background 0.3s; }
            .menu-btn:hover { background: rgba(255,255,255,0.3); } .menu-content { max-height: 70vh; overflow-y: auto; }
            .menu-tabs { display: flex; background: rgba(255,255,255,0.05); border-bottom: 1px solid rgba(255,255,255,0.1); }
            .tab-btn { flex: 1; padding: 12px 8px; background: none; border: none; color: #a0aec0; cursor: pointer;
                font-size: 11px; font-weight: 500; transition: all 0.3s; border-bottom: 3px solid transparent; }
            .tab-btn:hover { background: rgba(255,255,255,0.05); color: white; }
            .tab-btn.active { background: rgba(66,153,225,0.2); color: #4299e1; border-bottom-color: #4299e1; }
            .tab-content { padding: 20px; } .tab-panel { display: none; } .tab-panel.active { display: block; }
            .control-section, .physics-section, .hitbox-colors-section, .automation-section, .info-section { margin-bottom: 20px; }
            h4 { margin: 0 0 15px 0; color: #4299e1; font-size: 14px; font-weight: bold; padding-bottom: 8px;
                border-bottom: 1px solid rgba(66,153,225,0.3); }
            h5 { margin: 15px 0 8px 0; color: #63b3ed; font-size: 13px; }
            .button-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 20px; }
            .action-btn { padding: 12px 16px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;
                font-size: 12px; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px; }
            .action-btn.primary { background: linear-gradient(145deg, #48bb78, #38a169); color: white; }
            .action-btn.secondary { background: linear-gradient(145deg, #718096, #4a5568); color: white; }
            .action-btn.warning { background: linear-gradient(145deg, #ed8936, #dd6b20); color: white; }
            .action-btn.special { background: linear-gradient(145deg, #9f7aea, #805ad5); color: white; }
            .action-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
            .status-display { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px; margin-bottom: 20px; }
            .status-item { display: flex; justify-content: space-between; margin-bottom: 8px; }
            .status-label { color: #a0aec0; font-size: 12px; }
            .status-value { color: #4299e1; font-weight: bold; font-size: 12px; }
            .controls-info { background: rgba(255,255,255,0.03); padding: 15px; border-radius: 8px; }
            .controls-list { display: flex; flex-direction: column; gap: 8px; }
            .control-item { display: flex; justify-content: space-between; align-items: center; }
            .key { background: #4299e1; color: white; padding: 4px 8px; border-radius: 4px; font-size: 10px; font-weight: bold; }
            .desc { color: #a0aec0; font-size: 11px; } .slider-group { margin-bottom: 15px; }
            .slider-group label { display: block; margin-bottom: 8px; color: #e2e8f0; font-size: 12px; font-weight: 500; }
            .slider-group input[type="range"] { width: 100%; height: 6px; border-radius: 3px; background: #4a5568; outline: none; -webkit-appearance: none; }
            .slider-group input[type="range"]::-webkit-slider-thumb { appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #4299e1; cursor: pointer; }
            .preset-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 15px; }
            .preset-btn { padding: 10px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2);
                border-radius: 6px; color: white; cursor: pointer; font-size: 11px; transition: all 0.3s; }
            .preset-btn:hover { background: rgba(255,255,255,0.2); transform: translateY(-1px); }
            .debug-controls { margin-top: 15px; } /* Estilo adicional para los controles de debug */

            /* Estilos para la nueva sección de Hitbox Colors */
            .hitbox-colors-section { padding: 20px; }
            .color-picker-control { margin-bottom: 20px; display: flex; align-items: center; gap: 10px; }
            .color-picker-control label { color: #e2e8f0; font-size: 12px; font-weight: 500; }
            .color-picker-control #selected-hitbox-type-display { color: #4299e1; font-weight: bold; }
            .color-picker-control input[type="color"] { width: 40px; height: 28px; border: none; border-radius: 4px; cursor: pointer; }

            .hitbox-grid {
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
                margin-bottom: 15px;
            }

            .hitbox-item {
                flex: 1 1 calc(50% - 8px); /* Dos columnas, con espacio entre ellas */
                max-width: calc(50% - 8px);
                min-width: 150px;
            }

            .hitbox-item button.select-hitbox-type {
                background: rgba(255,255,255,0.08);
                border: 1px solid rgba(255,255,255,0.1);
                border-radius: 8px;
                color: white;
                cursor: pointer;
                font-size: 11px;
                padding: 8px 10px;
                width: 100%;
                text-align: left;
                transition: all 0.2s ease;
                display: flex;
                align-items: center;
                gap: 8px;
            }

            .hitbox-item button.select-hitbox-type:hover {
                background: rgba(255,255,255,0.15);
                border-color: #4299e1;
            }

            .hitbox-item button.select-hitbox-type.active {
                background: rgba(66,153,225,0.2);
                border-color: #4299e1;
                box-shadow: 0 0 8px rgba(66,153,225,0.4);
            }

            .hitbox-swatch {
                width: 18px;
                height: 18px;
                border-radius: 4px;
                border: 1px solid rgba(255,255,255,0.3);
                display: inline-block;
                flex-shrink: 0;
            }

            .hitbox-name {
                font-weight: bold;
                color: #e2e8f0;
            }
            .hitbox-description {
                font-size: 10px;
                color: #a0aec0;
                margin-left: 8px;
                flex-grow: 1; /* Para que la descripción ocupe el espacio restante */
            }

            .custom-hitbox-name {
                background: rgba(255,255,255,0.1);
                border: 1px solid rgba(255,255,255,0.2);
                border-radius: 4px;
                color: white;
                padding: 4px 6px;
                font-size: 11px;
                flex-grow: 1;
                margin-left: 5px;
                margin-right: 5px;
            }
            .custom-hitbox-name:focus { outline: none; border-color: #4299e1; }

            .remove-hitbox-type {
                background: #e53e3e;
                border: none;
                color: white;
                width: 20px;
                height: 20px;
                border-radius: 50%;
                cursor: pointer;
                font-size: 12px;
                font-weight: bold;
                flex-shrink: 0;
                padding: 0;
                line-height: 1;
            }
            .remove-hitbox-type:hover { background: #c53030; }

            /* Scrollbar */
            #physics-menu ::-webkit-scrollbar { width: 6px; }
            #physics-menu ::-webkit-scrollbar-track { background: rgba(255,255,255,0.1); border-radius: 3px; }
            #physics-menu ::-webkit-scrollbar-thumb { background: #4299e1; border-radius: 3px; }
            #physics-menu ::-webkit-scrollbar-thumb:hover { background: #3182ce; }
        `;
        document.head.appendChild(style);
    }

    // Make the menu draggable
    function makeDraggable(element) {
        let isDragging = false;
        let offsetX, offsetY;

        const header = element.querySelector('.menu-header');

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            const rect = element.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            header.style.cursor = 'grabbing';
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                e.preventDefault();
                let newX = e.clientX - offsetX;
                let newY = e.clientY - offsetY;

                const maxX = window.innerWidth - element.offsetWidth;
                const maxY = window.innerHeight - element.offsetHeight;

                newX = Math.max(0, Math.min(newX, maxX));
                newY = Math.max(0, Math.min(newY, maxY));

                element.style.left = `${newX}px`;
                element.style.top = `${newY}px`;
                element.style.right = 'auto';
                element.style.bottom = 'auto';
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            header.style.cursor = 'grab';
        });
    }

    // Function to ensure the menu stays visible (called periodically)
    function ensureMenuVisibility() {
        const menu = document.getElementById('physics-menu');
        if (menu) {
            menu.style.setProperty('display', 'block', 'important');
            menu.style.setProperty('opacity', '1', 'important');
            menu.style.setProperty('visibility', 'visible', 'important');
            menu.style.setProperty('pointer-events', 'auto', 'important');
        }
    }

    // Initialize the core physics logic and menu functionality
    function initializePhysicsAndMenu() {
        // Attempt to get canvas immediately
        drawariaCanvas = document.getElementById('canvas');
        if (drawariaCanvas && !drawariaCtx) {
            drawariaCtx = drawariaCanvas.getContext('2d');
            console.log('🎨 Drawaria Canvas and Context detected.');
        }

        // Create the Menu Controller instance immediately if it doesn't exist
        if (!menuControllerInstance) {
            menuControllerInstance = new PhysicsMenuController();
            console.log('🎮 PhysicsMenuController instance created. Waiting for manual activation.');
            // Cargar la UI de hitboxes al inicio
            menuControllerInstance.populateHitboxColorsUI();
        }

        // Initialize PhysicsAvatar within the controller if it hasn't been done yet.
        // This will allow the PhysicsAvatar to start detecting the DOM avatar.
        if (menuControllerInstance && !menuControllerInstance.avatar) {
            menuControllerInstance.initPhysicsSystem();
        }
    }

    // --- Entry point ---
    // Create and show the menu structure as soon as the DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', createAndShowMenuContainer);
    } else {
        createAndShowMenuContainer();
    }

    // Start the continuous check for physics and menu functionality activation
    // This will also ensure the menu's visibility persistently
    setInterval(initializePhysicsAndMenu, 500);
    setInterval(ensureMenuVisibility, 500); // Ensure menu visibility periodically

    console.log('🌌 Drawaria Avatar Physics (v6.0) Loader initiated. Menu should be visible. Physics control is MANUAL.');

})();