Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Quantum Flipper - The Backflipping Bird</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| font-family: 'Press Start 2P', cursive; | |
| background-color: #0f0f1a; | |
| color: #e0e0ff; | |
| } | |
| #gameCanvas { | |
| display: block; | |
| background: linear-gradient(135deg, #000428, #004e92); | |
| } | |
| .universe-portal { | |
| position: absolute; | |
| width: 100px; | |
| height: 100px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle, rgba(0,255,255,0.8) 0%, rgba(0,0,255,0) 70%); | |
| box-shadow: 0 0 30px cyan; | |
| animation: pulse 2s infinite alternate; | |
| z-index: 10; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); opacity: 0.8; } | |
| 100% { transform: scale(1.2); opacity: 1; } | |
| } | |
| .streak-effect { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); | |
| pointer-events: none; | |
| z-index: 5; | |
| } | |
| .turtle { | |
| position: absolute; | |
| width: 40px; | |
| height: 30px; | |
| background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 80"><path fill="%234CAF50" d="M20,40 Q50,10 80,40 Q50,70 20,40 Z"/><circle cx="35" cy="35" r="5" fill="black"/><circle cx="65" cy="35" r="5" fill="black"/></svg>'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| z-index: 2; | |
| } | |
| .bird { | |
| position: absolute; | |
| width: 50px; | |
| height: 40px; | |
| background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 80"><path fill="%23FF5722" d="M10,40 L40,20 L90,40 L40,60 Z"/><circle cx="70" cy="40" r="10" fill="%23FF9800"/><circle cx="75" cy="35" r="3" fill="white"/><circle cx="75" cy="35" r="1" fill="black"/></svg>'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| z-index: 3; | |
| transition: transform 0.2s ease; | |
| } | |
| .flipping { | |
| animation: backflip 0.5s linear; | |
| } | |
| @keyframes backflip { | |
| 0% { transform: rotateY(0deg); } | |
| 100% { transform: rotateY(360deg); } | |
| } | |
| .game-ui { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 100; | |
| } | |
| .universe-indicator { | |
| position: absolute; | |
| bottom: 20px; | |
| right: 20px; | |
| font-size: 14px; | |
| background: rgba(0,0,0,0.7); | |
| padding: 10px; | |
| border-radius: 10px; | |
| border: 2px solid cyan; | |
| } | |
| .streak-counter { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| font-size: 18px; | |
| color: gold; | |
| text-shadow: 0 0 5px rgba(255,215,0,0.7); | |
| } | |
| .game-over { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0,0,0,0.8); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 200; | |
| } | |
| .start-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(135deg, #000428, #004e92); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 300; | |
| } | |
| .title { | |
| font-size: 3rem; | |
| color: #FF5722; | |
| text-shadow: 0 0 10px rgba(255,87,34,0.7); | |
| margin-bottom: 2rem; | |
| animation: glow 1.5s infinite alternate; | |
| } | |
| @keyframes glow { | |
| from { text-shadow: 0 0 5px #FF5722; } | |
| to { text-shadow: 0 0 20px #FF9800, 0 0 30px #FF5722; } | |
| } | |
| .instructions { | |
| background: rgba(0,0,0,0.7); | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin-bottom: 2rem; | |
| max-width: 500px; | |
| text-align: center; | |
| line-height: 1.6; | |
| } | |
| .btn { | |
| background: linear-gradient(135deg, #FF5722, #FF9800); | |
| border: none; | |
| color: white; | |
| padding: 15px 30px; | |
| font-size: 1.2rem; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| font-family: 'Press Start 2P', cursive; | |
| transition: all 0.3s; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.3); | |
| } | |
| .btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 8px 20px rgba(0,0,0,0.4); | |
| } | |
| .btn:active { | |
| transform: translateY(1px); | |
| } | |
| .particle { | |
| position: absolute; | |
| width: 5px; | |
| height: 5px; | |
| background-color: cyan; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| z-index: 1; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="gameCanvas"></canvas> | |
| <div class="game-ui"> | |
| <div id="score" class="text-white text-xl">Score: 0</div> | |
| </div> | |
| <div id="streakCounter" class="streak-counter hidden">Streak: 0</div> | |
| <div id="universeIndicator" class="universe-indicator hidden">Universe: 1</div> | |
| <div id="startScreen" class="start-screen"> | |
| <h1 class="title">QUANTUM FLIPPER</h1> | |
| <div class="instructions"> | |
| <p>Control the quantum bird with arrow keys</p> | |
| <p>Press SPACE to backflip</p> | |
| <p>3 backflips in a row opens a portal to a parallel universe</p> | |
| <p>Avoid the turtles - they'll break your streak!</p> | |
| </div> | |
| <button id="startBtn" class="btn">START ADVENTURE</button> | |
| </div> | |
| <div id="gameOverScreen" class="game-over hidden"> | |
| <h1 class="title">GAME OVER</h1> | |
| <div id="finalScore" class="text-white text-2xl mb-8">Score: 0</div> | |
| <div id="universesVisited" class="text-cyan-300 text-xl mb-8">Universes Visited: 0</div> | |
| <button id="restartBtn" class="btn">FLIP AGAIN</button> | |
| </div> | |
| <script> | |
| // Game variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const startScreen = document.getElementById('startScreen'); | |
| const gameOverScreen = document.getElementById('gameOverScreen'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const restartBtn = document.getElementById('restartBtn'); | |
| const scoreDisplay = document.getElementById('score'); | |
| const finalScoreDisplay = document.getElementById('finalScore'); | |
| const universesVisitedDisplay = document.getElementById('universesVisited'); | |
| const streakCounter = document.getElementById('streakCounter'); | |
| const universeIndicator = document.getElementById('universeIndicator'); | |
| // Set canvas size | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // Game state | |
| let gameRunning = false; | |
| let score = 0; | |
| let streak = 0; | |
| let universe = 1; | |
| let bird = { | |
| x: 100, | |
| y: 100, | |
| width: 50, | |
| height: 40, | |
| speed: 5, | |
| isFlipping: false | |
| }; | |
| let turtles = []; | |
| let portals = []; | |
| let particles = []; | |
| let keys = {}; | |
| let lastFlipTime = 0; | |
| let flipCooldown = 500; // milliseconds | |
| let gameLoopInterval; | |
| let turtleSpawnInterval; | |
| let backgroundHue = 200; | |
| // Event listeners | |
| window.addEventListener('keydown', (e) => { | |
| keys[e.key] = true; | |
| if (e.key === ' ' && gameRunning && !bird.isFlipping && Date.now() - lastFlipTime > flipCooldown) { | |
| performBackflip(); | |
| } | |
| }); | |
| window.addEventListener('keyup', (e) => { | |
| keys[e.key] = false; | |
| }); | |
| startBtn.addEventListener('click', startGame); | |
| restartBtn.addEventListener('click', startGame); | |
| window.addEventListener('resize', () => { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| }); | |
| function startGame() { | |
| // Reset game state | |
| gameRunning = true; | |
| score = 0; | |
| streak = 0; | |
| universe = 1; | |
| bird.x = 100; | |
| bird.y = 100; | |
| turtles = []; | |
| portals = []; | |
| particles = []; | |
| // Update UI | |
| scoreDisplay.textContent = `Score: ${score}`; | |
| streakCounter.classList.add('hidden'); | |
| universeIndicator.textContent = `Universe: ${universe}`; | |
| universeIndicator.classList.remove('hidden'); | |
| // Hide screens | |
| startScreen.classList.add('hidden'); | |
| gameOverScreen.classList.add('hidden'); | |
| // Start game loops | |
| clearInterval(gameLoopInterval); | |
| clearInterval(turtleSpawnInterval); | |
| gameLoopInterval = setInterval(gameLoop, 16); | |
| turtleSpawnInterval = setInterval(spawnTurtle, 2000); | |
| } | |
| function gameLoop() { | |
| update(); | |
| render(); | |
| } | |
| function update() { | |
| // Move bird based on key presses | |
| if (keys['ArrowUp'] && bird.y > 0) { | |
| bird.y -= bird.speed; | |
| } | |
| if (keys['ArrowDown'] && bird.y < canvas.height - bird.height) { | |
| bird.y += bird.speed; | |
| } | |
| if (keys['ArrowLeft'] && bird.x > 0) { | |
| bird.x -= bird.speed; | |
| } | |
| if (keys['ArrowRight'] && bird.x < canvas.width - bird.width) { | |
| bird.x += bird.speed; | |
| } | |
| // Update turtles | |
| turtles.forEach((turtle, index) => { | |
| turtle.x += turtle.speedX; | |
| turtle.y += turtle.speedY; | |
| // Bounce off walls | |
| if (turtle.x <= 0 || turtle.x >= canvas.width - turtle.width) { | |
| turtle.speedX *= -1; | |
| } | |
| if (turtle.y <= 0 || turtle.y >= canvas.height - turtle.height) { | |
| turtle.speedY *= -1; | |
| } | |
| // Check collision with bird | |
| if ( | |
| bird.x < turtle.x + turtle.width && | |
| bird.x + bird.width > turtle.x && | |
| bird.y < turtle.y + turtle.height && | |
| bird.y + bird.height > turtle.y | |
| ) { | |
| // If bird is flipping, turtle is destroyed | |
| if (bird.isFlipping) { | |
| turtles.splice(index, 1); | |
| score += 50; | |
| createParticles(turtle.x + turtle.width/2, turtle.y + turtle.height/2, 10, 'lime'); | |
| } else { | |
| // Otherwise, streak is broken | |
| if (streak > 0) { | |
| streak = 0; | |
| streakCounter.textContent = `Streak: ${streak}`; | |
| streakCounter.classList.add('hidden'); | |
| createParticles(bird.x + bird.width/2, bird.y + bird.height/2, 15, 'red'); | |
| } | |
| } | |
| } | |
| }); | |
| // Update particles | |
| particles.forEach((particle, index) => { | |
| particle.x += particle.speedX; | |
| particle.y += particle.speedY; | |
| particle.lifetime--; | |
| if (particle.lifetime <= 0) { | |
| particles.splice(index, 1); | |
| } | |
| }); | |
| // Update score display | |
| scoreDisplay.textContent = `Score: ${score}`; | |
| } | |
| function render() { | |
| // Clear canvas | |
| ctx.fillStyle = `hsl(${backgroundHue}, 80%, 10%)`; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Draw stars | |
| drawStars(); | |
| // Draw portals | |
| portals.forEach(portal => { | |
| ctx.beginPath(); | |
| ctx.arc(portal.x, portal.y, portal.radius, 0, Math.PI * 2); | |
| const gradient = ctx.createRadialGradient( | |
| portal.x, portal.y, portal.radius * 0.3, | |
| portal.x, portal.y, portal.radius | |
| ); | |
| gradient.addColorStop(0, 'rgba(0, 255, 255, 0.8)'); | |
| gradient.addColorStop(1, 'rgba(0, 0, 255, 0)'); | |
| ctx.fillStyle = gradient; | |
| ctx.fill(); | |
| // Draw event horizon | |
| ctx.beginPath(); | |
| ctx.arc(portal.x, portal.y, portal.radius * 0.7, 0, Math.PI * 2); | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; | |
| ctx.fill(); | |
| // Draw spiral | |
| for (let i = 0; i < 8; i++) { | |
| const angle = i * (Math.PI / 4) + Date.now() / 500; | |
| const startRadius = portal.radius * 0.7; | |
| const endRadius = portal.radius; | |
| ctx.beginPath(); | |
| ctx.moveTo( | |
| portal.x + Math.cos(angle) * startRadius, | |
| portal.y + Math.sin(angle) * startRadius | |
| ); | |
| ctx.lineTo( | |
| portal.x + Math.cos(angle) * endRadius, | |
| portal.y + Math.sin(angle) * endRadius | |
| ); | |
| ctx.strokeStyle = `rgba(0, 255, 255, ${0.5 + 0.5 * Math.sin(Date.now()/200 + i)})`; | |
| ctx.lineWidth = 2; | |
| ctx.stroke(); | |
| } | |
| }); | |
| // Draw particles | |
| particles.forEach(particle => { | |
| ctx.fillStyle = particle.color; | |
| ctx.beginPath(); | |
| ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| }); | |
| // Draw turtles | |
| turtles.forEach(turtle => { | |
| ctx.save(); | |
| ctx.translate(turtle.x + turtle.width/2, turtle.y + turtle.height/2); | |
| // Add slight rotation based on movement direction | |
| if (Math.abs(turtle.speedX) > 0.1) { | |
| ctx.rotate(Math.atan2(turtle.speedY, turtle.speedX)); | |
| } | |
| // Turtle shell | |
| ctx.beginPath(); | |
| ctx.moveTo(-15, 0); | |
| ctx.quadraticCurveTo(0, -25, 15, 0); | |
| ctx.quadraticCurveTo(0, 25, -15, 0); | |
| ctx.fillStyle = '#4CAF50'; | |
| ctx.fill(); | |
| // Turtle head | |
| ctx.beginPath(); | |
| ctx.arc(-20, -5, 8, 0, Math.PI * 2); | |
| ctx.fillStyle = '#4CAF50'; | |
| ctx.fill(); | |
| // Eyes | |
| ctx.beginPath(); | |
| ctx.arc(-15, -5, 3, 0, Math.PI * 2); | |
| ctx.fillStyle = 'black'; | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(-25, -5, 3, 0, Math.PI * 2); | |
| ctx.fillStyle = 'black'; | |
| ctx.fill(); | |
| // Legs (simplified) | |
| ctx.fillStyle = '#388E3C'; | |
| ctx.fillRect(10, -10, 5, 5); | |
| ctx.fillRect(10, 5, 5, 5); | |
| ctx.fillRect(-5, -15, 5, 5); | |
| ctx.fillRect(-5, 10, 5, 5); | |
| ctx.restore(); | |
| }); | |
| // Draw bird | |
| ctx.save(); | |
| ctx.translate(bird.x + bird.width/2, bird.y + bird.height/2); | |
| // Apply flip rotation if flipping | |
| if (bird.isFlipping) { | |
| const flipProgress = (Date.now() - lastFlipTime) / flipCooldown; | |
| ctx.rotate(flipProgress * Math.PI * 2); | |
| } | |
| // Bird body | |
| ctx.beginPath(); | |
| ctx.moveTo(-20, 0); | |
| ctx.lineTo(10, -15); | |
| ctx.lineTo(30, 0); | |
| ctx.lineTo(10, 15); | |
| ctx.closePath(); | |
| ctx.fillStyle = '#FF5722'; | |
| ctx.fill(); | |
| // Bird head | |
| ctx.beginPath(); | |
| ctx.arc(25, 0, 10, 0, Math.PI * 2); | |
| ctx.fillStyle = '#FF9800'; | |
| ctx.fill(); | |
| // Eye | |
| ctx.beginPath(); | |
| ctx.arc(27, -2, 3, 0, Math.PI * 2); | |
| ctx.fillStyle = 'white'; | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(27, -2, 1, 0, Math.PI * 2); | |
| ctx.fillStyle = 'black'; | |
| ctx.fill(); | |
| // Beak | |
| ctx.beginPath(); | |
| ctx.moveTo(35, 0); | |
| ctx.lineTo(40, -5); | |
| ctx.lineTo(40, 5); | |
| ctx.closePath(); | |
| ctx.fillStyle = '#FFC107'; | |
| ctx.fill(); | |
| ctx.restore(); | |
| // Check if bird entered a portal | |
| portals.forEach((portal, index) => { | |
| const dx = bird.x + bird.width/2 - portal.x; | |
| const dy = bird.y + bird.height/2 - portal.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < portal.radius) { | |
| // Travel to new universe | |
| universe++; | |
| universeIndicator.textContent = `Universe: ${universe}`; | |
| backgroundHue = (backgroundHue + 60) % 360; | |
| // Clear turtles and portals | |
| turtles = []; | |
| portals = []; | |
| // Create celebration particles | |
| createParticles(bird.x + bird.width/2, bird.y + bird.height/2, 50, 'cyan'); | |
| // Reset streak | |
| streak = 0; | |
| streakCounter.classList.add('hidden'); | |
| } | |
| }); | |
| } | |
| function drawStars() { | |
| // Create a starfield effect | |
| ctx.fillStyle = 'white'; | |
| for (let i = 0; i < 200; i++) { | |
| // Use a simple hash to create consistent star positions per universe | |
| const x = (Math.sin(i * 100 + universe * 10) * 0.5 + 0.5) * canvas.width; | |
| const y = (Math.cos(i * 100 + universe * 10) * 0.5 + 0.5) * canvas.height; | |
| const size = Math.random() * 1.5; | |
| // Make some stars twinkle | |
| const opacity = 0.5 + 0.5 * Math.sin(Date.now() / 1000 + i); | |
| ctx.globalAlpha = opacity; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.globalAlpha = 1; | |
| } | |
| function performBackflip() { | |
| bird.isFlipping = true; | |
| lastFlipTime = Date.now(); | |
| score += 20; | |
| // Create flip particles | |
| createParticles(bird.x + bird.width/2, bird.y + bird.height/2, 15, 'orange'); | |
| // End flip animation after cooldown | |
| setTimeout(() => { | |
| bird.isFlipping = false; | |
| // Increase streak | |
| streak++; | |
| streakCounter.textContent = `Streak: ${streak}`; | |
| streakCounter.classList.remove('hidden'); | |
| // Check for streak milestone | |
| if (streak >= 3) { | |
| createPortal(); | |
| streak = 0; | |
| streakCounter.classList.add('hidden'); | |
| } | |
| }, flipCooldown); | |
| } | |
| function spawnTurtle() { | |
| if (!gameRunning) return; | |
| const side = Math.floor(Math.random() * 4); | |
| let x, y, speedX, speedY; | |
| switch (side) { | |
| case 0: // top | |
| x = Math.random() * canvas.width; | |
| y = -40; | |
| speedX = (Math.random() - 0.5) * 3; | |
| speedY = Math.random() * 2 + 1; | |
| break; | |
| case 1: // right | |
| x = canvas.width + 40; | |
| y = Math.random() * canvas.height; | |
| speedX = -(Math.random() * 2 + 1); | |
| speedY = (Math.random() - 0.5) * 3; | |
| break; | |
| case 2: // bottom | |
| x = Math.random() * canvas.width; | |
| y = canvas.height + 40; | |
| speedX = (Math.random() - 0.5) * 3; | |
| speedY = -(Math.random() * 2 + 1); | |
| break; | |
| case 3: // left | |
| x = -40; | |
| y = Math.random() * canvas.height; | |
| speedX = Math.random() * 2 + 1; | |
| speedY = (Math.random() - 0.5) * 3; | |
| break; | |
| } | |
| turtles.push({ | |
| x, | |
| y, | |
| width: 40, | |
| height: 30, | |
| speedX, | |
| speedY | |
| }); | |
| // Randomly spawn between 1-3 turtles | |
| if (Math.random() > 0.7) { | |
| setTimeout(spawnTurtle, Math.random() * 500); | |
| } | |
| } | |
| function createPortal() { | |
| const x = Math.random() * (canvas.width - 200) + 100; | |
| const y = Math.random() * (canvas.height - 200) + 100; | |
| portals.push({ | |
| x, | |
| y, | |
| radius: 60 | |
| }); | |
| // Create portal particles | |
| createParticles(x, y, 30, 'cyan'); | |
| } | |
| function createParticles(x, y, count, color) { | |
| for (let i = 0; i < count; i++) { | |
| const angle = Math.random() * Math.PI * 2; | |
| const speed = Math.random() * 3 + 1; | |
| particles.push({ | |
| x, | |
| y, | |
| size: Math.random() * 3 + 1, | |
| speedX: Math.cos(angle) * speed, | |
| speedY: Math.sin(angle) * speed, | |
| lifetime: Math.random() * 30 + 20, | |
| color | |
| }); | |
| } | |
| } | |
| function gameOver() { | |
| gameRunning = false; | |
| clearInterval(gameLoopInterval); | |
| clearInterval(turtleSpawnInterval); | |
| finalScoreDisplay.textContent = `Score: ${score}`; | |
| universesVisitedDisplay.textContent = `Universes Visited: ${universe - 1}`; | |
| gameOverScreen.classList.remove('hidden'); | |
| } | |
| // Start with the start screen | |
| startScreen.classList.remove('hidden'); | |
| </script> | |
| </body> | |
| </html> | |