Загрузка данных


<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Космический Полёт</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/fredoka@5.0.5/index.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -webkit-user-select: none;
            touch-action: none;
        }

        body {
            background-color: #050510;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: 'Fredoka', sans-serif;
            overflow: hidden;
        }

        #game-container {
            position: relative;
            width: 100%;
            max-width: 800px;
            aspect-ratio: 4 / 3;
            background: #000;
            border-radius: 24px;
            overflow: hidden;
            box-shadow: 0 0 60px rgba(59, 130, 246, 0.3), 0 0 120px rgba(239, 68, 68, 0.1);
        }

        canvas {
            display: block;
            width: 100%;
            height: 100%;
        }

        .ui-layer {
            position: absolute;
            inset: 0;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background: rgba(15, 12, 41, 0.85);
            backdrop-filter: blur(8px);
            z-index: 10;
            transition: opacity 0.3s ease;
        }

        .ui-layer.hidden {
            opacity: 0;
            pointer-events: none;
        }

        h1 {
            font-size: clamp(2.5rem, 8vw, 4.5rem);
            font-weight: 900;
            background: linear-gradient(135deg, #ef4444 0%, #3b82f6 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            margin-bottom: 1rem;
            text-align: center;
            text-shadow: 0 4px 20px rgba(239, 68, 68, 0.3);
            letter-spacing: 2px;
        }

        p.subtitle {
            color: #cbd5e1;
            font-size: clamp(1rem, 3vw, 1.25rem);
            margin-bottom: 2.5rem;
            text-align: center;
            max-width: 80%;
            line-height: 1.5;
        }

        .btn {
            background: linear-gradient(135deg, #ef4444 0%, #3b82f6 100%);
            color: white;
            font-family: 'Fredoka', sans-serif;
            font-size: clamp(1.2rem, 4vw, 1.5rem);
            font-weight: 700;
            padding: 16px 48px;
            border: none;
            border-radius: 50px;
            cursor: pointer;
            box-shadow: 0 8px 25px rgba(239, 68, 68, 0.4), 0 4px 10px rgba(59, 130, 246, 0.3);
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        .btn:hover {
            transform: translateY(-3px) scale(1.05);
            box-shadow: 0 12px 35px rgba(239, 68, 68, 0.5), 0 6px 15px rgba(59, 130, 246, 0.4);
        }

        .btn:active {
            transform: translateY(1px) scale(0.98);
        }

        #countdown-display {
            font-size: clamp(5rem, 15vw, 8rem);
            font-weight: 900;
            color: #fbbf24;
            text-shadow: 0 0 40px rgba(251, 191, 36, 0.6);
            animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
        }

        @keyframes popIn {
            0% { transform: scale(0.5); opacity: 0; }
            100% { transform: scale(1); opacity: 1; }
        }

        #score-hud {
            position: absolute;
            top: 24px;
            left: 50%;
            transform: translateX(-50%);
            font-size: clamp(2rem, 6vw, 3.5rem);
            font-weight: 900;
            color: white;
            text-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
            z-index: 5;
            pointer-events: none;
        }

        .score-label {
            font-size: 1rem;
            color: #94a3b8;
            text-align: center;
            margin-bottom: 0.5rem;
            text-transform: uppercase;
            letter-spacing: 2px;
        }

        .final-score {
            font-size: clamp(3rem, 10vw, 5rem);
            font-weight: 900;
            color: #fbbf24;
            margin-bottom: 2rem;
            text-shadow: 0 0 30px rgba(251, 191, 36, 0.4);
        }
    </style>
</head>
<body>

    <div id="game-container">
        <canvas id="gameCanvas" width="800" height="600"></canvas>
        
        <div id="score-hud" class="hidden">0</div>

        <div id="start-screen" class="ui-layer">
            <h1>КОСМИЧЕСКИЙ<br>ПОЛЁТ</h1>
            <p class="subtitle">Нажимай ПРОБЕЛ или кликай по экрану,<br>чтобы управлять кораблём и избегать колонн!</p>
            <button class="btn" id="start-btn">Начать игру</button>
        </div>

        <div id="countdown-screen" class="ui-layer hidden">
            <div id="countdown-display">3</div>
        </div>

        <div id="gameover-screen" class="ui-layer hidden">
            <h1>ИГРА ОКОНЧЕНА</h1>
            <div class="score-label">Твой счёт</div>
            <div class="final-score" id="final-score">0</div>
            <button class="btn" id="restart-btn">Начать заново</button>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        
        const startScreen = document.getElementById('start-screen');
        const countdownScreen = document.getElementById('countdown-screen');
        const countdownDisplay = document.getElementById('countdown-display');
        const gameoverScreen = document.getElementById('gameover-screen');
        const scoreHud = document.getElementById('score-hud');
        const finalScoreDisplay = document.getElementById('final-score');
        const startBtn = document.getElementById('start-btn');
        const restartBtn = document.getElementById('restart-btn');

        let gameState = 'MENU'; // MENU, COUNTDOWN, PLAYING, GAMEOVER
        let score = 0;
        let frames = 0;
        let animationId;

        // Game Constants
        const GRAVITY = 0.25;
        const JUMP_STRENGTH = -6.5;
        const OBSTACLE_SPEED = 3.5;
        const OBSTACLE_WIDTH = 70;
        const OBSTACLE_GAP = 170;
        const OBSTACLE_SPAWN_RATE = 110;

        class Bird {
            constructor() {
                this.x = 150;
                this.y = canvas.height / 2;
                this.velocity = 0;
                this.radius = 22;
                this.angle = 0;
                this.wingOffset = 0;
            }

            update() {
                this.velocity += GRAVITY;
                this.y += this.velocity;
                
                // Rotation based on velocity
                this.angle = Math.min(Math.PI / 4, Math.max(-Math.PI / 4, (this.velocity * 0.08)));
                
                // Wing animation
                this.wingOffset = Math.sin(frames * 0.3) * 5;
            }

            jump() {
                this.velocity = JUMP_STRENGTH;
            }

            draw(ctx) {
                ctx.save();
                ctx.translate(this.x, this.y);
                ctx.rotate(this.angle);

                // Glow effect
                ctx.shadowColor = 'rgba(59, 130, 246, 0.6)';
                ctx.shadowBlur = 20;

                // Body (Red-Blue gradient)
                const grad = ctx.createLinearGradient(-this.radius, -this.radius, this.radius, this.radius);
                grad.addColorStop(0, '#ef4444');
                grad.addColorStop(0.5, '#a855f7');
                grad.addColorStop(1, '#3b82f6');
                
                ctx.beginPath();
                ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
                ctx.fillStyle = grad;
                ctx.fill();
                
                ctx.shadowBlur = 0; // Reset shadow for details
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 2.5;
                ctx.stroke();

                // Wing
                ctx.beginPath();
                ctx.ellipse(-8, 6 + this.wingOffset, 14, 9, -0.3, 0, Math.PI * 2);
                ctx.fillStyle = '#fbbf24';
                ctx.fill();
                ctx.strokeStyle = '#f59e0b';
                ctx.lineWidth = 2;
                ctx.stroke();

                // Eyes (White)
                ctx.fillStyle = '#ffffff';
                ctx.beginPath();
                ctx.arc(8, -7, 8, 0, Math.PI * 2);
                ctx.arc(8, 7, 8, 0, Math.PI * 2);
                ctx.fill();

                // Pupils (Black)
                ctx.fillStyle = '#0f172a';
                ctx.beginPath();
                ctx.arc(10, -7, 3.5, 0, Math.PI * 2);
                ctx.arc(10, 7, 3.5, 0, Math.PI * 2);
                ctx.fill();

                // Beak
                ctx.beginPath();
                ctx.moveTo(16, -2);
                ctx.lineTo(28, 2);
                ctx.lineTo(16, 6);
                ctx.closePath();
                ctx.fillStyle = '#f97316';
                ctx.fill();
                ctx.strokeStyle = '#c2410c';
                ctx.lineWidth = 1.5;
                ctx.stroke();

                ctx.restore();
            }
        }

        class Obstacle {
            constructor(x) {
                this.x = x;
                this.width = OBSTACLE_WIDTH;
                this.gap = OBSTACLE_GAP;
                this.minHeight = 60;
                this.maxHeight = canvas.height - this.gap - this.minHeight;
                this.topHeight = Math.random() * (this.maxHeight - this.minHeight) + this.minHeight;
                this.bottomY = this.topHeight + this.gap;
                this.passed = false;
            }

            update() {
                this.x -= OBSTACLE_SPEED;
            }

            draw(ctx) {
                const drawPillar = (x, y, w, h, isTop) => {
                    // Main stone body
                    ctx.fillStyle = '#6b7280';
                    ctx.fillRect(x, y, w, h);
                    
                    // 3D Highlight (left)
                    ctx.fillStyle = '#9ca3af';
                    ctx.fillRect(x, y, 12, h);
                    
                    // 3D Shadow (right)
                    ctx.fillStyle = '#4b5563';
                    ctx.fillRect(x + w - 12, y, 12, h);

                    // Cap
                    const capHeight = 24;
                    const capY = isTop ? y + h - capHeight : y;
                    ctx.fillStyle = '#6b7280';
                    ctx.fillRect(x - 6, capY, w + 12, capHeight);
                    
                    // Cap highlight/shadow
                    ctx.fillStyle = '#9ca3af';
                    ctx.fillRect(x - 6, capY, 12, capHeight);
                    ctx.fillStyle = '#4b5563';
                    ctx.fillRect(x + w - 6, capY, 12, capHeight);

                    // Stone details (cracks/lines)
                    ctx.strokeStyle = '#374151';
                    ctx.lineWidth = 2;
                    ctx.beginPath();
                    if (isTop) {
                        ctx.moveTo(x + 20, y + 30);
                        ctx.lineTo(x + 40, y + 50);
                        ctx.moveTo(x + 25, y + 80);
                        ctx.lineTo(x + 50, y + 90);
                    } else {
                        ctx.moveTo(x + 30, y + 40);
                        ctx.lineTo(x + 45, y + 60);
                        ctx.moveTo(x + 20, y + 100);
                        ctx.lineTo(x + 35, y + 110);
                    }
                    ctx.stroke();
                };

                drawPillar(this.x, 0, this.width, this.topHeight, true);
                drawPillar(this.x, this.bottomY, this.width, canvas.height - this.bottomY, false);
            }
        }

        class Star {
            constructor() {
                this.x = Math.random() * canvas.width;
                this.y = Math.random() * canvas.height;
                this.size = Math.random() * 2.5 + 0.5;
                this.speed = Math.random() * 0.8 + 0.2;
                this.opacity = Math.random() * 0.8 + 0.2;
                this.twinkleSpeed = Math.random() * 0.05 + 0.01;
                this.twinkleDir = 1;
            }

            update() {
                this.x -= this.speed;
                if (this.x < 0) {
                    this.x = canvas.width;
                    this.y = Math.random() * canvas.height;
                }
                
                this.opacity += this.twinkleSpeed * this.twinkleDir;
                if (this.opacity >= 1 || this.opacity <= 0.2) {
                    this.twinkleDir *= -1;
                }
            }

            draw(ctx) {
                ctx.fillStyle = `rgba(255, 255, 255, ${this.opacity})`;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        let bird;
        let obstacles = [];
        let stars = [];

        function init() {
            bird = new Bird();
            obstacles = [];
            stars = Array.from({ length: 120 }, () => new Star());
            score = 0;
            frames = 0;
            scoreHud.textContent = '0';
        }

        function checkCollision(circle, rectX, rectY, rectW, rectH) {
            let testX = circle.x;
            let testY = circle.y;

            if (circle.x < rectX) testX = rectX;
            else if (circle.x > rectX + rectW) testX = rectX + rectW;

            if (circle.y < rectY) testY = rectY;
            else if (circle.y > rectY + rectH) testY = rectY + rectH;

            const distX = circle.x - testX;
            const distY = circle.y - testY;
            const distance = Math.sqrt((distX * distX) + (distY * distY));

            return distance <= circle.radius - 2; // -2 for slight forgiveness
        }

        function gameOver() {
            gameState = 'GAMEOVER';
            finalScoreDisplay.textContent = score;
            scoreHud.classList.add('hidden');
            gameoverScreen.classList.remove('hidden');
        }

        function startCountdown() {
            gameState = 'COUNTDOWN';
            startScreen.classList.add('hidden');
            countdownScreen.classList.remove('hidden');
            
            let count = 3;
            countdownDisplay.textContent = count;
            
            // Reset animation
            countdownDisplay.style.animation = 'none';
            countdownDisplay.offsetHeight; // Trigger reflow
            countdownDisplay.style.animation = 'popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards';

            const timer = setInterval(() => {
                count--;
                if (count > 0) {
                    countdownDisplay.textContent = count;
                    countdownDisplay.style.animation = 'none';
                    countdownDisplay.offsetHeight;
                    countdownDisplay.style.animation = 'popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards';
                } else if (count === 0) {
                    countdownDisplay.textContent = 'ВПЕРЁД!';
                    countdownDisplay.style.color = '#22c55e';
                    countdownDisplay.style.animation = 'none';
                    countdownDisplay.offsetHeight;
                    countdownDisplay.style.animation = 'popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards';
                } else {
                    clearInterval(timer);
                    countdownScreen.classList.add('hidden');
                    countdownDisplay.style.color = '#fbbf24'; // Reset color
                    gameState = 'PLAYING';
                    scoreHud.classList.remove('hidden');
                    bird.jump(); // Initial jump
                }
            }, 800);
        }

        function drawBackground() {
            // Deep space gradient
            const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
            grad.addColorStop(0, '#0f0c29');
            grad.addColorStop(0.5, '#302b63');
            grad.addColorStop(1, '#24243e');
            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // Distant nebula effect
            const nebula = ctx.createRadialGradient(canvas.width * 0.8, canvas.height * 0.2, 0, canvas.width * 0.8, canvas.height * 0.2, 300);
            nebula.addColorStop(0, 'rgba(239, 68, 68, 0.15)');
            nebula.addColorStop(1, 'rgba(0, 0, 0, 0)');
            ctx.fillStyle = nebula;
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            const nebula2 = ctx.createRadialGradient(canvas.width * 0.2, canvas.height * 0.8, 0, canvas.width * 0.2, canvas.height * 0.8, 250);
            nebula2.addColorStop(0, 'rgba(59, 130, 246, 0.15)');
            nebula2.addColorStop(1, 'rgba(0, 0, 0, 0)');
            ctx.fillStyle = nebula2;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }

        function gameLoop() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            drawBackground();

            // Update and draw stars (always animate for visual appeal)
            stars.forEach(star => {
                star.update();
                star.draw(ctx);
            });

            if (gameState === 'PLAYING') {
                bird.update();
                
                // Spawn obstacles
                if (frames % OBSTACLE_SPAWN_RATE === 0) {
                    obstacles.push(new Obstacle(canvas.width));
                }

                // Update obstacles
                for (let i = obstacles.length - 1; i >= 0; i--) {
                    const obs = obstacles[i];
                    obs.update();

                    // Score counting
                    if (!obs.passed && obs.x + obs.width < bird.x) {
                        obs.passed = true;
                        score++;
                        scoreHud.textContent = score;
                    }

                    // Remove off-screen obstacles
                    if (obs.x + obs.width < 0) {
                        obstacles.splice(i, 1);
                        continue;
                    }

                    // Collision detection
                    if (checkCollision(bird, obs.x, 0, obs.width, obs.topHeight) ||
                        checkCollision(bird, obs.x, obs.bottomY, obs.width, canvas.height - obs.bottomY)) {
                        gameOver();
                    }
                }

                // Floor/Ceiling collision
                if (bird.y + bird.radius >= canvas.height || bird.y - bird.radius <= 0) {
                    gameOver();
                }

                frames++;
            } else if (gameState === 'MENU' || gameState === 'GAMEOVER') {
                // Idle animation for bird in menu
                bird.y = (canvas.height / 2) + Math.sin(Date.now() * 0.003) * 15;
                bird.wingOffset = Math.sin(Date.now() * 0.01) * 5;
            }

            // Draw obstacles
            obstacles.forEach(obs => obs.draw(ctx));

            // Draw bird
            bird.draw(ctx);

            animationId = requestAnimationFrame(gameLoop);
        }

        function handleInput(e) {
            if (e.type === 'keydown' && e.code !== 'Space') return;
            if (e.type === 'keydown') e.preventDefault(); // Prevent scrolling

            if (gameState === 'MENU') {
                startCountdown();
            } else if (gameState === 'PLAYING') {
                bird.jump();
            } else if (gameState === 'GAMEOVER') {
                init();
                gameState = 'MENU';
                gameoverScreen.classList.add('hidden');
                startScreen.classList.remove('hidden');
            }
        }

        // Event Listeners
        window.addEventListener('keydown', handleInput);
        canvas.addEventListener('mousedown', handleInput);
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            handleInput(e);
        }, { passive: false });

        startBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            startCountdown();
        });

        restartBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            init();
            gameState = 'MENU';
            gameoverScreen.classList.add('hidden');
            startScreen.classList.remove('hidden');
        });

        // Initialize and start loop
        init();
        gameLoop();
    </script>
</body>
</html>