|
|
<!DOCTYPE html> |
|
|
<html lang="en" class="dark"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>SkyFlap Odyssey: Aerial Avian Adventure</title> |
|
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico"> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/3.60.0/phaser.min.js"></script> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); |
|
|
|
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: 'Poppins', sans-serif; |
|
|
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%); |
|
|
color: #f8fafc; |
|
|
overflow-x: hidden; |
|
|
} |
|
|
|
|
|
.game-container { |
|
|
position: relative; |
|
|
max-width: 1200px; |
|
|
margin: 0 auto; |
|
|
border-radius: 20px; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); |
|
|
} |
|
|
|
|
|
#gameCanvas { |
|
|
display: block; |
|
|
margin: 0 auto; |
|
|
border: 3px solid #475569; |
|
|
border-radius: 15px; |
|
|
background: linear-gradient(180deg, #1e40af 0%, #3b82f6 100%); |
|
|
} |
|
|
|
|
|
.stats-overlay { |
|
|
position: absolute; |
|
|
top: 20px; |
|
|
left: 20px; |
|
|
z-index: 10; |
|
|
background: rgba(15, 23, 42, 0.8); |
|
|
padding: 15px; |
|
|
border-radius: 12px; |
|
|
backdrop-filter: blur(10px); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.score-animation { |
|
|
animation: scorePop 0.6s ease-out; |
|
|
} |
|
|
|
|
|
@keyframes scorePop { |
|
|
0% { transform: scale(1); } |
|
|
50% { transform: scale(1.3); } |
|
|
100% { transform: scale(1); } |
|
|
} |
|
|
|
|
|
.power-up-indicator { |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { opacity: 1; } |
|
|
50% { opacity: 0.6; } |
|
|
100% { opacity: 1; } |
|
|
} |
|
|
|
|
|
.menu-transition { |
|
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
} |
|
|
|
|
|
.parallax-bg { |
|
|
position: absolute; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
top: 0; |
|
|
left: 0; |
|
|
z-index: -1; |
|
|
} |
|
|
|
|
|
.bg-layer-1 { animation: scrollBg 60s linear infinite; } |
|
|
.bg-layer-2 { animation: scrollBg 40s linear infinite; } |
|
|
.bg-layer-3 { animation: scrollBg 20s linear infinite; } |
|
|
|
|
|
@keyframes scrollBg { |
|
|
from { transform: translateX(0); } |
|
|
to { transform: translateX(-100%); } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="min-h-screen flex items-center justify-center p-4"> |
|
|
|
|
|
<div class="parallax-bg"> |
|
|
<div class="bg-layer-1 absolute inset-0 opacity-20"> |
|
|
<div class="flex"> |
|
|
<div class="w-64 h-64 bg-gradient-to-br from-blue-400 to-purple-500 rounded-full blur-3xl"></div> |
|
|
<div class="w-48 h-48 bg-gradient-to-br from-green-400 to-blue-500 rounded-full blur-2xl ml-32"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-layer-2 absolute inset-0 opacity-15"> |
|
|
<div class="flex mt-32"> |
|
|
<div class="w-32 h-32 bg-gradient-to-br from-yellow-400 to-red-500 rounded-full blur-2xl"></div> |
|
|
<div class="w-56 h-56 bg-gradient-to-br from-pink-400 to-red-500 rounded-full blur-3xl ml-64"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-layer-3 absolute inset-0 opacity-10"> |
|
|
<div class="flex mt-64"> |
|
|
<div class="w-40 h-40 bg-gradient-to-br from-purple-400 to-pink-500 rounded-full blur-2xl ml-96"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="game-container"> |
|
|
|
|
|
<div class="stats-overlay"> |
|
|
<div class="flex items-center space-x-6"> |
|
|
<div class="text-center"> |
|
|
<div class="text-sm text-gray-400">SCORE</div> |
|
|
<div id="currentScore" class="text-2xl font-bold text-white score-animation">0</div> |
|
|
</div> |
|
|
<div class="text-center"> |
|
|
<div class="text-sm text-gray-400">BEST</div> |
|
|
<div id="bestScore" class="text-xl font-semibold text-yellow-400">0</div> |
|
|
</div> |
|
|
<div id="powerUpDisplay" class="hidden"> |
|
|
<div class="text-center"> |
|
|
<div class="text-sm text-gray-400">POWER</div> |
|
|
<div class="text-lg font-semibold text-green-400 power-up-indicator">ACTIVE</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="gameCanvas" class="w-full h-auto"></div> |
|
|
|
|
|
|
|
|
<div id="startMenu" class="absolute inset-0 flex flex-col items-center justify-center bg-gradient-to-br from-black/80 to-blue-900/80 backdrop-blur-sm"> |
|
|
<div class="text-center space-y-6"> |
|
|
<h1 class="text-5xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent"> |
|
|
SkyFlap Odyssey |
|
|
</h1> |
|
|
<p class="text-xl text-gray-300">Aerial Avian Adventure</p> |
|
|
|
|
|
<div class="space-y-4 mt-8"> |
|
|
<button onclick="startGame()" class="w-64 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-semibold py-3 px-6 rounded-xl transition-all duration-300 transform hover:scale-105"> |
|
|
<i data-feather="play" class="inline mr-2"></i> |
|
|
Start Flight |
|
|
</button> |
|
|
|
|
|
<button onclick="showSettings()" class="w-64 bg-gray-700 hover:bg-gray-600 text-white font-semibold py-3 px-6 rounded-xl transition-all duration-300"> |
|
|
<i data-feather="settings" class="inline mr-2"></i> |
|
|
Game Settings |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mt-6 text-sm text-gray-400"> |
|
|
<p>Tap, Click, or Press SPACE to flap</p> |
|
|
<p>Gamepad supported</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="gameOverMenu" class="absolute inset-0 flex flex-col items-center justify-center bg-gradient-to-br from-red-900/80 to-black/80 backdrop-blur-sm hidden"> |
|
|
<div class="text-center space-y-6"> |
|
|
<h2 class="text-4xl font-bold text-red-400">Flight Terminated</h2> |
|
|
|
|
|
<div class="bg-black/50 p-6 rounded-2xl space-y-4"> |
|
|
<div class="text-2xl font-semibold">Final Score: <span id="finalScore" class="text-yellow-400">0</span></div> |
|
|
<div class="text-lg">Best Score: <span id="finalBestScore" class="text-green-400">0</span></div> |
|
|
|
|
|
<div class="space-y-3"> |
|
|
<button onclick="restartGame()" class="w-64 bg-gradient-to-r from-green-500 to-blue-600 hover:from-green-600 hover:to-blue-700 text-white font-semibold py-3 px-6 rounded-xl transition-all duration-300"> |
|
|
<i data-feather="refresh-cw" class="inline mr-2"></i> |
|
|
Fly Again |
|
|
</button> |
|
|
|
|
|
<button onclick="showStartMenu()" class="w-64 bg-gray-700 hover:bg-gray-600 text-white font-semibold py-3 px-6 rounded-xl transition-all duration-300"> |
|
|
<i data-feather="home" class="inline mr-2"></i> |
|
|
Main Menu |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="settingsMenu" class="absolute inset-0 flex flex-col items-center justify-center bg-gradient-to-br from-gray-900/80 to-black/80 backdrop-blur-sm hidden"> |
|
|
<div class="bg-black/70 p-8 rounded-2xl max-w-md w-full space-y-6"> |
|
|
<h2 class="text-3xl font-bold text-center text-white">Settings</h2> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Music Volume</label> |
|
|
<input type="range" min="0" max="100" value="50" class="w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer slider"> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">SFX Volume</label> |
|
|
<input type="range" min="0" max="100" value="70" class="w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer slider"> |
|
|
</div> |
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<span class="text-sm font-medium text-gray-300">Visual Effects</span> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" checked class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex space-x-4 pt-4"> |
|
|
<button onclick="saveSettings()" class="flex-1 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-semibold py-2 px-4 rounded-xl transition-all duration-300"> |
|
|
Save |
|
|
</button> |
|
|
|
|
|
<button onclick="showStartMenu()" class="flex-1 bg-gray-700 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-xl transition-all duration-300"> |
|
|
Cancel |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mt-8 text-center space-y-2"> |
|
|
<div class="flex justify-center space-x-8 text-sm text-gray-400"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<i data-feather="mouse-pointer"></i> |
|
|
<span>Click/Tap</span> |
|
|
</div> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<i data-feather="square"></i> |
|
|
<span>Space Bar</span> |
|
|
</div> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<i data-feather="gamepad"></i> |
|
|
<span>Gamepad A</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
let game; |
|
|
let currentScore = 0; |
|
|
let bestScore = 0; |
|
|
let isGameRunning = false; |
|
|
|
|
|
|
|
|
if (localStorage.getItem('skyflapBestScore')) { |
|
|
bestScore = parseInt(localStorage.getItem('skyflapBestScore')); |
|
|
document.getElementById('bestScore').textContent = bestScore; |
|
|
} |
|
|
|
|
|
|
|
|
const config = { |
|
|
type: Phaser.AUTO, |
|
|
width: 800, |
|
|
height: 600, |
|
|
parent: 'gameCanvas', |
|
|
physics: { |
|
|
default: 'arcade', |
|
|
arcade: { |
|
|
gravity: { y: 800 }, |
|
|
debug: false |
|
|
} |
|
|
}, |
|
|
scene: { |
|
|
preload: preload, |
|
|
create: create, |
|
|
update: update |
|
|
}, |
|
|
scale: { |
|
|
mode: Phaser.Scale.FIT, |
|
|
autoCenter: Phaser.Scale.CENTER_BOTH |
|
|
} |
|
|
}; |
|
|
|
|
|
function preload() { |
|
|
|
|
|
this.load.image('bird', 'http://static.photos/nature/64x64/1'); |
|
|
this.load.image('pipe', 'http://static.photos/green/128x512/2'); |
|
|
this.load.image('background', 'http://static.photos/blue/800x600/3'); |
|
|
} |
|
|
|
|
|
function create() { |
|
|
|
|
|
this.add.image(400, 300, 'background'); |
|
|
|
|
|
|
|
|
this.bird = this.physics.add.sprite(100, 300, 'bird'); |
|
|
this.bird.setCollideWorldBounds(true); |
|
|
|
|
|
|
|
|
this.pipes = this.physics.add.group(); |
|
|
|
|
|
|
|
|
this.input.on('pointerdown', flap, this); |
|
|
this.input.keyboard.on('keydown-SPACE', flap, this); |
|
|
|
|
|
|
|
|
this.pipeTimer = this.time.addEvent({ |
|
|
delay: 1500, |
|
|
callback: generatePipe, |
|
|
callbackScope: this, |
|
|
loop: true |
|
|
}); |
|
|
|
|
|
|
|
|
this.physics.add.collider(this.bird, this.pipes, gameOver, null, this); |
|
|
|
|
|
|
|
|
this.scoreText = this.add.text(400, 50, '0', { |
|
|
fontSize: '32px', |
|
|
fill: '#ffffff', |
|
|
stroke: '#000000', |
|
|
strokeThickness: 4 |
|
|
}).setOrigin(0.5); |
|
|
} |
|
|
|
|
|
function update() { |
|
|
if (!isGameRunning) return; |
|
|
|
|
|
|
|
|
this.bird.rotation = Phaser.Math.Clamp(this.bird.body.velocity.y * 0.01, -0.5, 0.5); |
|
|
} |
|
|
|
|
|
function flap() { |
|
|
if (!isGameRunning) return; |
|
|
this.bird.setVelocityY(-300); |
|
|
} |
|
|
|
|
|
function generatePipe() { |
|
|
if (!isGameRunning) return; |
|
|
|
|
|
const gap = 200; |
|
|
const pipeX = 800; |
|
|
const pipeY = Phaser.Math.Between(100, 500); |
|
|
|
|
|
|
|
|
const topPipe = this.pipes.create(pipeX, pipeY - gap / 2, 'pipe'); |
|
|
topPipe.setScale(1, -1); |
|
|
topPipe.setVelocityX(-200); |
|
|
|
|
|
|
|
|
const bottomPipe = this.pipes.create(pipeX, pipeY + gap / 2, 'pipe'); |
|
|
bottomPipe.setVelocityX(-200); |
|
|
|
|
|
|
|
|
this.time.delayedCall(4000, () => { |
|
|
topPipe.destroy(); |
|
|
bottomPipe.destroy(); |
|
|
}); |
|
|
|
|
|
|
|
|
this.time.delayedCall(2000, () => { |
|
|
currentScore++; |
|
|
updateScore(); |
|
|
}); |
|
|
} |
|
|
|
|
|
function gameOver() { |
|
|
isGameRunning = false; |
|
|
|
|
|
|
|
|
if (currentScore > bestScore) { |
|
|
bestScore = currentScore; |
|
|
localStorage.setItem('skyflapBestScore', bestScore); |
|
|
|
|
|
|
|
|
document.getElementById('finalScore').textContent = currentScore; |
|
|
document.getElementById('finalBestScore').textContent = bestScore; |
|
|
document.getElementById('gameOverMenu').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function updateScore() { |
|
|
document.getElementById('currentScore').textContent = currentScore; |
|
|
document.getElementById('bestScore').textContent = bestScore; |
|
|
|
|
|
|
|
|
document.getElementById('currentScore').classList.add('score-animation'); |
|
|
setTimeout(() => { |
|
|
document.getElementById('currentScore').classList.remove('score-animation'); |
|
|
}, 600); |
|
|
} |
|
|
|
|
|
|
|
|
function startGame() { |
|
|
document.getElementById('startMenu').classList.add('hidden'); |
|
|
document.getElementById('gameOverMenu').classList.add('hidden'); |
|
|
isGameRunning = true; |
|
|
currentScore = 0; |
|
|
updateScore(); |
|
|
|
|
|
|
|
|
if (!game) { |
|
|
game = new Phaser.Game(config); |
|
|
} else { |
|
|
|
|
|
game.scene.restart(); |
|
|
} |
|
|
} |
|
|
|
|
|
function restartGame() { |
|
|
document.getElementById('gameOverMenu').classList.add('hidden'); |
|
|
isGameRunning = true; |
|
|
currentScore = 0; |
|
|
updateScore(); |
|
|
game.scene.restart(); |
|
|
} |
|
|
|
|
|
function showStartMenu() { |
|
|
document.getElementById('startMenu').classList.remove('hidden'); |
|
|
document.getElementById('settingsMenu').classList.add('hidden'); |
|
|
document.getElementById('gameOverMenu').classList.add('hidden'); |
|
|
isGameRunning = false; |
|
|
} |
|
|
|
|
|
function showSettings() { |
|
|
document.getElementById('startMenu').classList.add('hidden'); |
|
|
document.getElementById('settingsMenu').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function saveSettings() { |
|
|
|
|
|
showStartMenu(); |
|
|
} |
|
|
|
|
|
|
|
|
window.addEventListener("gamepadconnected", (e) => { |
|
|
console.log("Gamepad connected:", e.gamepad.id); |
|
|
}); |
|
|
|
|
|
window.addEventListener("gamepaddisconnected", (e) => { |
|
|
console.log("Gamepad disconnected:", e.gamepad.id); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
feather.replace(); |
|
|
}); |
|
|
|
|
|
|
|
|
function handleResize() { |
|
|
const container = document.querySelector('.game-container'); |
|
|
const canvas = document.getElementById('gameCanvas'); |
|
|
|
|
|
if (window.innerWidth < 768) { |
|
|
container.style.maxWidth = '100%'; |
|
|
if (game) { |
|
|
game.scale.setGameSize(400, 300); |
|
|
} |
|
|
} else { |
|
|
container.style.maxWidth = '1200px'; |
|
|
if (game) { |
|
|
game.scale.setGameSize(800, 600); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
window.addEventListener('resize', handleResize); |
|
|
handleResize(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|