Drawaria Physics Engine Tennis🥎

Advanced tennis physics with professional Wimbledon court and spin system!

// ==UserScript==
// @name         Drawaria Physics Engine Tennis🥎
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Advanced tennis physics with professional Wimbledon court and spin system!
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    /* ---------- SHARED SYSTEM COMPONENTS ---------- */
    let drawariaSocket = null;
    let drawariaCanvas = null;
    let drawariaCtx = null;

    // Optimized command queue
    const commandQueue = [];
    let batchProcessor = null;
    const BATCH_SIZE = 15;
    const BATCH_INTERVAL = 40;

    const positionCache = new Map();
    const MOVEMENT_THRESHOLD = 2;

    // ✅ COLORES OFICIALES DE TENIS
    const TENNIS_COURT_COLORS = {
        courtColor: '#228B22',      // Verde césped Wimbledon
        lineColor: '#FFFFFF',       // Líneas blancas oficiales
        netColor: '#000000',        // Red negra
        postColor: '#8B4513',       // Postes marrones
        textColor: '#FFFFFF',       // Texto blanco
        clayColor: '#CD853F',       // Arcilla (superficie alternativa)
        hardColor: '#4169E1'        // Cemento azul (superficie alternativa)
    };

    // Tennis physics constants[1][2]
    const TENNIS_PHYSICS = {
        GRAVITY: 300,               // Menor que baloncesto
        BALL_MASS: 0.1,            // Pelota más liviana
        BALL_RADIUS: 15,           // Pelota más pequeña
        TIMESTEP: 1/60,
        MAX_VELOCITY: 800,         // Más rápida que baloncesto
        AIR_RESISTANCE: 0.003,     // Menos resistencia
        RESTITUTION_BALL: 0.75,    // Rebote medio de tenis
        RESTITUTION_NET: 0.1,      // Rebote muy bajo en la red
        RESTITUTION_WALL: 0.6,
        FRICTION_COURT: 0.85,      // Alta fricción en césped
        PLAYER_INTERACTION_FORCE: 300,
        PLAYER_PUSH_MULTIPLIER: 2.2,
        PLAYER_RESTITUTION: 0.95,
        PLAYER_DETECTION_RADIUS_MULTIPLIER: 2.5,

        // Tennis specific
        SERVE_FORCE: 400,
        SPIN_FACTOR: 0.8,
        BALL_COLOR: '#9ACD32',     // Verde lima tenis
        NET_HEIGHT: 40,
        COURT_SURFACE: 'grass'     // grass, clay, hard
    };

    const TENNIS_MATCH = {
        SETS_TO_WIN: 2,
        GAMES_TO_WIN: 6,
        POINTS: [0, 15, 30, 40, 'DEUCE', 'ADV'],
        SERVE_AREAS: {
            left: { x1: 0, y1: 0, x2: 0.5, y2: 0.5 },
            right: { x1: 0.5, y1: 0, x2: 1, y2: 0.5 }
        }
    };

    let isDrawing = false;
    let isStopped = false;

    // WebSocket interception[3]
    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 for tennis engine.');
            startBatchProcessor();
        }
        return originalWebSocketSend.apply(this, args);
    };

    function startBatchProcessor() {
        if (batchProcessor) return;
        batchProcessor = setInterval(() => {
            if (!drawariaSocket || drawariaSocket.readyState !== WebSocket.OPEN || commandQueue.length === 0) {
                return;
            }
            const batch = commandQueue.splice(0, BATCH_SIZE);
            batch.forEach(cmd => {
                try { drawariaSocket.send(cmd); } catch (e) { console.warn('Failed to send command:', e); }
            });
        }, BATCH_INTERVAL);
    }

    function enqueueDrawCommand(x1, y1, x2, y2, color, thickness) {
        if (!drawariaCanvas || !drawariaSocket) 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 cmd = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`;
        commandQueue.push(cmd);

        if (drawariaCtx) {
            drawariaCtx.strokeStyle = color;
            drawariaCtx.lineWidth = thickness;
            drawariaCtx.lineCap = 'round';
            drawariaCtx.lineJoin = 'round';
            drawariaCtx.beginPath();
            drawariaCtx.moveTo(x1, y1);
            drawariaCtx.lineTo(x2, y2);
            drawariaCtx.stroke();
        }
    }

    // Helper functions
    function clamp(value, min, max) { return Math.min(Math.max(value, min), max); }
    function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

    // ✅ SISTEMA DE COORDENADAS DE TENIS PROFESIONAL
    function getCanvasSize() {
        return {
            width: drawariaCanvas.width,
            height: drawariaCanvas.height
        };
    }

    function calculateTennisCoordinates() {
        const size = getCanvasSize();

        const coords = {
            // Dimensiones oficiales de cancha de tenis (proporción 2:1)
            court: {
                x: Math.floor(size.width * 0.1),
                y: Math.floor(size.height * 0.1),
                width: Math.floor(size.width * 0.8),
                height: Math.floor(size.height * 0.8)
            },

            // Red central
            net: {
                x: Math.floor(size.width * 0.5),
                y1: Math.floor(size.height * 0.1),
                y2: Math.floor(size.height * 0.9),
                height: TENNIS_PHYSICS.NET_HEIGHT,
                postHeight: 50
            },

            // Líneas de servicio
            serviceLines: {
                leftService: Math.floor(size.width * 0.35),
                rightService: Math.floor(size.width * 0.65),
                topService: Math.floor(size.height * 0.3),
                bottomService: Math.floor(size.height * 0.7)
            },

            // Líneas laterales (singles y doubles)
            sideLines: {
                leftSingles: Math.floor(size.width * 0.15),
                rightSingles: Math.floor(size.width * 0.85),
                leftDoubles: Math.floor(size.width * 0.1),
                rightDoubles: Math.floor(size.width * 0.9)
            },

            // Líneas de fondo
            baseLines: {
                top: Math.floor(size.height * 0.1),
                bottom: Math.floor(size.height * 0.9)
            },

            // Texto
            text: {
                x: Math.floor(size.width * 0.5),
                y: Math.floor(size.height * 0.05),
                pixelSize: Math.max(2, Math.floor(size.width * 0.004))
            }
        };

        return coords;
    }

    function sendDrawCommand(x, y, x2, y2, color, thickness) {
        if (!drawariaSocket || !drawariaCanvas) return;

        const normX = (x / drawariaCanvas.width).toFixed(4);
        const normY = (y / drawariaCanvas.height).toFixed(4);
        const normX2 = (x2 / drawariaCanvas.width).toFixed(4);
        const normY2 = (y2 / drawariaCanvas.height).toFixed(4);

        const command = `42["drawcmd",0,[${normX},${normY},${normX2},${normY2},false,${0 - thickness},"${color}",0,0,{}]]`;
        drawariaSocket.send(command);
    }

    async function drawLineLocalAndServer(startX, startY, endX, endY, color, thickness, delay = 50) {
        if (isStopped) {
            isDrawing = false;
            return;
        }

        const canvasSize = getCanvasSize();
        startX = clamp(startX, -50, canvasSize.width + 50);
        startY = clamp(startY, 0, canvasSize.height);
        endX = clamp(endX, -50, canvasSize.width + 50);
        endY = clamp(endY, 0, canvasSize.height);

        if (drawariaCtx && startX >= 0 && startX <= canvasSize.width && startY >= 0 && startY <= canvasSize.height) {
            drawariaCtx.strokeStyle = color;
            drawariaCtx.lineWidth = thickness;
            drawariaCtx.lineCap = 'round';
            drawariaCtx.lineJoin = 'round';
            drawariaCtx.beginPath();
            drawariaCtx.moveTo(startX, startY);
            drawariaCtx.lineTo(endX, endY);
            drawariaCtx.stroke();
        }

        sendDrawCommand(startX, startY, endX, endY, color, thickness);
        await sleep(delay);
    }

    async function drawPixel(x, y, color, size = 2) {
        if (isStopped) return;

        const canvasSize = getCanvasSize();
        x = clamp(x, 0, canvasSize.width - size);
        y = clamp(y, 0, canvasSize.height - size);

        if (drawariaCtx) {
            drawariaCtx.fillStyle = color;
            drawariaCtx.fillRect(x, y, size, size);
        }

        sendDrawCommand(x, y, x + 1, y + 1, color, size);
        await sleep(15);
    }

    // ✅ FUNCIONES DE DIBUJO DE CANCHA DE TENIS
    async function drawTennisCourtSurface() {
        if (isStopped) return;

        updateStatus(document.getElementById('tennis-status'), "🎾 Dibujando superficie de césped Wimbledon...", TENNIS_COURT_COLORS.courtColor);

        const canvasSize = getCanvasSize();
        const coords = calculateTennisCoordinates();

        // Superficie principal de césped
        for (let y = coords.court.y; y < coords.court.y + coords.court.height; y += 8) {
            await drawLineLocalAndServer(coords.court.x, y, coords.court.x + coords.court.width, y, TENNIS_COURT_COLORS.courtColor, 2, 25);
            if (isStopped) break;
        }

        // Líneas de textura para simular césped
        for (let y = coords.court.y + 10; y < coords.court.y + coords.court.height - 10; y += 20) {
            for (let x = coords.court.x + 10; x < coords.court.x + coords.court.width - 10; x += 30) {
                await drawLineLocalAndServer(x, y, x + 15, y, '#32CD32', 1, 10);
                if (isStopped) break;
            }
            if (isStopped) break;
        }
    }

    async function drawTennisCourtLines(coords) {
        if (isStopped) return;

        updateStatus(document.getElementById('tennis-status'), "⚪ Dibujando líneas oficiales de tenis...", TENNIS_COURT_COLORS.lineColor);

        const lineThickness = Math.max(3, Math.floor(drawariaCanvas.width * 0.006));

        // Perímetro exterior (doubles)
        await drawRectangleOutline({
            x: coords.sideLines.leftDoubles,
            y: coords.baseLines.top,
            width: coords.sideLines.rightDoubles - coords.sideLines.leftDoubles,
            height: coords.baseLines.bottom - coords.baseLines.top
        }, TENNIS_COURT_COLORS.lineColor, lineThickness);

        // Líneas laterales singles
        await drawLineLocalAndServer(
            coords.sideLines.leftSingles, coords.baseLines.top,
            coords.sideLines.leftSingles, coords.baseLines.bottom,
            TENNIS_COURT_COLORS.lineColor, lineThickness, 60
        );

        await drawLineLocalAndServer(
            coords.sideLines.rightSingles, coords.baseLines.top,
            coords.sideLines.rightSingles, coords.baseLines.bottom,
            TENNIS_COURT_COLORS.lineColor, lineThickness, 60
        );

        // Líneas de servicio horizontales
        await drawLineLocalAndServer(
            coords.sideLines.leftSingles, coords.serviceLines.topService,
            coords.sideLines.rightSingles, coords.serviceLines.topService,
            TENNIS_COURT_COLORS.lineColor, lineThickness, 70
        );

        await drawLineLocalAndServer(
            coords.sideLines.leftSingles, coords.serviceLines.bottomService,
            coords.sideLines.rightSingles, coords.serviceLines.bottomService,
            TENNIS_COURT_COLORS.lineColor, lineThickness, 70
        );

        // Línea central de servicio
        await drawLineLocalAndServer(
            coords.net.x, coords.serviceLines.topService,
            coords.net.x, coords.serviceLines.bottomService,
            TENNIS_COURT_COLORS.lineColor, lineThickness, 80
        );
    }

    async function drawTennisNet(coords) {
        if (isStopped) return;

        updateStatus(document.getElementById('tennis-status'), "🕸️ Instalando red de tenis...", TENNIS_COURT_COLORS.netColor);

        // Postes de la red
        const postWidth = 8;
        const postHeight = coords.net.postHeight;

        // Poste izquierdo
        await drawRectangleOutline({
            x: coords.net.x - postWidth/2,
            y: coords.net.y1 - postHeight,
            width: postWidth,
            height: postHeight
        }, TENNIS_COURT_COLORS.postColor, 3);

        // Poste derecho
        await drawRectangleOutline({
            x: coords.net.x - postWidth/2,
            y: coords.net.y2,
            width: postWidth,
            height: postHeight
        }, TENNIS_COURT_COLORS.postColor, 3);

        // Red central (líneas verticales)
        const netDensity = 12;
        for (let i = 0; i < netDensity; i++) {
            const netY = coords.net.y1 + (i * (coords.net.y2 - coords.net.y1) / netDensity);
            await drawLineLocalAndServer(
                coords.net.x, netY,
                coords.net.x, netY + (coords.net.y2 - coords.net.y1) / netDensity,
                TENNIS_COURT_COLORS.netColor, 2, 30
            );
            if (isStopped) break;
        }

        // Líneas horizontales de la red
        const horizontalNetLines = 8;
        for (let i = 0; i < horizontalNetLines; i++) {
            const netX = coords.net.x - 2 + (i * 0.5);
            await drawLineLocalAndServer(
                netX, coords.net.y1,
                netX, coords.net.y2,
                TENNIS_COURT_COLORS.netColor, 1, 25
            );
            if (isStopped) break;
        }
    }

    // ✅ FUNCIONES GEOMÉTRICAS
    async function drawRectangleOutline(rectCoords, color, thickness) {
        await drawLineLocalAndServer(rectCoords.x, rectCoords.y,
            rectCoords.x + rectCoords.width, rectCoords.y, color, thickness, 40);
        await drawLineLocalAndServer(rectCoords.x + rectCoords.width, rectCoords.y,
            rectCoords.x + rectCoords.width, rectCoords.y + rectCoords.height, color, thickness, 40);
        await drawLineLocalAndServer(rectCoords.x + rectCoords.width, rectCoords.y + rectCoords.height,
            rectCoords.x, rectCoords.y + rectCoords.height, color, thickness, 40);
        await drawLineLocalAndServer(rectCoords.x, rectCoords.y + rectCoords.height,
            rectCoords.x, rectCoords.y, color, thickness, 40);
    }

    // ✅ TEXTO TENNIS EN PIXEL ART
    const TENNIS_LETTERS = {
        'T': [[1,1,1,1,1],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0]],
        'E': [[1,1,1,1],[1,0,0,0],[1,1,1,0],[1,0,0,0],[1,1,1,1]],
        'N': [[1,0,0,1],[1,1,0,1],[1,0,1,1],[1,0,0,1],[1,0,0,1]],
        'I': [[1,1,1],[0,1,0],[0,1,0],[0,1,0],[1,1,1]],
        'S': [[1,1,1,1],[1,0,0,0],[1,1,1,1],[0,0,0,1],[1,1,1,1]]
    };

    async function drawTennisPixelText(text, coords) {
        if (isStopped) return;

        const letterSpacing = coords.text.pixelSize * 6;
        const textWidth = text.length * letterSpacing;
        let currentX = coords.text.x - (textWidth / 2);

        for (let i = 0; i < text.length; i++) {
            if (isStopped) break;

            const letter = text[i].toUpperCase();
            if (letter === ' ') {
                currentX += letterSpacing;
                continue;
            }

            const pattern = TENNIS_LETTERS[letter];
            if (!pattern) continue;

            for (let row = 0; row < pattern.length; row++) {
                for (let col = 0; col < pattern[row].length; col++) {
                    if (pattern[row][col] === 1) {
                        const pixelX = currentX + (col * coords.text.pixelSize);
                        const pixelY = coords.text.y + (row * coords.text.pixelSize);

                        const canvasSize = getCanvasSize();
                        if (pixelX >= 0 && pixelX < canvasSize.width && pixelY >= 0 && pixelY < canvasSize.height) {
                            await drawPixel(pixelX, pixelY, TENNIS_COURT_COLORS.textColor, coords.text.pixelSize);
                        }
                    }
                }
            }

            currentX += letterSpacing;
            await sleep(100);
        }
    }

    // ✅ FUNCIÓN PRINCIPAL: CANCHA DE TENIS COMPLETA
    async function drawCompleteTennisCourt() {
        if (isDrawing) {
            alert('Ya está en curso un dibujo. Presiona "Parar" para cancelar.');
            return;
        }

        if (!drawariaSocket || !drawariaCanvas || !drawariaCtx) {
            alert('No se detectó conexión o canvas. Asegúrate de estar en una sala de juego.');
            return;
        }

        isDrawing = true;
        isStopped = false;
        const statusDiv = document.getElementById('tennis-status') || createStatusDiv();

        try {
            const coords = calculateTennisCoordinates();
            const canvasSize = getCanvasSize();

            console.log(`🎾 Cancha de tenis Wimbledon iniciada:`);
            console.log(`📏 Canvas: ${canvasSize.width}x${canvasSize.height}`);

            updateStatus(statusDiv, `🎾 CANCHA DE TENIS WIMBLEDON: ${canvasSize.width}x${canvasSize.height}`, "#228B22");
            await sleep(800);

            // FASE 1: SUPERFICIE DE CÉSPED
            updateStatus(statusDiv, "🎾 FASE 1: Superficie de césped Wimbledon...", TENNIS_COURT_COLORS.courtColor);
            await drawTennisCourtSurface();
            await sleep(300);
            if (isStopped) return;

            // FASE 2: LÍNEAS OFICIALES
            updateStatus(statusDiv, "⚪ FASE 2: Líneas oficiales de tenis...", TENNIS_COURT_COLORS.lineColor);
            await drawTennisCourtLines(coords);
            await sleep(300);
            if (isStopped) return;

            // FASE 3: RED Y POSTES
            updateStatus(statusDiv, "🕸️ FASE 3: Instalando red de tenis...", TENNIS_COURT_COLORS.netColor);
            await drawTennisNet(coords);
            await sleep(300);
            if (isStopped) return;

            // FASE 4: TEXTO TENNIS
            updateStatus(statusDiv, "🎮 FASE 4: Texto blanco 'TENNIS'...", TENNIS_COURT_COLORS.textColor);
            await drawTennisPixelText("TENNIS", coords);

            // CANCHA COMPLETA
            updateStatus(statusDiv, "🏆 ¡CANCHA DE TENIS WIMBLEDON COMPLETA! 🎾🏆", "#006400");

            setTimeout(() => {
                if (statusDiv && statusDiv.parentNode) {
                    statusDiv.style.opacity = 0;
                    setTimeout(() => statusDiv.remove(), 500);
                }
            }, 4000);

        } catch (error) {
            console.error("Error en cancha de tenis:", error);
            updateStatus(statusDiv, `❌ Error: ${error.message}`, "#B22222");
        } finally {
            isDrawing = false;
        }
    }

    function createStatusDiv() {
        const statusDiv = document.createElement('div');
        statusDiv.id = 'tennis-status';
        statusDiv.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: linear-gradient(135deg, #228B22 0%, #32CD32 100%);
            color: white;
            padding: 20px 45px;
            border-radius: 35px;
            font-weight: bold;
            z-index: 10000;
            transition: opacity 0.5s;
            text-align: center;
            min-width: 480px;
            box-shadow: 0 15px 35px rgba(0,0,0,0.5);
            text-shadow: 1px 1px 3px rgba(0,0,0,0.4);
            border: 2px solid #FFFFFF;
        `;
        document.body.appendChild(statusDiv);
        return statusDiv;
    }

    function updateStatus(statusDiv, message, color) {
        if (!statusDiv) return;
        statusDiv.textContent = message;
        if (color) {
            statusDiv.style.background = color;
        }
        statusDiv.style.opacity = 1;
    }

    /* ---------- ADVANCED TENNIS PHYSICS ENGINE ---------- */
    class AdvancedDrawariaTennis {
        constructor() {
            this.initialized = false;
            this.isActive = false;
            this.physicsObjects = new Map();
            this.objectIdCounter = 0;
            this.lastRenderTime = 0;
            this.renderInterval = 1000 / 30;

            // Sistema de raquetas virtuales[4][5]
            this.racketSystem = {
                playerRackets: new Map(),
                racketLength: 60,
                racketWidth: 8,
                hitCooldown: 300
            };

            // Tennis match scoring
            this.tennisMatch = {
                active: false,
                scores: { p1: { sets: 0, games: 0, points: 0 }, p2: { sets: 0, games: 0, points: 0 } },
                serving: 'p1',
                courtSurface: 'grass',
                lastServeTime: 0
            };

            this.gameStats = {
                totalHits: 0,
                maxVelocityReached: 0,
                ballsCreated: 0,
                totalAces: 0,
                netHits: 0
            };

            this.controls = {
                showDebug: false,
                defaultBallColor: TENNIS_PHYSICS.BALL_COLOR,
                courtSurface: 'grass'
            };

            this.playerTracker = {
                players: new Map(),
                detectionRadius: TENNIS_PHYSICS.BALL_RADIUS * TENNIS_PHYSICS.PLAYER_DETECTION_RADIUS_MULTIPLIER,
                lastUpdateTime: 0
            };

            this.init();
        }

        init() {
            if (this.initialized) return;

            const checkGameReady = () => {
                const gameCanvas = document.getElementById('canvas');
                if (gameCanvas) {
                    this.canvasElement = gameCanvas;
                    drawariaCanvas = gameCanvas;
                    this.canvasContext = gameCanvas.getContext('2d');
                    drawariaCtx = gameCanvas.getContext('2d');

                    this.initialized = true;
                    this.createTennisPanel();
                    console.log('✅ Advanced Tennis Physics Engine v1.0 initialized');
                } else {
                    setTimeout(checkGameReady, 100);
                }
            };
            checkGameReady();
        }

        createTennisPanel() {
            const existingPanel = document.getElementById('tennis-physics-panel');
            if (existingPanel) existingPanel.remove();

            const panel = document.createElement('div');
            panel.id = 'tennis-physics-panel';
            panel.style.cssText = `
                position: fixed !important;
                top: 20px !important;
                right: 20px !important;
                width: 380px !important;
                z-index: 2147483647 !important;
                background: linear-gradient(135deg, #0f2f0f, #1a4a1a) !important;
                border: 2px solid #32CD32 !important;
                border-radius: 15px !important;
                color: white !important;
                font-family: 'Segoe UI', Arial, sans-serif !important;
                overflow: hidden !important;
                box-shadow: 0 0 30px rgba(50,205,50,0.4) !important;
            `;

            const header = document.createElement('div');
            header.id = 'tennis-panel-header';
            header.style.cssText = `
                background: linear-gradient(45deg, #228B22, #32CD32) !important;
                padding: 12px 20px !important;
                font-weight: bold !important;
                text-align: center !important;
                font-size: 14px !important;
                cursor: move !important;
                user-select: none !important;
                display: flex !important;
                justify-content: space-between !important;
                align-items: center !important;
            `;

            const title = document.createElement('div');
            title.innerHTML = '🎾 WIMBLEDON TENNIS ENGINE v1.0';
            title.style.flex = '1';

            const buttonContainer = document.createElement('div');
            buttonContainer.style.cssText = `display: flex !important; gap: 8px !important;`;

            const minimizeBtn = document.createElement('button');
            minimizeBtn.id = 'tennis-minimize-btn';
            minimizeBtn.innerHTML = '−';
            minimizeBtn.style.cssText = `
                width: 25px !important; height: 25px !important;
                background: rgba(255,255,255,0.2) !important;
                border: none !important; border-radius: 4px !important;
                color: white !important; cursor: pointer !important;
                font-size: 16px !important; line-height: 1 !important; padding: 0 !important;
            `;

            const closeBtn = document.createElement('button');
            closeBtn.id = 'tennis-close-btn';
            closeBtn.innerHTML = '×';
            closeBtn.style.cssText = `
                width: 25px !important; height: 25px !important;
                background: rgba(255,0,0,0.6) !important;
                border: none !important; border-radius: 4px !important;
                color: white !important; cursor: pointer !important;
                font-size: 18px !important; line-height: 1 !important; padding: 0 !important;
            `;

            buttonContainer.appendChild(minimizeBtn);
            buttonContainer.appendChild(closeBtn);
            header.appendChild(title);
            header.appendChild(buttonContainer);

            const content = document.createElement('div');
            content.id = 'tennis-panel-content';
            content.style.cssText = `padding: 20px !important;`;
            content.innerHTML = `
                <!-- CREATE TENNIS COURT -->
                <div style="margin-bottom: 15px; text-align: center;">
                    <button id="create-tennis-court-btn" style="
                        width: 100%;
                        padding: 12px;
                        background: linear-gradient(135deg, #228B22, #32CD32);
                        color: white;
                        border: none;
                        border-radius: 8px;
                        cursor: pointer;
                        font-size: 14px;
                        font-weight: bold;
                        margin-bottom: 10px;
                        box-shadow: 0 4px 15px rgba(34,139,34,0.3);
                    ">🎾 Create Wimbledon Tennis Court</button>
                </div>

                <!-- LAUNCH TENNIS ENGINE -->
                <div style="margin-bottom: 15px; text-align: center;">
                    <button id="toggle-tennis-physics" style="
                        width: 100%;
                        padding: 12px;
                        background: linear-gradient(135deg, #32CD32, #9ACD32);
                        color: white;
                        border: none;
                        border-radius: 8px;
                        cursor: pointer;
                        font-size: 14px;
                        font-weight: bold;
                    ">🚀 Launch Tennis Engine</button>
                </div>

                <!-- TENNIS BALL CREATION -->
                <div style="display: flex; gap: 10px; margin-bottom: 15px;">
                    <button id="add-tennis-ball-btn" style="
                        flex: 1;
                        padding: 8px;
                        background: linear-gradient(135deg, #9ACD32, #32CD32);
                        color: white;
                        border: none;
                        border-radius: 6px;
                        cursor: pointer;
                        font-size: 12px;
                        font-weight: bold;
                    ">🎾 Add Tennis Ball</button>
                    <button id="serve-ball-btn" style="
                        flex: 1;
                        padding: 8px;
                        background: linear-gradient(135deg, #FFD700, #FFA500);
                        color: white;
                        border: none;
                        border-radius: 6px;
                        cursor: pointer;
                        font-size: 12px;
                        font-weight: bold;
                    ">🏆 Serve</button>
                </div>

                <!-- COURT SURFACE SELECTION -->
                <div style="margin-bottom: 15px;">
                    <label style="display: block; margin-bottom: 5px; font-size: 12px; color: #32CD32;">
                        🏟️ Court Surface:
                    </label>
                    <select id="court-surface" style="
                        width: 100%;
                        padding: 8px;
                        border: 1px solid #32CD32;
                        border-radius: 6px;
                        background: #1a4a1a;
                        color: white;
                        font-size: 12px;
                    ">
                        <option value="grass">🌱 Grass (Wimbledon)</option>
                        <option value="clay">🧱 Clay (Roland Garros)</option>
                        <option value="hard">🏢 Hard Court (US Open)</option>
                    </select>
                </div>

                <!-- ACTION BUTTONS -->
                <div style="display: flex; gap: 8px; margin-bottom: 15px;">
                    <button id="reset-tennis-btn" style="
                        flex: 1;
                        padding: 8px;
                        background: linear-gradient(135deg, #74b9ff, #0984e3);
                        color: white;
                        border: none;
                        border-radius: 6px;
                        cursor: pointer;
                        font-weight: bold;
                        font-size: 11px;
                    ">🔄 Reset</button>
                    <button id="stop-tennis-court-btn" style="
                        flex: 1;
                        padding: 8px;
                        background: linear-gradient(135deg, #e74c3c, #c0392b);
                        color: white;
                        border: none;
                        border-radius: 6px;
                        cursor: pointer;
                        font-weight: bold;
                        font-size: 11px;
                    ">⛔ Stop Court</button>
                </div>

                <!-- TENNIS MODES -->
                <div style="margin-bottom: 15px;">
                    <h4 style="margin: 0 0 10px 0; font-size: 13px; color: #32CD32; text-align: center;">🌟 Tennis Modes</h4>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                        <button id="match-mode-toggle" class="tennis-mode-toggle" style="
                            padding: 8px;
                            background: linear-gradient(135deg, #444, #666);
                            color: white;
                            border: none;
                            border-radius: 6px;
                            cursor: pointer;
                            font-size: 10px;
                            font-weight: bold;
                        ">🏆 Match Mode</button>
                        <button id="clean-tennis-canvas-btn" style="
                            padding: 8px;
                            background: linear-gradient(135deg, #e17055, #d63031);
                            color: white;
                            border: none;
                            border-radius: 6px;
                            cursor: pointer;
                            font-size: 10px;
                            font-weight: bold;
                        ">🧹 Clean Court</button>
                    </div>
                </div>

                <!-- CLEAR ALL -->
                <div style="margin-bottom: 15px;">
                    <button id="clear-tennis-balls-btn" style="
                        width: 100%;
                        padding: 10px;
                        background: linear-gradient(135deg, #990000, #cc0000);
                        color: white;
                        border: none;
                        border-radius: 8px;
                        cursor: pointer;
                        font-weight: bold;
                    ">🗑️ Clear All Tennis Balls</button>
                </div>

                <!-- TENNIS SCOREBOARD -->
                <div id="tennis-scoreboard" style="
                    display: none;
                    background: rgba(0,0,0,0.4);
                    padding: 15px;
                    border-radius: 8px;
                    text-align: center;
                    margin-bottom: 15px;
                    border: 2px solid #FFD700;
                ">
                    <h4 style="margin: 0 0 10px 0; color: #FFD700; font-size: 14px;">🎾 WIMBLEDON SCORE</h4>
                    <div style="display: flex; justify-content: space-between; font-size: 14px; font-weight: bold;">
                        <div style="color: #ff6b6b;">
                            P1: <span id="tennis-score-p1-sets">0</span>-<span id="tennis-score-p1-games">0</span>
                            <br><span id="tennis-score-p1-points" style="font-size: 18px;">0</span>
                        </div>
                        <div style="color: #666; font-size: 12px;">vs</div>
                        <div style="color: #74b9ff;">
                            P2: <span id="tennis-score-p2-sets">0</span>-<span id="tennis-score-p2-games">0</span>
                            <br><span id="tennis-score-p2-points" style="font-size: 18px;">0</span>
                        </div>
                    </div>
                    <div style="margin-top: 10px; font-size: 11px; color: #FFD700;">
                        Serving: <span id="serving-player">P1</span>
                    </div>
                </div>

                <!-- TENNIS STATS -->
                <div id="tennis-stats" style="
                    background: rgba(0,0,0,0.3);
                    padding: 10px;
                    border-radius: 6px;
                    font-size: 10px;
                    text-align: center;
                    border: 1px solid rgba(50,205,50,0.3);
                ">
                    <div>Tennis Balls: <span id="tennis-ball-count">0</span> | Hits: <span id="hits-count">0</span></div>
                    <div>Aces: <span id="aces-count">0</span> | Net Hits: <span id="net-hits-count">0</span></div>
                    <div>Max Speed: <span id="tennis-max-speed">0</span> km/h</div>
                    <div>Surface: <span id="surface-info">Grass</span></div>
                </div>

                <!-- HELP TEXT -->
                <div style="
                    text-align: center;
                    margin-top: 15px;
                    font-size: 9px;
                    color: rgba(255,255,255,0.6);
                    border-top: 1px solid rgba(255,255,255,0.1);
                    padding-top: 10px;
                ">
                    Professional Wimbledon Court • Tennis Physics<br>
                    <span style="color: #32CD32;">Spin effects • Net detection • Court surfaces</span>
                </div>
            `;

            panel.appendChild(header);
            panel.appendChild(content);
            document.body.appendChild(panel);

            this.makeTennisPanelDraggable();
            this.setupTennisPanelButtons();
            this.setupTennisEventListeners();
            this.startTennisStatsMonitoring();
        }

        setupTennisEventListeners() {
            // Tennis court controls
            document.getElementById('create-tennis-court-btn')?.addEventListener('click', () => drawCompleteTennisCourt());
            document.getElementById('toggle-tennis-physics')?.addEventListener('click', () => this.toggleTennisPhysics());
            document.getElementById('stop-tennis-court-btn')?.addEventListener('click', () => this.stopTennisCourtDrawing());

            // Tennis ball creation
            document.getElementById('add-tennis-ball-btn')?.addEventListener('click', () => this.addRandomTennisBall());
            document.getElementById('serve-ball-btn')?.addEventListener('click', () => this.serveTennisBall());

            // Actions
            document.getElementById('reset-tennis-btn')?.addEventListener('click', () => this.resetAllTennisBalls());
            document.getElementById('clear-tennis-balls-btn')?.addEventListener('click', () => this.clearAllTennisBalls());
            document.getElementById('match-mode-toggle')?.addEventListener('click', () => this.toggleTennisMatch());
            document.getElementById('clean-tennis-canvas-btn')?.addEventListener('click', () => this.cleanTennisCourt());

            // Court surface
            document.getElementById('court-surface')?.addEventListener('change', (e) => {
                this.controls.courtSurface = e.target.value;
                this.updateSurfacePhysics(e.target.value);
                this.showTennisFeedback(`🏟️ Court Surface: ${e.target.options[e.target.selectedIndex].text}`, '#32CD32');
            });

            // Canvas click for tennis ball
            if (this.canvasElement) {
                this.canvasElement.addEventListener('click', (e) => this.createTennisBall(e.clientX - this.canvasElement.getBoundingClientRect().left, e.clientY - this.canvasElement.getBoundingClientRect().top));
            }
        }

        stopTennisCourtDrawing() {
            isStopped = true;
            const statusDiv = document.getElementById('tennis-status');
            if (statusDiv) {
                updateStatus(statusDiv, "⛔ Dibujo de cancha detenido", "#B22222");
            }
            this.showTennisFeedback('⛔ Tennis court drawing stopped', '#B22222');
        }

        /* ---------- TENNIS PHYSICS ENGINE ---------- */
        toggleTennisPhysics() {
            const toggleBtn = document.getElementById('toggle-tennis-physics');
            if (!this.isActive) {
                this.startTennisPhysics();
                if (toggleBtn) {
                    toggleBtn.textContent = '🛑 Stop Tennis Engine';
                    toggleBtn.style.background = 'linear-gradient(135deg, #f56565, #e53e3e)';
                }
            } else {
                this.stopTennisPhysics();
                if (toggleBtn) {
                    toggleBtn.textContent = '🚀 Launch Tennis Engine';
                    toggleBtn.style.background = 'linear-gradient(135deg, #32CD32, #9ACD32)';
                }
            }
        }

        startTennisPhysics() {
            if (this.isActive) return;
            this.isActive = true;
            this.startTennisGameLoop();
            this.showTennisFeedback('🚀 Wimbledon Tennis Engine Started!', '#32CD32');
        }

        stopTennisPhysics() {
            this.isActive = false;
            this.showTennisFeedback('🛑 Tennis Engine Stopped', '#f56565');
        }

        startTennisGameLoop() {
            if (!this.isActive) return;
            const currentTime = performance.now();
            if (currentTime - this.lastRenderTime >= this.renderInterval) {
                this.updateTennisPhysics();
                this.renderTennisBalls();
                this.lastRenderTime = currentTime;
            }
            requestAnimationFrame(() => this.startTennisGameLoop());
        }

        updateTennisPhysics() {
            const dt = TENNIS_PHYSICS.TIMESTEP;

            // Surface-specific physics adjustments
            let gravityMultiplier = 1;
            let frictionMultiplier = 1;
            let bounceMultiplier = 1;

            switch(this.controls.courtSurface) {
                case 'clay':
                    frictionMultiplier = 1.5;
                    bounceMultiplier = 0.8;
                    break;
                case 'hard':
                    frictionMultiplier = 0.8;
                    bounceMultiplier = 1.1;
                    break;
                case 'grass':
                default:
                    frictionMultiplier = 1.0;
                    bounceMultiplier = 0.9;
                    break;
            }

            // Update tennis balls
            this.physicsObjects.forEach(ball => {
                if (ball.type !== 'tennis') return;

                // Apply air resistance
                ball.vx *= (1 - TENNIS_PHYSICS.AIR_RESISTANCE * dt);
                ball.vy *= (1 - TENNIS_PHYSICS.AIR_RESISTANCE * dt);

                // Apply gravity
                ball.vy += TENNIS_PHYSICS.GRAVITY * gravityMultiplier * dt;

                // Apply spin effects (Magnus effect)
                if (ball.spin) {
                    const spinForce = ball.spin * TENNIS_PHYSICS.SPIN_FACTOR;
                    ball.vx += spinForce * dt;
                    ball.vy += spinForce * 0.5 * dt;
                    ball.spin *= 0.98; // Spin decay
                }

                // Update position
                ball.x += ball.vx * dt;
                ball.y += ball.vy * dt;

                this.handleTennisBoundaryCollisions(ball, frictionMultiplier, bounceMultiplier);
                this.handleTennisNetCollision(ball);

                // Velocity limiting
                const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
                if (speed > this.gameStats.maxVelocityReached) {
                    this.gameStats.maxVelocityReached = speed;
                }

                if (speed > TENNIS_PHYSICS.MAX_VELOCITY) {
                    ball.vx = (ball.vx / speed) * TENNIS_PHYSICS.MAX_VELOCITY;
                    ball.vy = (ball.vy / speed) * TENNIS_PHYSICS.MAX_VELOCITY;
                }
            });

            this.handleTennisBallCollisions();
            this.handleTennisPlayerCollisions();

            if (this.tennisMatch.active) {
                this.checkTennisScoring();
            }
        }

        updateSurfacePhysics(surface) {
            this.controls.courtSurface = surface;

            // Update existing balls' physics
            this.physicsObjects.forEach(ball => {
                if (ball.type !== 'tennis') return;

                switch(surface) {
                    case 'clay':
                        ball.friction = TENNIS_PHYSICS.FRICTION_COURT * 1.5;
                        ball.restitution = TENNIS_PHYSICS.RESTITUTION_BALL * 0.8;
                        break;
                    case 'hard':
                        ball.friction = TENNIS_PHYSICS.FRICTION_COURT * 0.8;
                        ball.restitution = TENNIS_PHYSICS.RESTITUTION_BALL * 1.1;
                        break;
                    case 'grass':
                    default:
                        ball.friction = TENNIS_PHYSICS.FRICTION_COURT;
                        ball.restitution = TENNIS_PHYSICS.RESTITUTION_BALL;
                        break;
                }
            });
        }

        /* ---------- TENNIS BALL CREATION ---------- */
        addRandomTennisBall() {
            if (!this.canvasElement) return;

            const padding = 80;
                        const x = Math.random() * (this.canvasElement.width - 2 * padding) + padding;
            const y = Math.random() * (this.canvasElement.height * 0.3 - 2 * padding) + padding;

            this.createTennisBall(x, y);
        }

        serveTennisBall() {
            if (!this.canvasElement) return;

            const coords = calculateTennisCoordinates();

            // Serve desde línea de fondo
            const isP1Serving = this.tennisMatch.serving === 'p1';
            const serveX = isP1Serving ? coords.court.x + 50 : coords.court.x + coords.court.width - 50;
            const serveY = this.canvasElement.height * 0.85;

            const ball = this.createTennisBall(serveX, serveY);

            // Aplicar fuerza de saque
            const serveDirection = isP1Serving ? 1 : -1;
            ball.vx = TENNIS_PHYSICS.SERVE_FORCE * serveDirection * 0.6;
            ball.vy = -TENNIS_PHYSICS.SERVE_FORCE * 0.8;
            ball.spin = Math.random() * 20 - 10; // Spin aleatorio

            this.showTennisFeedback(`🎾 ${this.tennisMatch.serving.toUpperCase()} SERVES!`, '#FFD700');
        }

        createTennisBall(x, y) {
            const id = `tennis_${this.objectIdCounter++}`;
            const ball = {
                id: id,
                type: 'tennis',
                x: x, y: y, vx: 0, vy: 0,
                radius: TENNIS_PHYSICS.BALL_RADIUS,
                color: TENNIS_PHYSICS.BALL_COLOR,
                mass: TENNIS_PHYSICS.BALL_MASS,
                restitution: TENNIS_PHYSICS.RESTITUTION_BALL,
                friction: TENNIS_PHYSICS.FRICTION_COURT,
                lastRenderX: -9999, lastRenderY: -9999,
                creationTime: performance.now(),
                lastCollisionTime: 0,

                // Tennis specific properties
                spin: 0,
                lastBounceTime: 0,
                bounceCount: 0,
                lastHitTime: 0,
                isInPlay: true
            };

            this.physicsObjects.set(id, ball);
            this.gameStats.ballsCreated++;
            return ball;
        }

        /* ---------- TENNIS COLLISION HANDLING ---------- */
        handleTennisBoundaryCollisions(ball, frictionMultiplier, bounceMultiplier) {
            if (!this.canvasElement) return;

            const ballHalfSize = ball.radius;
            const coords = calculateTennisCoordinates();

            // Límites de la cancha
            const boundaries = {
                left: coords.court.x + ballHalfSize,
                right: coords.court.x + coords.court.width - ballHalfSize,
                top: coords.court.y + ballHalfSize,
                bottom: coords.court.y + coords.court.height - ballHalfSize
            };

            // Colisiones laterales
            if (ball.x < boundaries.left) {
                ball.x = boundaries.left;
                ball.vx = -ball.vx * TENNIS_PHYSICS.RESTITUTION_WALL * bounceMultiplier;
                ball.vy *= frictionMultiplier;
            } else if (ball.x > boundaries.right) {
                ball.x = boundaries.right;
                ball.vx = -ball.vx * TENNIS_PHYSICS.RESTITUTION_WALL * bounceMultiplier;
                ball.vy *= frictionMultiplier;
            }

            // Colisiones verticales
            if (ball.y < boundaries.top) {
                ball.y = boundaries.top;
                ball.vy = -ball.vy * TENNIS_PHYSICS.RESTITUTION_WALL * bounceMultiplier;
                ball.vx *= frictionMultiplier;
            } else if (ball.y > boundaries.bottom) {
                ball.y = boundaries.bottom;
                ball.vy = -ball.vy * ball.restitution * bounceMultiplier;
                ball.vx *= ball.friction * frictionMultiplier;

                // Registrar rebote
                ball.bounceCount++;
                ball.lastBounceTime = performance.now();
            }
        }

        handleTennisNetCollision(ball) {
            const coords = calculateTennisCoordinates();
            const netX = coords.net.x;
            const netTop = coords.net.y1;
            const netBottom = coords.net.y2;
            const netHeight = TENNIS_PHYSICS.NET_HEIGHT;

            // Verificar colisión con la red (área vertical)
            if (Math.abs(ball.x - netX) < ball.radius + 5 &&
                ball.y > netTop && ball.y < netBottom &&
                ball.y > netBottom - netHeight) {

                // La pelota golpea la red
                ball.vx = -ball.vx * TENNIS_PHYSICS.RESTITUTION_NET;
                ball.vy *= 0.5; // Pierde velocidad vertical
                ball.spin = 0; // Pierde spin

                // Posicionar fuera de la red
                if (ball.x < netX) {
                    ball.x = netX - ball.radius - 5;
                } else {
                    ball.x = netX + ball.radius + 5;
                }

                this.gameStats.netHits++;
                this.showTennisFeedback('🕸️ NET HIT!', '#ff4757');
            }
        }

        handleTennisBallCollisions() {
            const ballsArray = Array.from(this.physicsObjects.values()).filter(obj => obj.type === 'tennis');

            for (let i = 0; i < ballsArray.length; i++) {
                const ballA = ballsArray[i];
                for (let j = i + 1; j < ballsArray.length; j++) {
                    const ballB = ballsArray[j];

                    const dx = ballB.x - ballA.x;
                    const dy = ballB.y - ballA.y;
                    const distance = Math.sqrt(dx * dx + dy * dy);
                    const minDistance = ballA.radius + ballB.radius;

                    if (distance < minDistance && distance !== 0) {
                        // Separar pelotas
                        const normalX = dx / distance;
                        const normalY = dy / distance;
                        const overlap = minDistance - distance;

                        ballA.x -= normalX * overlap * 0.5;
                        ballA.y -= normalY * overlap * 0.5;
                        ballB.x += normalX * overlap * 0.5;
                        ballB.y += normalY * overlap * 0.5;

                        // Intercambiar velocidades (rebote elástico)
                        const tempVx = ballA.vx;
                        const tempVy = ballA.vy;
                        ballA.vx = ballB.vx * 0.9;
                        ballA.vy = ballB.vy * 0.9;
                        ballB.vx = tempVx * 0.9;
                        ballB.vy = tempVy * 0.9;

                        // Intercambiar spin
                        const tempSpin = ballA.spin;
                        ballA.spin = ballB.spin * 0.5;
                        ballB.spin = tempSpin * 0.5;
                    }
                }
            }
        }

        /* ---------- SISTEMA DE RAQUETAS VIRTUALES ---------- */
        handleTennisPlayerCollisions() {
            const players = this.getTennisPlayerPositions();
            if (players.length === 0) return;

            this.physicsObjects.forEach(ball => {
                if (ball.type !== 'tennis') return;

                players.forEach(player => {
                    const dx = ball.x - player.x;
                    const dy = ball.y - player.y;
                    const distance = Math.sqrt(dx * dx + dy * dy);
                    const racketReach = this.racketSystem.racketLength;

                    if (distance < racketReach && distance > 0) {
                        const currentTime = performance.now();

                        if (currentTime - ball.lastHitTime > this.racketSystem.hitCooldown) {
                            this.executeTennisHit(ball, player, dx, dy, distance);
                            ball.lastHitTime = currentTime;
                            this.gameStats.totalHits++;
                        }
                    }
                });
            });
        }

        executeTennisHit(ball, player, dx, dy, distance) {
            const normalX = dx / distance;
            const normalY = dy / distance;

            // Fuerza del golpe basada en proximidad
            const hitIntensity = Math.max(0.3, (this.racketSystem.racketLength - distance) / this.racketSystem.racketLength);
            const baseHitForce = TENNIS_PHYSICS.PLAYER_INTERACTION_FORCE * hitIntensity;

            // Aplicar fuerza del golpe
            ball.vx += normalX * baseHitForce * TENNIS_PHYSICS.TIMESTEP * 2;
            ball.vy += normalY * baseHitForce * TENNIS_PHYSICS.TIMESTEP * 2;

            // Añadir velocidad del jugador si se está moviendo
            if (player.vx || player.vy) {
                const playerSpeed = Math.sqrt((player.vx || 0) ** 2 + (player.vy || 0) ** 2);
                if (playerSpeed > 30) {
                    ball.vx += (player.vx || 0) * 0.8;
                    ball.vy += (player.vy || 0) * 0.8;
                }
            }

            // Generar spin aleatorio en el golpe
            ball.spin = (Math.random() - 0.5) * 40 * hitIntensity;

            // Efecto visual del golpe
            this.showTennisHitEffect(ball.x, ball.y, player.type);
        }

        showTennisHitEffect(x, y, playerType) {
            const hitColor = playerType === 'self' ? '#48bb78' : '#f56565';
            this.showTennisFeedback(`🎾 ${playerType === 'self' ? 'YOUR' : 'OPPONENT'} HIT!`, hitColor);
        }

        getTennisPlayerPositions() {
            const currentTime = performance.now();
            const players = [];
            if (!drawariaCanvas) return players;

            const canvasRect = drawariaCanvas.getBoundingClientRect();
            const deltaTime = currentTime - this.playerTracker.lastUpdateTime;

            // Self player
            const selfPlayer = document.querySelector('div.spawnedavatar.spawnedavatar-self');
            if (selfPlayer) {
                const rect = selfPlayer.getBoundingClientRect();
                const currentPos = {
                    type: 'self',
                    id: selfPlayer.dataset.playerid || 'self',
                    x: rect.left - canvasRect.left + rect.width / 2,
                    y: rect.top - canvasRect.top + rect.height / 2,
                    width: rect.width,
                    height: rect.height,
                    radius: Math.max(rect.width, rect.height) / 2,
                    vx: 0,
                    vy: 0
                };

                const prevPlayer = this.playerTracker.players.get('self');
                if (prevPlayer && deltaTime > 0) {
                    currentPos.vx = (currentPos.x - prevPlayer.x) / (deltaTime / 1000);
                    currentPos.vy = (currentPos.y - prevPlayer.y) / (deltaTime / 1000);
                }

                players.push(currentPos);
                this.playerTracker.players.set('self', currentPos);
            }

            // Other players
            const otherPlayers = document.querySelectorAll('div.spawnedavatar.spawnedavatar-otherplayer');
            otherPlayers.forEach((player, index) => {
                const rect = player.getBoundingClientRect();
                const playerId = player.dataset.playerid || `other_${index}`;
                const currentPos = {
                    type: 'other',
                    id: playerId,
                    x: rect.left - canvasRect.left + rect.width / 2,
                    y: rect.top - canvasRect.top + rect.height / 2,
                    width: rect.width,
                    height: rect.height,
                    radius: Math.max(rect.width, rect.height) / 2,
                    vx: 0,
                    vy: 0
                };

                const prevPlayer = this.playerTracker.players.get(playerId);
                if (prevPlayer && deltaTime > 0) {
                    currentPos.vx = (currentPos.x - prevPlayer.x) / (deltaTime / 1000);
                    currentPos.vy = (currentPos.y - prevPlayer.y) / (deltaTime / 1000);
                }

                players.push(currentPos);
                this.playerTracker.players.set(playerId, currentPos);
            });

            this.playerTracker.lastUpdateTime = currentTime;
            return players;
        }

        /* ---------- TENNIS SCORING SYSTEM ---------- */
        checkTennisScoring() {
            if (!this.tennisMatch.active || !this.canvasElement) return;

            const coords = calculateTennisCoordinates();

            this.physicsObjects.forEach(ball => {
                if (ball.type !== 'tennis' || !ball.isInPlay) return;

                // Verificar si la pelota está dentro de la cancha después del rebote
                if (ball.bounceCount > 0 && ball.lastBounceTime > 0) {
                    const isInCourt = this.checkTennisBallInCourt(ball, coords);

                    if (!isInCourt) {
                        this.scoreTennisPoint(ball);
                        ball.isInPlay = false;
                    }
                }

                // Verificar doble rebote (punto perdido)
                if (ball.bounceCount >= 2) {
                    this.scoreTennisPoint(ball, 'double_bounce');
                    ball.isInPlay = false;
                }
            });
        }

        checkTennisBallInCourt(ball, coords) {
            return ball.x >= coords.sideLines.leftSingles &&
                   ball.x <= coords.sideLines.rightSingles &&
                   ball.y >= coords.baseLines.top &&
                   ball.y <= coords.baseLines.bottom;
        }

        async scoreTennisPoint(ball, reason = 'out') {
            const opponent = this.tennisMatch.serving === 'p1' ? 'p2' : 'p1';

            if (reason === 'out' || reason === 'double_bounce') {
                this.tennisMatch.scores[opponent].points++;
            }

            await this.updateTennisScore();

            const reasonText = reason === 'double_bounce' ? 'DOUBLE BOUNCE' : 'OUT';
            this.showTennisFeedback(`🎾 ${reasonText}! Point to ${opponent.toUpperCase()}`, '#FFD700');

            // Cambiar servidor cada juego
            if (this.checkGameWon()) {
                this.tennisMatch.serving = this.tennisMatch.serving === 'p1' ? 'p2' : 'p1';
            }

            setTimeout(() => {
                this.clearAllTennisBalls(false);
                if (this.tennisMatch.active) {
                    this.serveTennisBall();
                }
            }, 2000);
        }

        async updateTennisScore() {
            // Convertir puntos numéricos a sistema de tenis (0, 15, 30, 40, deuce, etc.)
            const p1Points = this.convertToTennisPoints(this.tennisMatch.scores.p1.points);
            const p2Points = this.convertToTennisPoints(this.tennisMatch.scores.p2.points);

            document.getElementById('tennis-score-p1-points').textContent = p1Points;
            document.getElementById('tennis-score-p2-points').textContent = p2Points;
            document.getElementById('tennis-score-p1-sets').textContent = this.tennisMatch.scores.p1.sets;
            document.getElementById('tennis-score-p1-games').textContent = this.tennisMatch.scores.p1.games;
            document.getElementById('tennis-score-p2-sets').textContent = this.tennisMatch.scores.p2.sets;
            document.getElementById('tennis-score-p2-games').textContent = this.tennisMatch.scores.p2.games;
            document.getElementById('serving-player').textContent = this.tennisMatch.serving.toUpperCase();
        }

        convertToTennisPoints(numericPoints) {
            const tennisPoints = ['0', '15', '30', '40'];

            if (numericPoints < 4) {
                return tennisPoints[numericPoints];
            } else if (numericPoints === 4) {
                return 'DEUCE';
            } else {
                return 'ADV';
            }
        }

        checkGameWon() {
            const p1 = this.tennisMatch.scores.p1;
            const p2 = this.tennisMatch.scores.p2;

            // Verificar si alguien ganó el punto
            if ((p1.points >= 4 && p1.points - p2.points >= 2) ||
                (p2.points >= 4 && p2.points - p1.points >= 2)) {

                // Resetear puntos y sumar juego
                if (p1.points > p2.points) {
                    p1.games++;
                } else {
                    p2.games++;
                }

                p1.points = 0;
                p2.points = 0;

                // Verificar si alguien ganó el set
                if ((p1.games >= 6 && p1.games - p2.games >= 2) ||
                    (p2.games >= 6 && p2.games - p1.games >= 2)) {

                    if (p1.games > p2.games) {
                        p1.sets++;
                    } else {
                        p2.sets++;
                    }

                    p1.games = 0;
                    p2.games = 0;

                    // Verificar si alguien ganó el match
                    if (p1.sets >= TENNIS_MATCH.SETS_TO_WIN || p2.sets >= TENNIS_MATCH.SETS_TO_WIN) {
                        this.endTennisMatch(p1.sets > p2.sets ? 'p1' : 'p2');
                    }
                }

                return true;
            }

            return false;
        }

        /* ---------- TENNIS MATCH MODE ---------- */
        toggleTennisMatch() {
            const button = document.getElementById('match-mode-toggle');
            const scoreboard = document.getElementById('tennis-scoreboard');

            this.tennisMatch.active = !this.tennisMatch.active;

            if (this.tennisMatch.active) {
                button.style.background = 'linear-gradient(135deg, #FFD700, #FFA500)';
                button.setAttribute('data-active', 'true');
                scoreboard.style.display = 'block';
                this.setupTennisMatch();
                this.showTennisFeedback('🏆 WIMBLEDON MATCH MODE ACTIVATED!', '#FFD700');
            } else {
                button.style.background = 'linear-gradient(135deg, #444, #666)';
                button.removeAttribute('data-active');
                scoreboard.style.display = 'none';
                this.resetTennisMatch();
                this.showTennisFeedback('🏆 Match Mode Deactivated', '#666');
            }
        }

        async setupTennisMatch() {
            await drawCompleteTennisCourt();
            this.resetTennisMatchScores();
            await this.updateTennisScore();

            setTimeout(() => {
                this.serveTennisBall();
            }, 1500);
        }

        resetTennisMatchScores() {
            this.tennisMatch.scores = {
                p1: { sets: 0, games: 0, points: 0 },
                p2: { sets: 0, games: 0, points: 0 }
            };
            this.tennisMatch.serving = 'p1';
        }

        resetTennisMatch() {
            this.resetTennisMatchScores();
            if (this.tennisMatch.active) {
                this.clearAllTennisBalls(false);
                setTimeout(() => {
                    drawCompleteTennisCourt().then(() => {
                        this.serveTennisBall();
                    });
                }, 500);
            }
        }

        async endTennisMatch(winner) {
            this.showTennisFeedback(`🏆 ${winner.toUpperCase()} WINS THE WIMBLEDON MATCH!`, '#FFD700');

            setTimeout(() => {
                this.resetTennisMatch();
            }, 4000);
        }

        /* ---------- RENDERING ---------- */
        renderTennisBalls() {
            this.physicsObjects.forEach(obj => {
                if (obj.type !== 'tennis') return;

                const dx = Math.abs(obj.x - obj.lastRenderX);
                const dy = Math.abs(obj.y - obj.lastRenderY);

                const needsServerRedraw = dx > MOVEMENT_THRESHOLD || dy > MOVEMENT_THRESHOLD;

                if (needsServerRedraw) {
                    // Borrar posición anterior
                    if (obj.lastRenderX !== -9999 || obj.lastRenderY !== -9999) {
                        this.drawTennisBall(obj.lastRenderX, obj.lastRenderY, obj.radius, '#FFFFFF');
                    }

                    // Dibujar en nueva posición
                    this.drawTennisBall(obj.x, obj.y, obj.radius, obj.color);

                    obj.lastRenderX = obj.x;
                    obj.lastRenderY = obj.y;
                }
            });
        }

        drawTennisBall(x, y, radius, color) {
            const effectiveThickness = radius * 2.2; // Más pequeña que baloncesto
            enqueueDrawCommand(x, y, x + 0.1, y + 0.1, color, effectiveThickness);
        }

        /* ---------- UTILITY FUNCTIONS ---------- */
        clearAllTennisBalls(showFeedback = true) {
            this.physicsObjects.clear();
            positionCache.clear();

            if (drawariaCtx && drawariaCanvas) {
                drawariaCtx.clearRect(0, 0, drawariaCanvas.width, drawariaCanvas.height);
            }
            if (showFeedback) {
                this.showTennisFeedback('🗑️ ALL TENNIS BALLS CLEARED!', '#cc0000');
            }
        }

        resetAllTennisBalls() {
            if (this.canvasElement) {
                this.physicsObjects.forEach(obj => {
                    obj.x = this.canvasElement.width / 2 + (Math.random() - 0.5) * 100;
                    obj.y = this.canvasElement.height / 2 + (Math.random() - 0.5) * 100;
                    obj.vx = 0; obj.vy = 0; obj.spin = 0;
                    obj.lastRenderX = -9999; obj.lastRenderY = -9999;
                    obj.bounceCount = 0;
                    obj.isInPlay = true;
                });

                this.showTennisFeedback('🔄 All tennis balls reset to center court!', '#74b9ff');
            }
        }

        async cleanTennisCourt() {
            if (!drawariaCanvas) return;

            this.showTennisFeedback('🧹 Cleaning Wimbledon Court...', '#e17055');

            const canvasWidth = drawariaCanvas.width;
            const canvasHeight = drawariaCanvas.height;

            for (let y = 0; y < canvasHeight; y += 80) {
                for (let x = 0; x < canvasWidth; x += 80) {
                    const width = Math.min(80, canvasWidth - x);
                    const height = Math.min(80, canvasHeight - y);
                    enqueueDrawCommand(x, y, x + width, y + height, '#FFFFFF', Math.max(width, height));
                    await sleep(3);
                }
            }

            if (drawariaCtx) {
                drawariaCtx.clearRect(0, 0, canvasWidth, canvasHeight);
            }

            this.showTennisFeedback('🧹 Wimbledon Court Cleaned!', '#00d084');
        }

        /* ---------- PANEL FUNCTIONALITY ---------- */
        makeTennisPanelDraggable() {
            const panel = document.getElementById('tennis-physics-panel');
            const header = document.getElementById('tennis-panel-header');

            let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

            if (header) {
                header.onmousedown = dragMouseDown;
            } else {
                panel.onmousedown = dragMouseDown;
            }

            function dragMouseDown(e) {
                e = e || window.event;
                e.preventDefault();
                pos3 = e.clientX;
                pos4 = e.clientY;
                document.onmouseup = closeDragElement;
                document.onmousemove = elementDrag;
                panel.classList.add('panel-dragging');
            }

            function elementDrag(e) {
                e = e || window.event;
                e.preventDefault();
                pos1 = pos3 - e.clientX;
                pos2 = pos4 - e.clientY;
                pos3 = e.clientX;
                pos4 = e.clientY;

                const newTop = panel.offsetTop - pos2;
                const newLeft = panel.offsetLeft - pos1;

                const maxLeft = window.innerWidth - panel.offsetWidth;
                const maxTop = window.innerHeight - panel.offsetHeight;

                panel.style.top = Math.min(Math.max(0, newTop), maxTop) + "px";
                panel.style.left = Math.min(Math.max(0, newLeft), maxLeft) + "px";
                panel.style.right = 'auto';
            }

            function closeDragElement() {
                document.onmouseup = null;
                document.onmousemove = null;
                panel.classList.remove('panel-dragging');
            }
        }

        setupTennisPanelButtons() {
            const minimizeBtn = document.getElementById('tennis-minimize-btn');
            const closeBtn = document.getElementById('tennis-close-btn');
            const content = document.getElementById('tennis-panel-content');
            const panel = document.getElementById('tennis-physics-panel');

            let isMinimized = false;

            // MINIMIZE BUTTON
            minimizeBtn?.addEventListener('click', (e) => {
                e.stopPropagation();
                if (!panel) return;

                if (!isMinimized) {
                    content.style.display = 'none';
                    panel.style.height = 'auto';
                    minimizeBtn.innerHTML = '+';
                    isMinimized = true;
                    this.showTennisFeedback('📱 Tennis Panel Minimized', '#32CD32');
                } else {
                    content.style.display = 'block';
                    panel.style.height = 'auto';
                    minimizeBtn.innerHTML = '−';
                    isMinimized = false;
                    this.showTennisFeedback('📱 Tennis Panel Restored', '#32CD32');
                }
            });

            // CLOSE BUTTON
            closeBtn?.addEventListener('click', (e) => {
                e.stopPropagation();
                if (!panel) return;

                if (confirm('¿Estás seguro de que quieres cerrar el motor de tenis?')) {
                    if (this.isActive) {
                        this.stopTennisPhysics();
                    }
                    isStopped = true;
                    panel.remove();
                    this.showTennisFeedback('❌ Tennis Engine Closed', '#ff4757');
                    console.log('🔴 Tennis Panel closed by user');
                }
            });

            // Hover effects
            [minimizeBtn, closeBtn].forEach(btn => {
                if (!btn) return;
                btn.addEventListener('mouseenter', () => btn.style.opacity = '0.8');
                btn.addEventListener('mouseleave', () => btn.style.opacity = '1');
            });
        }

        startTennisStatsMonitoring() {
            setInterval(() => {
                document.getElementById('tennis-ball-count').textContent = this.physicsObjects.size;
                document.getElementById('hits-count').textContent = this.gameStats.totalHits;
                document.getElementById('aces-count').textContent = this.gameStats.totalAces;
                document.getElementById('net-hits-count').textContent = this.gameStats.netHits;
                document.getElementById('tennis-max-speed').textContent = Math.round(this.gameStats.maxVelocityReached * 3.6); // Convert to km/h
                document.getElementById('surface-info').textContent =
                    this.controls.courtSurface.charAt(0).toUpperCase() + this.controls.courtSurface.slice(1);
            }, 1000);
        }

        showTennisFeedback(message, color) {
            const feedback = document.createElement('div');
            feedback.style.cssText = `
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: ${color};
                color: white;
                padding: 15px 25px;
                border-radius: 10px;
                font-weight: bold;
                z-index: 2147483648;
                font-size: 16px;
                box-shadow: 0 4px 20px rgba(0,0,0,0.3);
                opacity: 0;
                transition: opacity 0.3s ease-in-out;
                text-shadow: 1px 1px 3px rgba(0,0,0,0.5);
                border: 2px solid #FFD700;
            `;
            feedback.innerHTML = message;

            document.body.appendChild(feedback);
            setTimeout(() => feedback.style.opacity = '1', 10);
            setTimeout(() => feedback.style.opacity = '0', 2500);
            setTimeout(() => feedback.remove(), 2800);
        }
    }

    /* ---------- GLOBAL INITIALIZATION ---------- */
    let tennisEngine = null;

    const initTennisEngine = () => {
        if (!tennisEngine) {
            console.log('🎾 Initializing Wimbledon Tennis Physics Engine v1.0...');
            tennisEngine = new AdvancedDrawariaTennis();

            setTimeout(() => {
                const confirmMsg = document.createElement('div');
                confirmMsg.style.cssText = `
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background: linear-gradient(45deg, #228B22, #32CD32);
                    color: white;
                    padding: 25px 35px;
                    border-radius: 20px;
                    font-size: 16px;
                    font-weight: bold;
                    z-index: 2147483648;
                    text-align: center;
                    box-shadow: 0 0 40px rgba(34,139,34,0.6);
                    opacity: 0;
                    transition: opacity 0.3s ease-in-out;
                    border: 3px solid #FFD700;
                `;
                confirmMsg.innerHTML = `
                    🎾 WIMBLEDON TENNIS ENGINE v1.0 LOADED! 🎾<br>
                    <div style="font-size: 12px; margin-top: 10px; color: #E0FFE0;">
                        ✅ Professional Wimbledon Court • Tennis Physics • Spin Effects<br>
                        ✅ Net Detection • Court Surfaces • Tennis Scoring System<br>
                        ✅ Virtual Rackets • Match Mode • Realistic Ball Physics
                    </div>
                `;

                document.body.appendChild(confirmMsg);
                setTimeout(() => confirmMsg.style.opacity = '1', 10);
                setTimeout(() => confirmMsg.style.opacity = '0', 4000);
                setTimeout(() => confirmMsg.remove(), 4300);
            }, 1000);
        }
    };

    // Enhanced CSS for Tennis styling
    const tennisStyle = document.createElement('style');
    tennisStyle.textContent = `
        @keyframes tennis-serve {
            0% { transform: scale(0) rotate(0deg); opacity: 1; }
            50% { transform: scale(1.3) rotate(180deg); opacity: 0.9; }
            100% { transform: scale(0) rotate(360deg); opacity: 0; }
        }

        @keyframes court-shine {
            0% { box-shadow: 0 0 15px rgba(34, 139, 34, 0.3); }
            50% { box-shadow: 0 0 25px rgba(50, 205, 50, 0.6); }
            100% { box-shadow: 0 0 15px rgba(34, 139, 34, 0.3); }
        }

        .tennis-mode-toggle[data-active="true"] {
            animation: court-shine 2s infinite;
        }

        #tennis-physics-panel {
            transition: none !important;
        }

        #tennis-panel-header:hover {
            background: linear-gradient(45deg, #228B22, #32CD32) !important;
        }

        #tennis-minimize-btn:hover {
            background: rgba(255,255,255,0.4) !important;
        }

        #tennis-close-btn:hover {
            background: rgba(255,0,0,0.8) !important;
        }

        /* Tennis court specific styling */
        .wimbledon-court {
            background: linear-gradient(45deg, #228B22, #32CD32);
        }

        .tennis-ball-spin {
            animation: tennis-ball-rotation 0.5s linear infinite;
        }

        @keyframes tennis-ball-rotation {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        /* Tennis match styling */
        @keyframes tennis-serve-ready {
            0%, 100% { transform: translateY(0) scale(1); }
            50% { transform: translateY(-8px) scale(1.05); }
        }

        .tennis-serving {
            animation: tennis-serve-ready 1s infinite ease-in-out;
        }

        /* Status div tennis styling */
        #tennis-status {
            font-family: 'Arial Black', Arial, sans-serif !important;
            animation: court-shine 3s infinite;
        }

        /* Button hover effects */
        button:hover {
            transform: translateY(-2px) !important;
            transition: all 0.2s ease !important;
        }

        /* Tennis color scheme */
        .tennis-green { color: #228B22; }
        .tennis-lime { color: #32CD32; }
        .tennis-white { color: #FFFFFF; }
        .tennis-black { color: #000000; }
        .tennis-brown { color: #8B4513; }

        /* Tennis specific animations */
        @keyframes tennis-net-hit {
            0% { opacity: 1; }
            50% { opacity: 0.3; transform: scale(1.2); }
            100% { opacity: 1; transform: scale(1); }
        }

        .tennis-net-collision {
            animation: tennis-net-hit 0.3s ease-out;
        }
    `;
    document.head.appendChild(tennisStyle);

    // Initialize when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initTennisEngine);
    } else {
        initTennisEngine();
    }
    setTimeout(initTennisEngine, 2000);

    console.log('🎾 Advanced Wimbledon Tennis Physics Engine v1.0 loaded successfully! 🎾');
    console.log('🏟️ Features: Professional Court • Tennis Physics • Spin Effects • Net Detection');
    console.log('🏆 Ready for Wimbledon-style tennis matches in Drawaria!');

})();