sapbot

Neon Flap

Feb 13th, 2026
48
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 17.01 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  6.     <title>Neon Flappy Bird</title>
  7.     <script src="https://cdn.tailwindcss.com"></script>
  8.     <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
  9.     <style>
  10.         @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
  11.        
  12.         body {
  13.             margin: 0;
  14.             overflow: hidden;
  15.             background-color: #1a1a2e;
  16.             font-family: 'Press Start 2P', cursive;
  17.             touch-action: none; /* Prevent pull-to-refresh on mobile */
  18.         }
  19.  
  20.         #game-container {
  21.             position: relative;
  22.             width: 100vw;
  23.             height: 100vh;
  24.             display: flex;
  25.             justify-content: center;
  26.             align-items: center;
  27.             background: linear-gradient(to bottom, #0f0c29, #302b63, #24243e);
  28.         }
  29.  
  30.         canvas {
  31.             box-shadow: 0 0 20px rgba(0,0,0,0.5);
  32.             max-width: 100%;
  33.             max-height: 100%;
  34.         }
  35.  
  36.         .ui-layer {
  37.             position: absolute;
  38.             top: 0;
  39.             left: 0;
  40.             width: 100%;
  41.             height: 100%;
  42.             pointer-events: none;
  43.             display: flex;
  44.             flex-direction: column;
  45.             justify-content: center;
  46.             align-items: center;
  47.             text-align: center;
  48.         }
  49.  
  50.         .hidden {
  51.             display: none !important;
  52.         }
  53.  
  54.         .neon-text {
  55.             color: #fff;
  56.             text-shadow:
  57.                 0 0 5px #fff,
  58.                 0 0 10px #fff,
  59.                 0 0 20px #ff00de,
  60.                 0 0 30px #ff00de,
  61.                 0 0 40px #ff00de;
  62.         }
  63.  
  64.         .neon-button {
  65.             pointer-events: auto;
  66.             background: transparent;
  67.             border: 4px solid #fff;
  68.             color: #fff;
  69.             padding: 15px 30px;
  70.             font-family: 'Press Start 2P', cursive;
  71.             font-size: 16px;
  72.             cursor: pointer;
  73.             text-transform: uppercase;
  74.             box-shadow: 0 0 10px #fff, inset 0 0 10px #fff;
  75.             text-shadow: 0 0 5px #fff;
  76.             transition: all 0.2s;
  77.             margin-top: 20px;
  78.         }
  79.  
  80.         .neon-button:hover {
  81.             background: #fff;
  82.             color: #000;
  83.             box-shadow: 0 0 20px #fff, 0 0 40px #ff00de, inset 0 0 10px #fff;
  84.         }
  85.  
  86.         #score-display {
  87.             position: absolute;
  88.             top: 20px;
  89.             left: 50%;
  90.             transform: translateX(-50%);
  91.             font-size: 48px;
  92.             color: white;
  93.             z-index: 10;
  94.             text-shadow: 2px 2px 0 #000;
  95.         }
  96.  
  97.         /* Scanline effect */
  98.         .scanlines {
  99.             position: absolute;
  100.             top: 0;
  101.             left: 0;
  102.             width: 100%;
  103.             height: 100%;
  104.             background: linear-gradient(
  105.                 to bottom,
  106.                 rgba(255,255,255,0),
  107.                 rgba(255,255,255,0) 50%,
  108.                 rgba(0,0,0,0.1) 50%,
  109.                 rgba(0,0,0,0.1)
  110.             );
  111.             background-size: 100% 4px;
  112.             pointer-events: none;
  113.             z-index: 5;
  114.         }
  115.     </style>
  116. </head>
  117. <body>
  118.  
  119. <div id="game-container">
  120.     <canvas id="gameCanvas"></canvas>
  121.     <div class="scanlines"></div>
  122.    
  123.     <div id="score-display">0</div>
  124.  
  125.     <div id="start-screen" class="ui-layer">
  126.         <h1 class="text-4xl md:text-6xl mb-4 neon-text">NEON FLAP</h1>
  127.         <p class="text-sm md:text-base text-gray-300 mb-8">TAP OR SPACE TO FLY</p>
  128.         <button id="start-btn" class="neon-button">START GAME</button>
  129.     </div>
  130.  
  131.     <div id="game-over-screen" class="ui-layer hidden">
  132.         <h1 class="text-4xl md:text-5xl mb-4 text-red-500" style="text-shadow: 2px 2px 0 #000;">GAME OVER</h1>
  133.         <div class="bg-black bg-opacity-50 p-6 border-2 border-white mb-4">
  134.             <p class="text-yellow-400 mb-2">SCORE: <span id="final-score">0</span></p>
  135.             <p class="text-cyan-400">BEST: <span id="best-score">0</span></p>
  136.         </div>
  137.         <button id="restart-btn" class="neon-button">TRY AGAIN</button>
  138.     </div>
  139. </div>
  140.  
  141. <script>
  142.     // --- Audio System (Tone.js) ---
  143.     let synth, membrane, metal;
  144.     let audioInitialized = false;
  145.  
  146.     async function initAudio() {
  147.         if (audioInitialized) return;
  148.         await Tone.start();
  149.        
  150.         // Synth for flapping
  151.         synth = new Tone.PolySynth(Tone.Synth, {
  152.             oscillator: { type: "square" },
  153.             envelope: { attack: 0.01, decay: 0.1, sustain: 0, release: 0.1 }
  154.         }).toDestination();
  155.         synth.volume.value = -10;
  156.  
  157.         // Membrane for scoring
  158.         membrane = new Tone.MembraneSynth().toDestination();
  159.         membrane.volume.value = -5;
  160.  
  161.         // Metal for crashing
  162.         metal = new Tone.MetalSynth({
  163.             frequency: 200,
  164.             envelope: { attack: 0.001, decay: 0.1, release: 0.01 },
  165.             harmonicity: 5.1,
  166.             modulationIndex: 32,
  167.             resonance: 4000,
  168.             octaves: 1.5
  169.         }).toDestination();
  170.         metal.volume.value = -5;
  171.  
  172.         audioInitialized = true;
  173.     }
  174.  
  175.     function playFlapSound() {
  176.         if(audioInitialized) synth.triggerAttackRelease("C4", "16n");
  177.     }
  178.  
  179.     function playScoreSound() {
  180.         if(audioInitialized) membrane.triggerAttackRelease("C5", "32n");
  181.     }
  182.  
  183.     function playCrashSound() {
  184.         if(audioInitialized) metal.triggerAttackRelease("32n");
  185.     }
  186.  
  187.     // --- Game Constants & Setup ---
  188.    const canvas = document.getElementById('gameCanvas');
  189.     const ctx = canvas.getContext('2d');
  190.     const startScreen = document.getElementById('start-screen');
  191.     const gameOverScreen = document.getElementById('game-over-screen');
  192.     const scoreDisplay = document.getElementById('score-display');
  193.     const finalScoreDisplay = document.getElementById('final-score');
  194.     const bestScoreDisplay = document.getElementById('best-score');
  195.     const startBtn = document.getElementById('start-btn');
  196.     const restartBtn = document.getElementById('restart-btn');
  197.  
  198.     // Responsive Canvas
  199.     let width, height;
  200.     function resize() {
  201.         width = Math.min(window.innerWidth, 450); // Max width for mobile feel
  202.         height = window.innerHeight;
  203.         canvas.width = width;
  204.         canvas.height = height;
  205.     }
  206.     window.addEventListener('resize', resize);
  207.     resize();
  208.  
  209.     // Game State
  210.     let frames = 0;
  211.     let score = 0;
  212.     let highScore = 0;
  213.     let gameState = 'START'; // START, PLAYING, GAMEOVER
  214.     let gameSpeed = 3;
  215.     let particles = [];
  216.  
  217.     // --- Entities ---
  218.  
  219.     const bird = {
  220.         x: 50,
  221.         y: 150,
  222.         w: 34,
  223.         h: 24,
  224.         radius: 12,
  225.         velocity: 0,
  226.         gravity: 0.25,
  227.         jump: 4.6,
  228.         rotation: 0,
  229.         color: '#fbbf24', // Tailwind amber-400
  230.        
  231.         draw: function() {
  232.             ctx.save();
  233.             ctx.translate(this.x, this.y);
  234.             // Rotation based on velocity
  235.             this.rotation = Math.min(Math.PI / 4, Math.max(-Math.PI / 4, (this.velocity * 0.1)));
  236.             ctx.rotate(this.rotation);
  237.  
  238.             // Glow
  239.             ctx.shadowBlur = 15;
  240.             ctx.shadowColor = this.color;
  241.  
  242.             // Body
  243.             ctx.fillStyle = this.color;
  244.             ctx.beginPath();
  245.             ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
  246.             ctx.fill();
  247.  
  248.             // Eye
  249.             ctx.fillStyle = '#fff';
  250.             ctx.beginPath();
  251.             ctx.arc(6, -6, 5, 0, Math.PI * 2);
  252.             ctx.fill();
  253.             ctx.fillStyle = '#000';
  254.             ctx.beginPath();
  255.             ctx.arc(8, -6, 2, 0, Math.PI * 2);
  256.             ctx.fill();
  257.  
  258.             // Beak
  259.             ctx.fillStyle = '#ef4444'; // Red-500
  260.             ctx.beginPath();
  261.             ctx.moveTo(6, 2);
  262.             ctx.lineTo(16, 6);
  263.             ctx.lineTo(6, 10);
  264.             ctx.fill();
  265.  
  266.             // Wing
  267.             ctx.fillStyle = '#fcd34d'; // Amber-300
  268.             ctx.beginPath();
  269.             ctx.ellipse(-6, 4, 8, 5, 0, 0, Math.PI * 2);
  270.             ctx.fill();
  271.  
  272.             ctx.restore();
  273.         },
  274.        
  275.         update: function() {
  276.             this.velocity += this.gravity;
  277.             this.y += this.velocity;
  278.  
  279.             // Floor collision
  280.             if (this.y + this.radius >= height - fg.h) {
  281.                 this.y = height - fg.h - this.radius;
  282.                 gameOver();
  283.             }
  284.            
  285.             // Ceiling collision (optional, but good for gameplay)
  286.             if (this.y - this.radius <= 0) {
  287.                this.y = this.radius;
  288.                this.velocity = 0;
  289.            }
  290.        },
  291.        
  292.        flap: function() {
  293.            this.velocity = -this.jump;
  294.            createParticles(this.x, this.y + 10, 5, '#fff');
  295.            playFlapSound();
  296.        }
  297.    };
  298.  
  299.    const pipes = {
  300.        position: [],
  301.        w: 60,
  302.        gap: 160,
  303.        dx: 3,
  304.        color: '#10b981', // Emerald-500
  305.  
  306.        reset: function() {
  307.            this.position = [];
  308.        },
  309.  
  310.        draw: function() {
  311.            ctx.shadowBlur = 10;
  312.            ctx.shadowColor = this.color;
  313.            
  314.            for (let i = 0; i < this.position.length; i++) {
  315.                let p = this.position[i];
  316.                let topY = p.y;
  317.                let bottomY = p.y + this.gap;
  318.  
  319.                // Top Pipe
  320.                ctx.fillStyle = this.color;
  321.                ctx.fillRect(p.x, 0, this.w, topY);
  322.                
  323.                // Top Pipe Cap
  324.                ctx.fillStyle = '#059669'; // Darker green
  325.                ctx.fillRect(p.x - 2, topY - 20, this.w + 4, 20);
  326.  
  327.                // Bottom Pipe
  328.                ctx.fillStyle = this.color;
  329.                ctx.fillRect(p.x, bottomY, this.w, height - bottomY - fg.h);
  330.  
  331.                // Bottom Pipe Cap
  332.                ctx.fillStyle = '#059669';
  333.                ctx.fillRect(p.x - 2, bottomY, this.w + 4, 20);
  334.            }
  335.            ctx.shadowBlur = 0;
  336.        },
  337.        
  338.        update: function() {
  339.            // Add new pipe
  340.            if (frames % 100 === 0) {
  341.                // Calculate random Y position for the gap
  342.                // Ensure gap isn't too high or too low
  343.                let minY = -150;
  344.                let maxY = -20;
  345.                let y = minY + Math.random() * (maxY - minY);
  346.                
  347.                this.position.push({
  348.                    x: width,
  349.                    y: y + 200, // Shift down
  350.                    passed: false
  351.                });
  352.            }
  353.            for (let i = 0; i < this.position.length; i++) {
  354.                let p = this.position[i];
  355.                p.x -= this.dx;
  356.                // Collision Detection
  357.                // X-axis overlap
  358.                let leftEdge = p.x;
  359.                let rightEdge = p.x + this.w;
  360.                let birdLeft = bird.x - bird.radius + 4; // +4 for forgiving hitbox
  361.                let birdRight = bird.x + bird.radius - 4;
  362.                if (birdRight > leftEdge && birdLeft < rightEdge) {
  363.                    // Y-axis overlap (Top Pipe)
  364.                    if (bird.y - bird.radius < p.y) {
  365.                        gameOver();
  366.                     }
  367.                     // Y-axis overlap (Bottom Pipe)
  368.                     if (bird.y + bird.radius > p.y + this.gap) {
  369.                         gameOver();
  370.                     }
  371.                 }
  372.  
  373.                 // Score counting
  374.                 if (p.x + this.w < bird.x && !p.passed) {
  375.                    score++;
  376.                    p.passed = true;
  377.                    scoreDisplay.innerText = score;
  378.                    playScoreSound();
  379.                    createParticles(bird.x, bird.y, 10, '#fbbf24'); // Confetti
  380.                }
  381.  
  382.                // Remove off-screen pipes
  383.                if (p.x + this.w <= 0) {
  384.                    this.position.shift();
  385.                    i--;
  386.                }
  387.            }
  388.        }
  389.    };
  390.  
  391.    const fg = {
  392.        h: 100, // Floor height
  393.        x: 0,
  394.        dx: 3,
  395.        color: '#374151', // Gray-700
  396.  
  397.        draw: function() {
  398.            ctx.fillStyle = this.color;
  399.            ctx.fillRect(0, height - this.h, width, this.h);
  400.            
  401.            // Detail lines on floor
  402.            ctx.fillStyle = '#4b5563';
  403.            let offset = (frames * this.dx) % 20;
  404.            for(let i = -20; i < width; i+=20) {
  405.                ctx.fillRect(i - offset, height - this.h, 10, this.h);
  406.            }
  407.        },
  408.        
  409.        update: function() {
  410.            // Just visual scrolling handled in draw
  411.        }
  412.    };
  413.  
  414.    const bg = {
  415.        draw: function() {
  416.            // Background is handled by CSS, but we can add stars here
  417.            ctx.fillStyle = '#fff';
  418.            for(let i=0; i<20; i++) {
  419.                // Pseudo-random stars based on frame to make them twinkle or move slowly
  420.                let x = (Math.sin(i * 132) * width + frames * 0.1) % width;
  421.                let y = (Math.cos(i * 453) * (height/2));
  422.                let size = Math.random() * 2;
  423.                if(x < 0) x += width;
  424.                ctx.globalAlpha = Math.random() * 0.5 + 0.2;
  425.                ctx.beginPath();
  426.                ctx.arc(x, y, size, 0, Math.PI*2);
  427.                ctx.fill();
  428.            }
  429.            ctx.globalAlpha = 1;
  430.        }
  431.    }
  432.  
  433.    // --- Particle System ---
  434.    function createParticles(x, y, count, color) {
  435.        for (let i = 0; i < count; i++) {
  436.            particles.push({
  437.                x: x,
  438.                y: y,
  439.                vx: (Math.random() - 0.5) * 5,
  440.                vy: (Math.random() - 0.5) * 5,
  441.                life: 1.0,
  442.                color: color
  443.            });
  444.        }
  445.    }
  446.  
  447.    function updateParticles() {
  448.        for (let i = 0; i < particles.length; i++) {
  449.            let p = particles[i];
  450.            p.x += p.vx;
  451.            p.y += p.vy;
  452.            p.life -= 0.02;
  453.            
  454.            ctx.globalAlpha = p.life;
  455.            ctx.fillStyle = p.color;
  456.            ctx.fillRect(p.x, p.y, 4, 4);
  457.            
  458.            if (p.life <= 0) {
  459.                particles.splice(i, 1);
  460.                i--;
  461.            }
  462.        }
  463.        ctx.globalAlpha = 1;
  464.    }
  465.  
  466.    // --- Game Control Functions ---
  467.  
  468.    function loop() {
  469.        // Clear
  470.        ctx.clearRect(0, 0, width, height);
  471.  
  472.        // Draw
  473.        bg.draw();
  474.        pipes.draw();
  475.        fg.draw();
  476.        bird.draw();
  477.        updateParticles();
  478.  
  479.        // Update
  480.        if (gameState === 'PLAYING') {
  481.            pipes.update();
  482.            bird.update();
  483.            fg.update();
  484.            frames++;
  485.        } else if (gameState === 'START') {
  486.            // Hover animation
  487.            bird.y = 150 + Math.sin(Date.now() / 300) * 5;
  488.        }
  489.  
  490.        requestAnimationFrame(loop);
  491.    }
  492.  
  493.    function startGame() {
  494.        initAudio();
  495.        gameState = 'PLAYING';
  496.        startScreen.classList.add('hidden');
  497.        gameOverScreen.classList.add('hidden');
  498.        score = 0;
  499.        frames = 0;
  500.        scoreDisplay.innerText = score;
  501.        bird.y = 150;
  502.        bird.velocity = 0;
  503.        pipes.reset();
  504.        bird.flap(); // Initial jump
  505.    }
  506.  
  507.    function gameOver() {
  508.        if (gameState === 'GAMEOVER') return;
  509.        gameState = 'GAMEOVER';
  510.        playCrashSound();
  511.        
  512.        if (score > highScore) {
  513.             highScore = score;
  514.         }
  515.        
  516.         finalScoreDisplay.innerText = score;
  517.         bestScoreDisplay.innerText = highScore;
  518.        
  519.         setTimeout(() => {
  520.             gameOverScreen.classList.remove('hidden');
  521.         }, 500);
  522.     }
  523.  
  524.     function resetGame() {
  525.         startGame();
  526.     }
  527.  
  528.     // --- Input Handling ---
  529.  
  530.     function handleInput(e) {
  531.         if (e.type === 'keydown' && e.code !== 'Space') return;
  532.         if (e.type === 'touchstart') e.preventDefault(); // Prevent zooming/scrolling
  533.  
  534.         switch (gameState) {
  535.             case 'START':
  536.                 // Handled by button, but space can start too
  537.                 if(e.type === 'keydown' || e.type === 'touchstart') startGame();
  538.                 break;
  539.             case 'PLAYING':
  540.                 bird.flap();
  541.                 break;
  542.             case 'GAMEOVER':
  543.                 // Handled by button
  544.                 break;
  545.         }
  546.     }
  547.  
  548.     window.addEventListener('keydown', handleInput);
  549.     canvas.addEventListener('mousedown', handleInput);
  550.     canvas.addEventListener('touchstart', handleInput, {passive: false});
  551.    
  552.     startBtn.addEventListener('click', (e) => {
  553.         e.stopPropagation(); // Prevent triggering canvas click immediately
  554.         startGame();
  555.     });
  556.    
  557.     restartBtn.addEventListener('click', (e) => {
  558.         e.stopPropagation();
  559.         resetGame();
  560.     });
  561.  
  562.     // Start Loop
  563.     loop();
  564.  
  565. </script>
  566. </body>
  567. </html>
Advertisement
Add Comment
Please, Sign In to add comment