Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cosmic Defender - Space Shooter</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></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=Space+Grotesk:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700&display=swap'); | |
| :root { | |
| --primary: #00f0ff; | |
| --secondary: #7b2dff; | |
| --tertiary: #ff2d7b; | |
| --dark: #0a0a1a; | |
| --darker: #050510; | |
| --light: #e0e0ff; | |
| --accent: #ff2d7b; | |
| } | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: 'Space Grotesk', 'Orbitron', sans-serif; | |
| background-color: var(--dark); | |
| color: var(--light); | |
| touch-action: none; | |
| } | |
| #canvas { | |
| display: block; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| z-index: 1; | |
| } | |
| #game-container { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: 2; | |
| } | |
| #player-ship { | |
| position: absolute; | |
| width: 60px; | |
| height: 60px; | |
| z-index: 10; | |
| transition: transform 0.1s ease; | |
| filter: drop-shadow(0 0 5px var(--primary)); | |
| } | |
| .projectile { | |
| position: absolute; | |
| width: 24px; | |
| height: 8px; | |
| z-index: 5; | |
| filter: drop-shadow(0 0 3px var(--primary)); | |
| } | |
| .enemy-ship { | |
| position: absolute; | |
| width: 50px; | |
| height: 50px; | |
| z-index: 5; | |
| filter: drop-shadow(0 0 5px var(--tertiary)); | |
| } | |
| .explosion { | |
| position: absolute; | |
| width: 120px; | |
| height: 120px; | |
| z-index: 6; | |
| pointer-events: none; | |
| } | |
| .explosion-particle { | |
| position: absolute; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| } | |
| .spark { | |
| position: absolute; | |
| width: 3px; | |
| height: 3px; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| filter: blur(0.5px); | |
| } | |
| .smoke { | |
| position: absolute; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| filter: blur(5px); | |
| opacity: 0.8; | |
| } | |
| .shockwave { | |
| position: absolute; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| border: 2px solid rgba(255, 255, 255, 0.7); | |
| transform: scale(0); | |
| opacity: 1; | |
| } | |
| @keyframes explode { | |
| 0% { transform: scale(0.5); opacity: 1; } | |
| 100% { transform: scale(2); opacity: 0; } | |
| } | |
| @keyframes particle-fly { | |
| 0% { transform: translate(0, 0) scale(1); opacity: 1; } | |
| 100% { transform: translate(var(--tx), var(--ty)) scale(0.2); opacity: 0; } | |
| } | |
| @keyframes spark-fly { | |
| 0% { transform: translate(0, 0); opacity: 1; } | |
| 100% { transform: translate(var(--tx), var(--ty)); opacity: 0; } | |
| } | |
| @keyframes smoke-rise { | |
| 0% { transform: translate(0, 0) scale(0.5); opacity: 0.8; } | |
| 100% { transform: translate(var(--tx), var(--ty)) scale(2); opacity: 0; } | |
| } | |
| @keyframes shockwave { | |
| 0% { transform: scale(0); opacity: 1; } | |
| 100% { transform: scale(3); opacity: 0; } | |
| } | |
| #hud { | |
| position: fixed; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 20; | |
| color: white; | |
| font-family: 'Orbitron', sans-serif; | |
| text-shadow: 0 0 5px var(--primary); | |
| } | |
| #game-over { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.8); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 30; | |
| color: white; | |
| font-size: 2rem; | |
| display: none; | |
| } | |
| #restart-btn { | |
| margin-top: 20px; | |
| padding: 10px 20px; | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| border: none; | |
| border-radius: 5px; | |
| color: white; | |
| font-family: 'Orbitron', sans-serif; | |
| cursor: pointer; | |
| font-size: 1rem; | |
| } | |
| .glass-panel { | |
| background: rgba(10, 10, 26, 0.4); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border-radius: 1.5rem; | |
| border: 1px solid rgba(224, 224, 255, 0.1); | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
| padding: 1.25rem; | |
| transition: all 0.3s ease; | |
| } | |
| .glass-panel:hover { | |
| border-color: rgba(224, 224, 255, 0.2); | |
| box-shadow: 0 8px 32px rgba(0, 240, 255, 0.1); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="canvas"></div> | |
| <div id="game-container"> | |
| <div id="player-ship"></div> | |
| <div id="hud"> | |
| <div>SCORE: <span id="score">0</span></div> | |
| <div>HEALTH: <span id="health">100</span>%</div> | |
| <div>ENEMIES: <span id="enemies">0</span></div> | |
| </div> | |
| <div id="game-over"> | |
| <h1>GAME OVER</h1> | |
| <p>Final Score: <span id="final-score">0</span></p> | |
| <button id="restart-btn">PLAY AGAIN</button> | |
| </div> | |
| </div> | |
| <script> | |
| // Main Three.js variables for background | |
| let scene, camera, renderer; | |
| let wormhole, particles = []; | |
| let explosionParticles = []; | |
| let debrisParticles = []; | |
| let sparkParticles = []; | |
| let starParticles = []; | |
| // Game variables | |
| let player = { | |
| x: 100, | |
| y: window.innerHeight / 2, | |
| width: 60, | |
| height: 60, | |
| speed: 8, | |
| health: 100, | |
| lastShot: 0, | |
| shootDelay: 300 | |
| }; | |
| let projectiles = []; | |
| let enemies = []; | |
| let explosions = []; | |
| let score = 0; | |
| let gameRunning = true; | |
| let enemySpawnRate = 1000; // ms | |
| let lastEnemySpawn = 0; | |
| let keys = { | |
| ArrowUp: false, | |
| ArrowDown: false, | |
| ArrowLeft: false, | |
| ArrowRight: false, | |
| ' ': false | |
| }; | |
| // Procedural SVG generation functions | |
| function generatePlayerShip() { | |
| const shipId = 'player-ship-' + Date.now(); | |
| const svg = ` | |
| <svg id="${shipId}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"> | |
| <defs> | |
| <linearGradient id="shipGradient" x1="0%" y1="0%" x2="100%" y2="100%"> | |
| <stop offset="0%" stop-color="#00f0ff" /> | |
| <stop offset="100%" stop-color="#7b2dff" /> | |
| </linearGradient> | |
| <radialGradient id="engineGlow" cx="20%" cy="50%" r="50%" fx="20%" fy="50%"> | |
| <stop offset="0%" stop-color="#ff2d7b" stop-opacity="0.8" /> | |
| <stop offset="100%" stop-color="#ff2d7b" stop-opacity="0" /> | |
| </radialGradient> | |
| </defs> | |
| <!-- Main hull --> | |
| <path d="M30,5 L55,30 L45,55 L15,55 L5,30 Z" fill="url(#shipGradient)" stroke="#ffffff" stroke-width="1" /> | |
| <!-- Cockpit --> | |
| <ellipse cx="30" cy="25" rx="10" ry="8" fill="#0a0a1a" stroke="#ffffff" stroke-width="1" /> | |
| <ellipse cx="30" cy="25" rx="6" ry="4" fill="#00f0ff" opacity="0.7" /> | |
| <!-- Engine glow --> | |
| <path d="M15,50 L45,50 L40,60 L20,60 Z" fill="url(#engineGlow)" /> | |
| <!-- Details --> | |
| <path d="M30,5 L30,25" stroke="#ffffff" stroke-width="1" stroke-dasharray="2,2" /> | |
| <path d="M15,55 L5,30 L15,20" stroke="#ffffff" stroke-width="1" fill="none" /> | |
| <path d="M45,55 L55,30 L45,20" stroke="#ffffff" stroke-width="1" fill="none" /> | |
| <!-- Thrusters --> | |
| <rect x="20" y="50" width="5" height="8" fill="#00f0ff" rx="1" /> | |
| <rect x="35" y="50" width="5" height="8" fill="#00f0ff" rx="1" /> | |
| </svg> | |
| `; | |
| return svg; | |
| } | |
| function generateEnemyShip() { | |
| const shipId = 'enemy-ship-' + Date.now(); | |
| const hue = Math.floor(Math.random() * 60) + 300; // Purple-red range | |
| const svg = ` | |
| <svg id="${shipId}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"> | |
| <defs> | |
| <linearGradient id="enemyGradient" x1="0%" y1="0%" x2="100%" y2="100%"> | |
| <stop offset="0%" stop-color="hsl(${hue}, 100%, 60%)" /> | |
| <stop offset="100%" stop-color="hsl(${hue + 20}, 100%, 40%)" /> | |
| </linearGradient> | |
| <radialGradient id="enemyGlow" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"> | |
| <stop offset="0%" stop-color="hsl(${hue}, 100%, 80%)" stop-opacity="0.8" /> | |
| <stop offset="100%" stop-color="hsl(${hue}, 100%, 60%)" stop-opacity="0" /> | |
| </radialGradient> | |
| </defs> | |
| <!-- Main body --> | |
| <path d="M25,5 L45,25 L25,45 L5,25 Z" fill="url(#enemyGradient)" stroke="#ffffff" stroke-width="1" /> | |
| <!-- Core --> | |
| <circle cx="25" cy="25" r="8" fill="url(#enemyGlow)" /> | |
| <circle cx="25" cy="25" r="5" fill="#0a0a1a" /> | |
| <!-- Spikes --> | |
| <path d="M25,5 L30,0 L35,5 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
| <path d="M45,25 L50,20 L50,30 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
| <path d="M25,45 L30,50 L35,45 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
| <path d="M5,25 L0,20 L0,30 Z" fill="hsl(${hue}, 100%, 60%)" /> | |
| <!-- Details --> | |
| <path d="M15,15 L35,35" stroke="#ffffff" stroke-width="1" stroke-dasharray="1,1" /> | |
| <path d="M15,35 L35,15" stroke="#ffffff" stroke-width="1" stroke-dasharray="1,1" /> | |
| </svg> | |
| `; | |
| return svg; | |
| } | |
| function generateProjectile() { | |
| const projId = 'projectile-' + Date.now(); | |
| const svg = ` | |
| <svg id="${projId}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 8"> | |
| <defs> | |
| <linearGradient id="projGradient" x1="0%" y1="50%" x2="100%" y2="50%"> | |
| <stop offset="0%" stop-color="#00f0ff" /> | |
| <stop offset="50%" stop-color="#7b2dff" /> | |
| <stop offset="100%" stop-color="#00f0ff" /> | |
| </linearGradient> | |
| <radialGradient id="projGlow" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"> | |
| <stop offset="0%" stop-color="#00f0ff" stop-opacity="0.8" /> | |
| <stop offset="100%" stop-color="#00f0ff" stop-opacity="0" /> | |
| </radialGradient> | |
| </defs> | |
| <!-- Projectile body --> | |
| <rect x="0" y="2" width="24" height="4" rx="2" fill="url(#projGradient)" /> | |
| <!-- Glow effects --> | |
| <ellipse cx="12" cy="4" rx="12" ry="4" fill="url(#projGlow)" /> | |
| <!-- Front tip --> | |
| <path d="M24,2 L28,4 L24,6 Z" fill="#00f0ff" /> | |
| </svg> | |
| `; | |
| return svg; | |
| } | |
| // Initialize the background scene | |
| function initBackground() { | |
| // Create scene | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x0a0a1a); | |
| scene.fog = new THREE.FogExp2(0x0a0a1a, 0.002); | |
| // Create camera | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); | |
| camera.position.set(0, 0, 50); | |
| // Create renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| document.getElementById('canvas').appendChild(renderer.domElement); | |
| // Create lights | |
| const ambientLight = new THREE.AmbientLight(0x404040); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 1); | |
| directionalLight.position.set(1, 1, 1); | |
| directionalLight.castShadow = true; | |
| scene.add(directionalLight); | |
| // Add colorful ambient light | |
| const coloredLight1 = new THREE.PointLight(0x7b2dff, 0.5, 100); | |
| coloredLight1.position.set(20, 20, 20); | |
| scene.add(coloredLight1); | |
| const coloredLight2 = new THREE.PointLight(0x00f0ff, 0.5, 100); | |
| coloredLight2.position.set(-20, -20, -20); | |
| scene.add(coloredLight2); | |
| // Create wormhole | |
| createWormhole(); | |
| // Create particles | |
| createParticles(); | |
| } | |
| // Create wormhole geometry | |
| function createWormhole() { | |
| const geometry = new THREE.TorusGeometry(15, 3, 32, 100); | |
| const material = new THREE.MeshPhongMaterial({ | |
| color: 0x00aaff, | |
| emissive: 0x0066ff, | |
| emissiveIntensity: 0.8, | |
| transparent: true, | |
| opacity: 0.9, | |
| wireframe: true, | |
| wireframeLinewidth: 2 | |
| }); | |
| wormhole = new THREE.Mesh(geometry, material); | |
| wormhole.rotation.x = Math.PI / 2; | |
| scene.add(wormhole); | |
| // Add inner glow | |
| const innerGlowGeometry = new THREE.SphereGeometry(12, 32, 32); | |
| const innerGlowMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x00aaff, | |
| transparent: true, | |
| opacity: 0.3 | |
| }); | |
| const innerGlow = new THREE.Mesh(innerGlowGeometry, innerGlowMaterial); | |
| wormhole.add(innerGlow); | |
| // Add energy field | |
| const energyFieldGeometry = new THREE.SphereGeometry(16, 64, 64); | |
| const energyFieldMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x7b2dff, | |
| transparent: true, | |
| opacity: 0.05, | |
| wireframe: true | |
| }); | |
| const energyField = new THREE.Mesh(energyFieldGeometry, energyFieldMaterial); | |
| wormhole.add(energyField); | |
| } | |
| // Create various particles | |
| function createParticles() { | |
| // Stars background | |
| const starGeometry = new THREE.BufferGeometry(); | |
| const starMaterial = new THREE.PointsMaterial({ | |
| color: 0xffffff, | |
| size: 0.2, | |
| transparent: true, | |
| opacity: 0.8 | |
| }); | |
| const starPositions = []; | |
| const starColors = []; | |
| for (let i = 0; i < 2000; i++) { | |
| starPositions.push( | |
| Math.random() * 2000 - 1000, | |
| Math.random() * 2000 - 1000, | |
| Math.random() * 2000 - 1000 | |
| ); | |
| // Add some color variation | |
| const colorIntensity = 0.7 + Math.random() * 0.3; | |
| starColors.push( | |
| colorIntensity, | |
| colorIntensity, | |
| colorIntensity | |
| ); | |
| } | |
| starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3)); | |
| starGeometry.setAttribute('color', new THREE.Float32BufferAttribute(starColors, 3)); | |
| const stars = new THREE.Points(starGeometry, starMaterial); | |
| scene.add(stars); | |
| starParticles.push(stars); | |
| // Debris | |
| for (let i = 0; i < 150; i++) { | |
| const size = 0.2 + Math.random() * 1.5; | |
| const debris = new THREE.Mesh( | |
| new THREE.BoxGeometry(size, size, size), | |
| new THREE.MeshPhongMaterial({ | |
| color: 0x888888, | |
| emissive: 0x333333, | |
| emissiveIntensity: 0.1 | |
| }) | |
| ); | |
| debris.position.set( | |
| Math.random() * 100 - 50, | |
| Math.random() * 100 - 50, | |
| Math.random() * 100 - 50 | |
| ); | |
| debris.rotation.set( | |
| Math.random() * Math.PI, | |
| Math.random() * Math.PI, | |
| Math.random() * Math.PI | |
| ); | |
| debris.userData = { | |
| velocity: new THREE.Vector3( | |
| Math.random() * 0.2 - 0.1, | |
| Math.random() * 0.2 - 0.1, | |
| Math.random() * 0.2 - 0.1 | |
| ), | |
| rotationSpeed: new THREE.Vector3( | |
| Math.random() * 0.02 - 0.01, | |
| Math.random() * 0.02 - 0.01, | |
| Math.random() * 0.02 - 0.01 | |
| ) | |
| }; | |
| scene.add(debris); | |
| debrisParticles.push(debris); | |
| } | |
| } | |
| // Initialize the game | |
| function initGame() { | |
| // Create player ship with procedural SVG | |
| const playerShip = document.getElementById('player-ship'); | |
| playerShip.innerHTML = generatePlayerShip(); | |
| // Position player ship | |
| updatePlayerPosition(); | |
| // Set up event listeners | |
| window.addEventListener('keydown', handleKeyDown); | |
| window.addEventListener('keyup', handleKeyUp); | |
| window.addEventListener('resize', onWindowResize); | |
| document.getElementById('restart-btn').addEventListener('click', resetGame); | |
| // Start game loop | |
| gameLoop(); | |
| } | |
| // Handle key down events | |
| function handleKeyDown(e) { | |
| if (keys.hasOwnProperty(e.key)) { | |
| keys[e.key] = true; | |
| e.preventDefault(); | |
| } | |
| } | |
| // Handle key up events | |
| function handleKeyUp(e) { | |
| if (keys.hasOwnProperty(e.key)) { | |
| keys[e.key] = false; | |
| e.preventDefault(); | |
| } | |
| } | |
| // Update player position based on key states | |
| function updatePlayerPosition() { | |
| if (keys.ArrowUp && player.y > player.height / 2) { | |
| player.y -= player.speed; | |
| } | |
| if (keys.ArrowDown && player.y < window.innerHeight - player.height / 2) { | |
| player.y += player.speed; | |
| } | |
| if (keys.ArrowLeft && player.x > player.width / 2) { | |
| player.x -= player.speed; | |
| } | |
| if (keys.ArrowRight && player.x < window.innerWidth - player.width / 2) { | |
| player.x += player.speed; | |
| } | |
| // Update DOM element position | |
| const ship = document.getElementById('player-ship'); | |
| ship.style.left = `${player.x - player.width / 2}px`; | |
| ship.style.top = `${player.y - player.height / 2}px`; | |
| // Shoot if space is pressed and delay has passed | |
| const now = Date.now(); | |
| if (keys[' '] && now - player.lastShot > player.shootDelay) { | |
| shoot(); | |
| player.lastShot = now; | |
| } | |
| } | |
| // Create a new projectile | |
| function shoot() { | |
| const projectile = document.createElement('div'); | |
| projectile.className = 'projectile'; | |
| projectile.innerHTML = generateProjectile(); | |
| projectile.style.left = `${player.x + player.width / 2}px`; | |
| projectile.style.top = `${player.y - 4}px`; | |
| document.getElementById('game-container').appendChild(projectile); | |
| projectiles.push({ | |
| element: projectile, | |
| x: player.x + player.width / 2, | |
| y: player.y, | |
| speed: 12, | |
| width: 24, | |
| height: 8 | |
| }); | |
| } | |
| // Update all projectiles | |
| function updateProjectiles() { | |
| for (let i = projectiles.length - 1; i >= 0; i--) { | |
| const p = projectiles[i]; | |
| p.x += p.speed; | |
| p.element.style.left = `${p.x}px`; | |
| // Remove if off screen | |
| if (p.x > window.innerWidth) { | |
| p.element.remove(); | |
| projectiles.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Spawn a new enemy | |
| function spawnEnemy() { | |
| const enemy = document.createElement('div'); | |
| enemy.className = 'enemy-ship'; | |
| enemy.innerHTML = generateEnemyShip(); | |
| const y = Math.random() * (window.innerHeight - 100) + 50; | |
| enemy.style.left = `${window.innerWidth}px`; | |
| enemy.style.top = `${y - 25}px`; | |
| document.getElementById('game-container').appendChild(enemy); | |
| enemies.push({ | |
| element: enemy, | |
| x: window.innerWidth, | |
| y: y, | |
| speed: 3 + Math.random() * 3, | |
| width: 50, | |
| height: 50 | |
| }); | |
| } | |
| // Update all enemies | |
| function updateEnemies() { | |
| for (let i = enemies.length - 1; i >= 0; i--) { | |
| const e = enemies[i]; | |
| e.x -= e.speed; | |
| e.element.style.left = `${e.x}px`; | |
| // Remove if off screen | |
| if (e.x < -e.width) { | |
| e.element.remove(); | |
| enemies.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Check for collisions | |
| function checkCollisions() { | |
| // Projectile-enemy collisions | |
| for (let i = projectiles.length - 1; i >= 0; i--) { | |
| const p = projectiles[i]; | |
| for (let j = enemies.length - 1; j >= 0; j--) { | |
| const e = enemies[j]; | |
| if (p.x < e.x + e.width && | |
| p.x + p.width > e.x && | |
| p.y < e.y + e.height && | |
| p.y + p.height > e.y) { | |
| // Collision detected | |
| createEnhancedExplosion(e.x + e.width/2, e.y + e.height/2); | |
| // Remove both projectile and enemy | |
| p.element.remove(); | |
| projectiles.splice(i, 1); | |
| e.element.remove(); | |
| enemies.splice(j, 1); | |
| // Increase score | |
| score += 100; | |
| document.getElementById('score').textContent = score; | |
| document.getElementById('enemies').textContent = enemies.length; | |
| break; | |
| } | |
| } | |
| } | |
| // Player-enemy collisions | |
| for (let i = enemies.length - 1; i >= 0; i--) { | |
| const e = enemies[i]; | |
| if (player.x < e.x + e.width && | |
| player.x + player.width > e.x && | |
| player.y < e.y + e.height && | |
| player.y + player.height > e.y) { | |
| // Collision detected | |
| createEnhancedExplosion(e.x + e.width/2, e.y + e.height/2); | |
| // Remove enemy | |
| e.element.remove(); | |
| enemies.splice(i, 1); | |
| // Decrease player health | |
| player.health -= 20; | |
| document.getElementById('health').textContent = player.health; | |
| if (player.health <= 0) { | |
| gameOver(); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| // Create enhanced explosion effect with particles, sparks and smoke | |
| function createEnhancedExplosion(x, y) { | |
| // Create explosion container | |
| const explosion = document.createElement('div'); | |
| explosion.className = 'explosion'; | |
| explosion.style.left = `${x - 60}px`; | |
| explosion.style.top = `${y - 60}px`; | |
| document.getElementById('game-container').appendChild(explosion); | |
| // Create shockwave | |
| const shockwave = document.createElement('div'); | |
| shockwave.className = 'shockwave'; | |
| shockwave.style.left = '60px'; | |
| shockwave.style.top = '60px'; | |
| shockwave.style.width = '40px'; | |
| shockwave.style.height = '40px'; | |
| shockwave.style.animation = 'shockwave 0.5s ease-out forwards'; | |
| explosion.appendChild(shockwave); | |
| // Create core explosion | |
| const core = document.createElement('div'); | |
| core.className = 'explosion-particle'; | |
| core.style.left = '60px'; | |
| core.style.top = '60px'; | |
| core.style.width = '40px'; | |
| core.style.height = '40px'; | |
| core.style.background = 'radial-gradient(circle, #ff9900, #ff2d7b)'; | |
| core.style.boxShadow = '0 0 20px #ff2d7b'; | |
| core.style.animation = 'explode 0.5s forwards'; | |
| explosion.appendChild(core); | |
| // Create particles (50-100) | |
| const particleCount = 50 + Math.floor(Math.random() * 50); | |
| for (let i = 0; i < particleCount; i++) { | |
| const particle = document.createElement('div'); | |
| particle.className = 'explosion-particle'; | |
| // Random size between 4-12px | |
| const size = 4 + Math.random() * 8; | |
| particle.style.width = `${size}px`; | |
| particle.style.height = `${size}px`; | |
| // Random color (yellow to red) | |
| const hue = 20 + Math.random() * 40; | |
| particle.style.background = `hsl(${hue}, 100%, 50%)`; | |
| particle.style.boxShadow = `0 0 ${size/2}px hsl(${hue}, 100%, 50%)`; | |
| // Random position around explosion center | |
| const angle = Math.random() * Math.PI * 2; | |
| const distance = 10 + Math.random() * 20; | |
| const px = 60 + Math.cos(angle) * distance; | |
| const py = 60 + Math.sin(angle) * distance; | |
| particle.style.left = `${px}px`; | |
| particle.style.top = `${py}px`; | |
| // Random movement direction and distance | |
| const tx = (Math.random() - 0.5) * 100; | |
| const ty = (Math.random() - 0.5) * 100; | |
| particle.style.setProperty('--tx', `${tx}px`); | |
| particle.style.setProperty('--ty', `${ty}px`); | |
| particle.style.animation = `particle-fly ${0.5 + Math.random() * 0.5}s ease-out forwards`; | |
| explosion.appendChild(particle); | |
| } | |
| // Create sparks (30-60) | |
| const sparkCount = 30 + Math.floor(Math.random() * 30); | |
| for (let i = 0; i < sparkCount; i++) { | |
| const spark = document.createElement('div'); | |
| spark.className = 'spark'; | |
| // Random color (white to yellow) | |
| const hue = 40 + Math.random() * 20; | |
| spark.style.background = `hsl(${hue}, 100%, 80%)`; | |
| // Random position around explosion center | |
| const angle = Math.random() * Math.PI * 2; | |
| const distance = 5 + Math.random() * 15; | |
| const px = 60 + Math.cos(angle) * distance; | |
| const py = 60 + Math.sin(angle) * distance; | |
| spark.style.left = `${px}px`; | |
| spark.style.top = `${py}px`; | |
| // Random movement direction and distance | |
| const tx = (Math.random() - 0.5) * 150; | |
| const ty = (Math.random() - 0.5) * 150; | |
| spark.style.setProperty('--tx', `${tx}px`); | |
| spark.style.setProperty('--ty', `${ty}px`); | |
| spark.style.animation = `spark-fly ${0.3 + Math.random() * 0.4}s linear forwards`; | |
| explosion.appendChild(spark); | |
| } | |
| // Create smoke (20-40) | |
| const smokeCount = 20 + Math.floor(Math.random() * 20); | |
| for (let i = 0; i < smokeCount; i++) { | |
| const smoke = document.createElement('div'); | |
| smoke.className = 'smoke'; | |
| // Random size between 10-30px | |
| const size = 10 + Math.random() * 20; | |
| smoke.style.width = `${size}px`; | |
| smoke.style.height = `${size}px`; | |
| // Random gray color | |
| const lightness = 20 + Math.random() * 30; | |
| smoke.style.background = `hsla(0, 0%, ${lightness}%, 0.7)`; | |
| // Random position around explosion center | |
| const angle = Math.random() * Math.PI * 2; | |
| const distance = 5 + Math.random() * 15; | |
| const px = 60 + Math.cos(angle) * distance; | |
| const py = 60 + Math.sin(angle) * distance; | |
| smoke.style.left = `${px}px`; | |
| smoke.style.top = `${py}px`; | |
| // Random upward movement with some horizontal variation | |
| const tx = (Math.random() - 0.5) * 40; | |
| const ty = -30 - Math.random() * 50; | |
| smoke.style.setProperty('--tx', `${tx}px`); | |
| smoke.style.setProperty('--ty', `${ty}px`); | |
| smoke.style.animation = `smoke-rise ${1 + Math.random() * 1}s ease-out forwards`; | |
| explosion.appendChild(smoke); | |
| } | |
| // Remove explosion after animation completes | |
| setTimeout(() => { | |
| explosion.remove(); | |
| }, 1000); | |
| } | |
| // Game over state | |
| function gameOver() { | |
| gameRunning = false; | |
| document.getElementById('final-score').textContent = score; | |
| document.getElementById('game-over').style.display = 'flex'; | |
| } | |
| // Reset the game | |
| function resetGame() { | |
| // Clear all elements | |
| projectiles.forEach(p => p.element.remove()); | |
| enemies.forEach(e => e.element.remove()); | |
| // Reset variables | |
| projectiles = []; | |
| enemies = []; | |
| player.health = 100; | |
| score = 0; | |
| gameRunning = true; | |
| // Update HUD | |
| document.getElementById('score').textContent = score; | |
| document.getElementById('health').textContent = player.health; | |
| document.getElementById('enemies').textContent = enemies.length; | |
| document.getElementById('game-over').style.display = 'none'; | |
| // Reset player position | |
| player.x = 100; | |
| player.y = window.innerHeight / 2; | |
| updatePlayerPosition(); | |
| // Regenerate player ship | |
| const playerShip = document.getElementById('player-ship'); | |
| playerShip.innerHTML = generatePlayerShip(); | |
| } | |
| // Handle window resize | |
| function onWindowResize() { | |
| // Update Three.js renderer | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| // Update player position if needed | |
| if (player.y > window.innerHeight - player.height / 2) { | |
| player.y = window.innerHeight - player.height / 2; | |
| updatePlayerPosition(); | |
| } | |
| } | |
| // Main game loop | |
| function gameLoop() { | |
| if (!gameRunning) return; | |
| // Update background animation | |
| updateBackground(); | |
| // Update game elements | |
| updatePlayerPosition(); | |
| updateProjectiles(); | |
| updateEnemies(); | |
| checkCollisions(); | |
| // Spawn new enemies | |
| const now = Date.now(); | |
| if (now - lastEnemySpawn > enemySpawnRate) { | |
| spawnEnemy(); | |
| lastEnemySpawn = now; | |
| // Increase difficulty | |
| if (score > 0 && score % 1000 === 0) { | |
| enemySpawnRate = Math.max(300, enemySpawnRate - 50); | |
| } | |
| } | |
| // Continue loop | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Update background animation | |
| function updateBackground() { | |
| // Rotate wormhole | |
| wormhole.rotation.z += 0.005; | |
| // Update debris particles | |
| debrisParticles.forEach(debris => { | |
| debris.position.add(debris.userData.velocity); | |
| debris.rotation.x += debris.userData.rotationSpeed.x; | |
| debris.rotation.y += debris.userData.rotationSpeed.y; | |
| debris.rotation.z += debris.userData.rotationSpeed.z; | |
| // Wrap around if out of bounds | |
| if (Math.abs(debris.position.x) > 100) debris.position.x = -debris.position.x; | |
| if (Math.abs(debris.position.y) > 100) debris.position.y = -debris.position.y; | |
| if (Math.abs(debris.position.z) > 100) debris.position.z = -debris.position.z; | |
| }); | |
| renderer.render(scene, camera); | |
| } | |
| // Initialize everything | |
| function init() { | |
| initBackground(); | |
| initGame(); | |
| } | |
| // Start the game | |
| init(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LukasBe/andromeda-blast" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |