BackflippingFlapper / index.html
Remsky's picture
Update index.html
028af9f verified
<!DOCTYPE html>
<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>