🦇 Bat Flap 🌕

Tap, click or press Space to flap your wings. Fly through the gaps between the haunted gravestones and don't touch anything!
Score: 0
Press "Start" to fly into the night...

Night has fallen and a little bat 🦇 is flying through the graveyard. Tap, click or press Space to flap its wings and slip through the gaps between the haunted gravestones. Every gap you pass scores a point, and the night gets faster as you go. One touch and the game is over. How far can you fly under the full moon?

Here is the code
CSS
.bat-game {
    text-align: center;
}

.instructions {
    max-width: 500px;
    margin: 0 auto 15px;
    font-size: 1em;
    color: #555;
}

.bat-info {
    margin-bottom: 10px;
    font-size: 1.2em;
}

#batCanvas {
    display: block;
    margin: 0 auto;
    max-width: 100%;
    border: 3px solid #ff7518;
    border-radius: 10px;
    background: #120a1f;
    touch-action: manipulation;
    cursor: pointer;
}

.bat-message {
    margin-top: 15px;
    font-weight: bold;
}

.start-btn {
    margin-top: 15px;
    padding: 10px 20px;
    font-size: 1em;
    background-color: #ff7518;
    color: #1a1025;
    font-weight: bold;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

.start-btn:hover {
    background-color: #ff8f45;
}

.start-btn:disabled {
    background-color: #ccc;
    cursor: not-allowed;
}
HTML
<div class="bat-game">
    <h1>🦇 Bat Flap 🌕</h1>
    <div class="instructions">
        Tap, click or press Space to flap your wings. Fly through the gaps between the haunted gravestones and don't touch anything!
    </div>
    <div class="bat-info">Score: <span id="batScore">0</span></div>
    <canvas id="batCanvas" width="320" height="400"></canvas>
    <div class="bat-message" id="batMessage">Press "Start" to fly into the night...</div>
    <button class="start-btn" id="batStart">Start</button>
</div>
JS
const canvas = document.getElementById('batCanvas');
    const ctx = canvas.getContext('2d');
    const scoreDisplay = document.getElementById('batScore');
    const message = document.getElementById('batMessage');
    const startButton = document.getElementById('batStart');
    const hiddenScore = document.getElementById('hiddenScore');

    const W = canvas.width;
    const H = canvas.height;
    const GRAVITY = 0.22; // per 1/60s step
    const FLAP = -4.6;
    const PIPE_SPACING = 190;
    const PIPE_WIDTH = 50;
    const GAP = 125;
    const BAT_X = 70;
    const BAT_SIZE = 28;

    let batY, velocity, pipes, score, running, waiting, lastTime, animationId, distance;

    function reset() {
        batY = H / 2;
        velocity = 0;
        pipes = [];
        score = 0;
        distance = 0;
        scoreDisplay.textContent = 0;
        hiddenScore.value = 0;
    }

    function flap() {
        if (!running) {
            return;
        }
        if (waiting) {
            // The first flap starts the flight
            waiting = false;
            message.textContent = 'Keep flapping!';
            lastTime = performance.now();
        }
        velocity = FLAP;
    }

    function addPipe() {
        const top = 60 + Math.random() * (H - GAP - 120);
        pipes.push({ x: W, top: top, passed: false });
    }

    function drawBackground() {
        ctx.fillStyle = '#120a1f';
        ctx.fillRect(0, 0, W, H);
        ctx.font = '50px serif';
        ctx.textAlign = 'center';
        ctx.fillText('🌕', W - 50, 60);
    }

    function drawPipe(pipe) {
        ctx.fillStyle = '#4a4458';
        ctx.fillRect(pipe.x, 0, PIPE_WIDTH, pipe.top);
        ctx.fillRect(pipe.x, pipe.top + GAP, PIPE_WIDTH, H - pipe.top - GAP);
        ctx.fillStyle = '#6b6580';
        ctx.fillRect(pipe.x - 4, pipe.top - 14, PIPE_WIDTH + 8, 14);
        ctx.fillRect(pipe.x - 4, pipe.top + GAP, PIPE_WIDTH + 8, 14);
    }

    function drawBat() {
        ctx.font = BAT_SIZE + 'px serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText('🦇', BAT_X, batY);
    }

    function hits(pipe) {
        const r = BAT_SIZE / 2 - 4;
        const overlapsX = BAT_X + r > pipe.x && BAT_X - r < pipe.x + PIPE_WIDTH;
        return overlapsX && (batY - r < pipe.top || batY + r > pipe.top + GAP);
    }

    function endGame() {
        running = false;
        cancelAnimationFrame(animationId);
        message.textContent = 'You crashed! Final score: ' + score;
        startButton.disabled = false;
        document.querySelector('.share-buttons').style.display = 'flex'; // Show share buttons
    }

    function loop(now) {
        if (waiting) {
            // Hover gently until the player flaps for the first time
            batY = H / 2 + Math.sin(now / 200) * 6;
            drawBackground();
            drawBat();
            animationId = requestAnimationFrame(loop);
            return;
        }

        // Frame-rate independent step (1 = one frame at 60fps)
        const step = Math.min((now - lastTime) / (1000 / 60), 3);
        lastTime = now;

        velocity += GRAVITY * step;
        batY += velocity * step;

        const speed = 2 + Math.min(score * 0.05, 1.5);
        distance += speed * step;
        if (pipes.length === 0 || distance >= PIPE_SPACING) {
            addPipe();
            distance = 0;
        }
        pipes.forEach(pipe => pipe.x -= speed * step);
        pipes = pipes.filter(pipe => pipe.x > -PIPE_WIDTH - 10);

        drawBackground();
        pipes.forEach(drawPipe);
        drawBat();

        for (const pipe of pipes) {
            if (hits(pipe)) {
                endGame();
                return;
            }
            if (!pipe.passed && pipe.x + PIPE_WIDTH < BAT_X) {
                pipe.passed = true;
                score++;
                scoreDisplay.textContent = score;
                hiddenScore.value = score; // Update hidden score field
            }
        }

        if (batY < 0 || batY > H) {
            endGame();
            return;
        }

        animationId = requestAnimationFrame(loop);
    }

    function startGame() {
        reset();
        running = true;
        waiting = true;
        startButton.disabled = true;
        message.textContent = 'Tap, click or press Space to start flying!';
        document.querySelector('.share-buttons').style.display = 'none';
        animationId = requestAnimationFrame(loop);
    }

    canvas.addEventListener('mousedown', flap);
    canvas.addEventListener('touchstart', event => {
        event.preventDefault();
        flap();
    }, { passive: false });
    document.addEventListener('keydown', event => {
        if (event.code === 'Space' && running) {
            event.preventDefault();
            flap();
        }
    });
    startButton.addEventListener('click', startGame);

    running = false;
    waiting = false;
    reset();
    drawBackground();
    drawBat();