<!DOCTYPE html>

<html lang="es">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>WIN Bird Flap</title>

    


<link href="https://fonts.googleapis.com/css2?family=Strait&display=swap" rel="stylesheet">

    


<script src="https://cdn.jsdelivr.net/npm/tone@14.7.71/build/Tone.js"></script>

    <style>

        /* * FIX: ENCAPSULACIÓN DE ESTILOS PARA EVITAR INTERFERENCIA CON ELEMENTOR 

         * Todos los estilos globales (como html, body, *) han sido eliminados.

         * El estilo ahora solo afecta al contenedor #game-container y sus hijos.

         */

        

        /* 1. Reset básico y fuente, SCOPED al contenedor principal */

        #game-container {

            /* Fuente y color que antes estaban en 'body' */

            font-family: 'Strait', sans-serif; 

            color: white;

            -webkit-user-select: none; 

            -moz-user-select: none;

            -ms-user-select: none;

            user-select: none;


            /* Propiedades de posicionamiento y tamaño */

            position: relative;

            margin: 0 auto; 

            margin-top: 5vh; 

            

            /* Forzamos una relación de aspecto (ej. 9:16) */

            aspect-ratio: 9 / 16;

            height: 90vh; 

            max-height: 750px; 

            width: auto; 

            max-width: 95vw; 


            /* Fallback para navegadores que no soportan aspect-ratio */

            @supports not (aspect-ratio: 9 / 16) {

                width: calc(90vh * 9 / 16); 

                max-width: 95vw;

            }


            border-radius: 10px;

            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);

            overflow: hidden; 

            background: linear-gradient(to bottom, #87CEEB 0%, #ADD8E6 100%); /* Cielo */

            

            /* Asegura que los hijos usen border-box */

            box-sizing: border-box;

        }


        /* 2. Aplicar box-sizing al contenido del juego */

        #game-container *, #game-container *::before, #game-container *::after {

            box-sizing: border-box;

            /* También aplicamos el reseteo de márgenes/paddings internamente, si fuera necesario

               aunque con el posicionamiento absoluto de las UI, esto es menos crítico. */

            margin: 0;

            padding: 0;

        }


        /* El lienzo del juego */

        #game-canvas {

            width: 100%;

            height: 100%;

            display: block;

        }


        /* Superposiciones de UI (Puntaje, Inicio, Fin) */

        .ui-overlay {

            position: absolute;

            top: 0;

            left: 0;

            width: 100%;

            height: 100%;

            display: flex;

            flex-direction: column;

            justify-content: center;

            align-items: center;

            text-align: center;

            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);

            padding: 20px;

        }


        /* Mensaje de inicio */

        #start-message {

            background: rgba(0, 0, 0, 0.3);

            font-size: clamp(1.2rem, 4vw, 1.8rem); 

            z-index: 10;

        }

        

        #start-message h1 {

            font-size: clamp(2rem, 8vw, 3rem); 

        }



        /* Pantalla de Game Over */

        #game-over-screen {

            background: rgba(0, 0, 0, 0.7);

            font-size: clamp(1.5rem, 5vw, 2.2rem); 

            z-index: 10;

            display: none; 

        }


        #game-over-screen h2 {

            /* Título "JUEGO TERMINADO" - Reducido a 5vw / 2rem máx. */

            font-size: clamp(1.5rem, 5vw, 2rem); 

            margin-bottom: 10px;

        }


        #final-score {

            font-size: clamp(1rem, 3.5vw, 1.5rem); 

            margin-bottom: 20px;

        }

        

        #final-score strong {

            font-size: clamp(1.5rem, 5.5vw, 2.2rem); 

            color: #FFD700; 

            display: block;

            margin-top: 10px;

        }


        /* Ocultar el botón de Reiniciar */

        #restart-button {

            display: none; 

        }


        /* Pantalla de puntaje en el juego */

        #score-display {

            position: absolute;

            top: 5%;

            left: 50%;

            transform: translateX(-50%);

            font-size: clamp(2.5rem, 10vw, 3.5rem);

            font-weight: bold;

            z-index: 5;

            display: none; 

        }

    </style>

</head>

<body>


    


<div id="game-container">

        


<canvas id="game-canvas"></canvas>


        


<div id="score-display">0</div>


        


<div id="start-message" class="ui-overlay">

            <h1>WIN Bird Flap</h1>

            


<p style="margin-top: 20px; font-size: clamp(0.9rem, 3vw, 1.1rem);">Modo Clásico: ¡Esquiva los tubos!</p>

            <p style="font-size: clamp(0.9rem, 3vw, 1.1rem);">Toca o haz clic para empezar</p>

            <p style="font-size: clamp(0.6rem, 2vw, 0.9rem); margin-top: 5px;">(El audio comenzará con la primera interacción)</p>

        </div>


        


<div id="game-over-screen" class="ui-overlay">

            


<h2>¡JUEGO TERMINADO!</h2>

            <p id="final-score">Puntaje Final: <strong>0</strong></p>

            


<button id="restart-button">Jugar de Nuevo</button>

        </div>

    </div>


    <script>

        // Inicializamos el contexto de audio en la primera interacción para evitar errores

        let audioContextStarted = false;


        // --- 1. CONFIGURACIÓN DE AUDIO (TONE.JS) ---


        const musicVolume = new Tone.Volume(-15).toDestination();

        const reverb = new Tone.Reverb({ decay: 4, wet: 0.5 }).connect(musicVolume);

        const synth = new Tone.PolySynth(Tone.Synth, {

            oscillator: { type: "sine" },

            envelope: { attack: 2, decay: 0.5, sustain: 0.5, release: 3 }

        }).connect(reverb); 


        const chords = [

            ["C4", "E4", "G4", "B4"],

            ["A3", "C4", "E4", "G4"],

            ["F3", "A3", "C4", "E4"],

            ["G3", "B3", "D4", "F4"]

        ];

        let chordIndex = 0;


        const musicLoop = new Tone.Loop(time => {

            const chord = chords[chordIndex % chords.length];

            synth.triggerAttackRelease(chord, "2n", time); 

            chordIndex++;

        }, "2n"); 

        

        // Sonido de aleteo (rápido y percusivo para evitar errores de temporización)

        const flapSound = new Tone.Synth({

            volume: -15, 

            oscillator: { type: "square" }, 

            envelope: {

                attack: 0.005,

                decay: 0.05,

                sustain: 0,

                release: 0.05,

            }

        }).toDestination();

        

        function playFlapSound() {

            flapSound.triggerAttackRelease("C6", "32n");

        }


        const scoreSound = new Tone.DuoSynth({

            vibratoAmount: 0.5, vibratoRate: 5, harmonicity: 1.5,

            voice0: { volume: -10, oscillator: { type: "square" }, envelope: { attack: 0.005, decay: 0.2, sustain: 0.4, release: 0.8, } },

            voice1: { volume: -10, oscillator: { type: "sine" }, envelope: { attack: 0.005, decay: 0.2, sustain: 0.4, release: 0.8, } }

        }).toDestination();

        function playScoreSound() {

            scoreSound.triggerAttackRelease("C6", "8n");

        }


        // Sonido de Crash/Colisión (MetalSynth con Distorsión)

        const distortion = new Tone.Distortion(0.8).toDestination(); 

        const crashSynth = new Tone.MetalSynth({

            volume: -10,

            frequency: 100,

            envelope: {

                attack: 0.001,

                decay: 0.2,

                release: 0.1

            },

            harmonicity: 5.1,

            modulationIndex: 32,

            resonance: 4000,

            octaves: 1.5

        }).connect(distortion); 

        

        function playCrashSound() {

            crashSynth.triggerAttackRelease("C4", "16n", Tone.now(), 0.5); 

        }



        // --- 2. CONFIGURACIÓN INICIAL (Resto del código) ---

        

        // Elementos del DOM

        const gameContainer = document.getElementById('game-container');

        const canvas = document.getElementById('game-canvas');

        const ctx = canvas.getContext('2d');

        const startMessage = document.getElementById('start-message');

        const gameOverScreen = document.getElementById('game-over-screen');

        const scoreDisplay = document.getElementById('score-display');

        const finalScoreDisplay = document.getElementById('final-score');

        // const restartButton = document.getElementById('restart-button'); // No usado al estar oculto


        // Definimos una resolución lógica fija para el juego

        const LOGICAL_WIDTH = 320;

        const LOGICAL_HEIGHT = 568; 

        canvas.width = LOGICAL_WIDTH;

        canvas.height = LOGICAL_HEIGHT;


        // --- 3. VARIABLES DEL JUEGO ---


        // Constantes de física y juego

        const BIRD_WIDTH = 28;

        const BIRD_HEIGHT = 22;

        const GRAVITY = 0.30; 

        const FLAP_STRENGTH = -6.0; 

        const PIPE_WIDTH = 50;

        

        // Constantes para la dificultad

        const INITIAL_PIPE_GAP = 140; 

        const INITIAL_PIPE_SPEED = 1.8; 

        const INITIAL_PIPE_SPAWN_INTERVAL = 120; 


        // Parámetros de dificultad progresiva

        const DIFFICULTY_START_TIME = 40 * 60; // Dificultad aumenta por los primeros 40 segundos de juego

        const MIN_PIPE_GAP = 110; 

        const MAX_PIPE_SPEED = 2.5; 

        

        // Variables de estado del juego 

        let currentPipeGap; 

        let currentPipeSpeed; 

        let bird, pipes, score, frame, gameState; 


        // --- 4. CLASES Y OBJETOS DEL JUEGO ---


        // Objeto Pájaro

        class Bird {

            constructor() {

                this.x = 50;

                this.y = LOGICAL_HEIGHT / 2 - BIRD_HEIGHT / 2;

                this.width = BIRD_WIDTH;

                this.height = BIRD_HEIGHT;

                this.velocityY = 0;

                this.rotation = 0;

            }


            // Actualiza la física del pájaro

            update() {

                this.velocityY += GRAVITY;

                this.y += this.velocityY;


                // Calcula la rotación

                if (this.velocityY < 0) {

                    this.rotation = -0.3; 

                } else {

                    this.rotation = Math.min(this.velocityY * 0.08, Math.PI / 3);

                }


                // Colisión con el suelo (TERMINA EL JUEGO)

                const GROUND_HEIGHT = 40;

                if (this.y + this.height > LOGICAL_HEIGHT - GROUND_HEIGHT) {

                    this.y = LOGICAL_HEIGHT - GROUND_HEIGHT - this.height;

                    this.velocityY = 0;

                    finalEndGame(); // Llama a la función de fin de juego (Game Over)

                    return;

                }


                // Colisión con el cielo

                if (this.y < 0) {

                    this.y = 0;

                    this.velocityY = 0;

                }

            }


            // Dibuja el pájaro 

            draw() {

                ctx.save();

                ctx.translate(this.x + this.width / 2, this.y + this.height / 2);

                ctx.rotate(this.rotation);

                

                // Cuerpo

                ctx.fillStyle = '#000080'; // Cambiado a azul marino

                ctx.beginPath();

                ctx.ellipse(0, 0, this.width / 2, this.height / 2, 0, 0, 2 * Math.PI);

                ctx.fill();


                // Ala

                ctx.fillStyle = '#4169E1'; // Cambiado a un azul más claro (Royal Blue) para contraste

                ctx.beginPath();

                ctx.ellipse(-3, 2, this.width / 4, this.height / 4, 0.3, 0, 2 * Math.PI);

                ctx.fill();


                // Ojo

                ctx.fillStyle = 'white';

                ctx.beginPath();

                ctx.arc(this.width / 4, -this.height / 6, 4, 0, 2 * Math.PI);

                ctx.fill();

                ctx.fillStyle = 'black';

                ctx.beginPath();

                ctx.arc(this.width / 4 + 1, -this.height / 6, 2, 0, 2 * Math.PI);

                ctx.fill();


                // Pico

                ctx.fillStyle = 'orange';

                ctx.beginPath();

                ctx.moveTo(this.width / 2, -2);

                ctx.lineTo(this.width / 2 + 8, this.height / 6 - 2);

                ctx.lineTo(this.width / 2, this.height / 3 - 2);

                ctx.fill();


                ctx.restore();

            }


            // Impulso hacia arriba

            flap() {

                this.velocityY = FLAP_STRENGTH;

                if (gameState === 'playing') {

                    playFlapSound();

                }

            }

        }


        // --- 5. FUNCIONES PRINCIPALES DEL JUEGO ---


        // Función para actualizar la dificultad basándose en el tiempo de la ronda

        function updateDifficulty() {

            // El aumento de dificultad se basa en el número de frames jugados (frame)

            const progress = Math.min(1, frame / DIFFICULTY_START_TIME); 

            currentPipeGap = INITIAL_PIPE_GAP - (INITIAL_PIPE_GAP - MIN_PIPE_GAP) * progress;

            currentPipeSpeed = INITIAL_PIPE_SPEED + (MAX_PIPE_SPEED - INITIAL_PIPE_SPEED) * progress;

        }

        

        // Función para limpiar el estado del juego (Bird, Pipes, Score)

        function resetGameState() {

            bird = new Bird();

            pipes = [];

            score = 0; // Puntaje de la partida actual

            frame = 0; // Frames de la partida actual

            

            scoreDisplay.textContent = '0';

        }



        // Inicializa o resetea el juego completo 

        function resetGame() {

            // Reset de estados

            currentPipeGap = INITIAL_PIPE_GAP;

            currentPipeSpeed = INITIAL_PIPE_SPEED;

            

            resetGameState(); // Prepara el estado inicial del juego

            

            gameState = 'start'; 


            // Detiene la música si ya estaba corriendo

            Tone.Transport.stop();

            musicLoop.stop();

            chordIndex = 0; 


            // Configuración de la UI

            startMessage.style.display = 'flex';

            gameOverScreen.style.display = 'none';

            scoreDisplay.style.display = 'none';

        }



        // Bucle principal del juego

        function gameLoop() {

            if (gameState !== 'over') {

                update();

            }

            draw();

            requestAnimationFrame(gameLoop);

        }


        // Función de actualización de estado

        function update() {

            if (gameState !== 'playing') return;


            frame++; // Frame counter for the current game

            

            updateDifficulty(); 


            bird.update();

            updatePipes();

            checkCollisions();

        }


        // Función de dibujado en el lienzo 

        function draw() {

            ctx.clearRect(0, 0, LOGICAL_WIDTH, LOGICAL_HEIGHT);

            drawPipes();

            drawGround();

            bird.draw();

        }

        

        // Comienza la partida (llamado en el primer click)

        function startGame() {

            if (gameState === 'start') {

                

                resetGameState();


                // Iniciar contexto de audio y música si no se ha hecho

                if (!audioContextStarted) {

                    Tone.start();

                    Tone.Transport.bpm.value = 60; 

                    audioContextStarted = true;

                }

                Tone.Transport.start();

                musicLoop.start(0);


                gameState = 'playing';

                startMessage.style.display = 'none';

                scoreDisplay.style.display = 'block';

                bird.flap(); // Primer impulso

            }

        }

        

        // Termina el juego COMPLETAMENTE (solo por colisión)

        function finalEndGame() {

            if (gameState === 'over') return; // Evita ejecuciones múltiples

            gameState = 'over';

            

            playCrashSound();

            

            // Parar la música

            Tone.Transport.stop(); 

            musicLoop.stop();

            

            // Mostrar la pantalla de Game Over y el puntaje

            gameOverScreen.style.display = 'flex';

            

            // Texto actualizado a JUEGO TERMINADO

            gameOverScreen.querySelector('h2').textContent = "¡JUEGO TERMINADO!";

            finalScoreDisplay.innerHTML = `Puntaje Final: <strong>${score}</strong>`;

            scoreDisplay.style.display = 'none';

            

            // --- INICIO DE LA MODIFICACIÓN ---

            // Enviamos el puntaje a la página contenedora (Elementor)

            if (window.parent && window.parent !== window) {

                // Usamos un objeto para poder identificar el mensaje

                window.parent.postMessage({

                    type: 'winBirdFlapScore', // Identificador único

                    score: score // El puntaje final

                }, '*'); // En producción, deberías cambiar '*' por el dominio de tu web (ej: "https://tu-web.com")

            }

            // --- FIN DE LA MODIFICACIÓN ---


            // NOTA: El botón de reiniciar está oculto por CSS.

        }


        // --- 6. LÓGICA DE OBSTÁCULOS (TUBERÍAS) ---


        function updatePipes() {

            // Añadir nueva tubería (el intervalo de aparición es constante)

            if (frame % INITIAL_PIPE_SPAWN_INTERVAL === 0) {

                const GROUND_HEIGHT = 40;

                let topHeight = Math.random() * (LOGICAL_HEIGHT - currentPipeGap - GROUND_HEIGHT - 80) + 40;

                let bottomHeight = LOGICAL_HEIGHT - topHeight - currentPipeGap - GROUND_HEIGHT;

                

                pipes.push({

                    x: LOGICAL_WIDTH,

                    topHeight: topHeight,

                    bottomHeight: bottomHeight,

                    scored: false

                });

            }


            // Mover y eliminar tuberías

            for (let i = pipes.length - 1; i >= 0; i--) {

                let p = pipes[i];

                p.x -= currentPipeSpeed; // Movimiento hacia la izquierda


                if (p.x + PIPE_WIDTH < 0) {

                    pipes.splice(i, 1);

                }


                // Comprobar puntuación

                if (!p.scored && p.x + PIPE_WIDTH < bird.x) {

                    p.scored = true;

                    score++;

                    scoreDisplay.textContent = score.toString();

                    playScoreSound();

                }

            }

        }


        function drawPipes() {

            ctx.fillStyle = '#28a745'; 

            ctx.strokeStyle = '#000';

            ctx.lineWidth = 2;


            for (const p of pipes) {

                // Tubería de arriba

                ctx.fillRect(p.x, 0, PIPE_WIDTH, p.topHeight);

                ctx.fillRect(p.x - 5, p.topHeight - 20, PIPE_WIDTH + 10, 20);


                // Tubería de abajo

                const GROUND_HEIGHT = 40;

                ctx.fillRect(p.x, LOGICAL_HEIGHT - GROUND_HEIGHT - p.bottomHeight, PIPE_WIDTH, p.bottomHeight);

                ctx.fillRect(p.x - 5, LOGICAL_HEIGHT - GROUND_HEIGHT - p.bottomHeight, PIPE_WIDTH + 10, 20);

                

                // Bordes (opcional para estilo)

                ctx.strokeRect(p.x, 0, PIPE_WIDTH, p.topHeight);

                ctx.strokeRect(p.x - 5, p.topHeight - 20, PIPE_WIDTH + 10, 20);

                ctx.strokeRect(p.x, LOGICAL_HEIGHT - GROUND_HEIGHT - p.bottomHeight, PIPE_WIDTH, p.bottomHeight);

                ctx.strokeRect(p.x - 5, LOGICAL_HEIGHT - GROUND_HEIGHT - p.bottomHeight, PIPE_WIDTH + 10, 20);

            }

        }


        // --- 7. LÓGICA DE COLISIONES Y SUELO ---


        function checkCollisions() {

            const GROUND_HEIGHT = 40;

            for (const p of pipes) {

                // Comprobación de colisión AABB 

                if (

                    bird.x < p.x + PIPE_WIDTH &&

                    bird.x + bird.width > p.x &&

                    (bird.y < p.topHeight || bird.y + bird.height > LOGICAL_HEIGHT - GROUND_HEIGHT - p.bottomHeight)

                ) {

                    finalEndGame(); // Colisión con tubería: TERMINA EL JUEGO

                    return;

                }

            }

        }


        function drawGround() {

            const GROUND_HEIGHT = 40;

            ctx.fillStyle = '#D2B48C'; 

            ctx.fillRect(0, LOGICAL_HEIGHT - GROUND_HEIGHT, LOGICAL_WIDTH, GROUND_HEIGHT);

            

            // Textura de hierba

            ctx.fillStyle = '#228B22'; 

            ctx.fillRect(0, LOGICAL_HEIGHT - GROUND_HEIGHT, LOGICAL_WIDTH, 10);

            

            // Líneas de detalle (Movimiento hacia la IZQUIERDA)

            ctx.strokeStyle = 'rgba(0,0,0,0.3)';

            ctx.lineWidth = 1;

            

            // Desplazamiento basado en el frame y velocidad, asegurando que sea hacia la izquierda (-)

            const scrollSpeed = currentPipeSpeed * 0.5;

            const offset = (-frame * scrollSpeed) % LOGICAL_WIDTH; 


            for(let i = 0; i < LOGICAL_WIDTH / 40 + 2; i++) {

                const x1 = (i * 40 + offset) % LOGICAL_WIDTH;

                const x2 = ((i * 40 + 10) + offset) % LOGICAL_WIDTH;


                // Asegurar que las coordenadas no sean negativas y se envuelvan correctamente

                const getWrappedX = (val) => (val < 0) ? val + LOGICAL_WIDTH : val;


                ctx.beginPath();

                ctx.moveTo( getWrappedX(x1), LOGICAL_HEIGHT - GROUND_HEIGHT + 15);

                ctx.lineTo( getWrappedX(x2), LOGICAL_HEIGHT - GROUND_HEIGHT / 2);

                ctx.stroke();

            }

        }


        // --- 8. MANEJADORES DE EVENTOS (INPUT) ---


        function handleInput(e) {

            e.preventDefault(); 

            

            if (gameState === 'start') {

                startGame();

            } else if (gameState === 'playing') {

                bird.flap();

            }

        }


        // Eventos para móvil y escritorio

        gameContainer.addEventListener('mousedown', handleInput);

        gameContainer.addEventListener('touchstart', handleInput, { passive: false });

        window.addEventListener('keydown', (e) => {

            if (e.code === 'Space') {

                handleInput(e);

            }

        });


        // El manejador del botón de reiniciar se ha eliminado al estar el botón oculto.

        

        // --- 9. INICIO DEL JUEGO ---

        

        // Iniciar el juego al cargar la página

        resetGame();

        gameLoop();


    </script>

</body>

</html>