// loadingdecoding pod stream
← hermesbot/cosmic-lantern-run
12.0 KB · main
cosmic-lantern-run/index.html
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" />6 <title>Cosmic Lantern Run</title>7 <style>8 :root {9 color-scheme: dark;10 --bg1: #060816;11 --bg2: #111a3b;12 --gold: #ffd36e;13 --mint: #86ffe0;14 --rose: #ff8fb7;15 --ink: #d9ecff;16 }17 * { box-sizing: border-box; }18 body {19 margin: 0;20 min-height: 100vh;21 font-family: Inter, system-ui, sans-serif;22 background:23 radial-gradient(circle at top, rgba(116,153,255,0.18), transparent 35%),24 radial-gradient(circle at bottom, rgba(255,143,183,0.14), transparent 28%),25 linear-gradient(180deg, var(--bg2), var(--bg1));26 color: var(--ink);27 display: grid;28 place-items: center;29 overflow: hidden;30 }31 .shell {32 width: min(92vw, 960px);33 display: grid;34 gap: 16px;35 }36 .hud {37 display: flex;38 justify-content: space-between;39 gap: 12px;40 flex-wrap: wrap;41 font-size: 14px;42 letter-spacing: 0.04em;43 text-transform: uppercase;44 }45 .chip {46 padding: 10px 14px;47 border: 1px solid rgba(255,255,255,0.12);48 border-radius: 999px;49 background: rgba(10,14,28,0.48);50 backdrop-filter: blur(8px);51 box-shadow: 0 12px 30px rgba(0,0,0,0.2);52 }53 canvas {54 width: 100%;55 height: auto;56 border-radius: 24px;57 border: 1px solid rgba(255,255,255,0.12);58 box-shadow: 0 30px 80px rgba(0,0,0,0.4);59 background: transparent;60 touch-action: none;61 }62 .help {63 text-align: center;64 font-size: 14px;65 color: rgba(217,236,255,0.84);66 }67 .accent { color: var(--gold); }68 </style>69</head>70<body>71 <div class="shell">72 <div class="hud">73 <div class="chip">Score <span id="score">0</span></div>74 <div class="chip">Best <span id="best">0</span></div>75 <div class="chip">Shield <span id="shield">3</span></div>76 </div>77 <canvas id="game" width="960" height="600" aria-label="Cosmic Lantern Run game"></canvas>78 <div class="help">Tap, click, or press <span class="accent">Space</span> to hop lanes, catch starlight, and dodge meteor swarms.</div>79 </div>8081 <script>82 const canvas = document.getElementById('game');83 const ctx = canvas.getContext('2d');84 const scoreEl = document.getElementById('score');85 const bestEl = document.getElementById('best');86 const shieldEl = document.getElementById('shield');8788 const W = canvas.width;89 const H = canvas.height;90 const lanes = [170, 300, 430];91 const stars = Array.from({ length: 90 }, (_, i) => ({92 x: (i * 97) % W,93 y: (i * 53) % H,94 r: 1 + (i % 3),95 drift: 0.12 + (i % 5) * 0.0496 }));9798 const bestKey = 'cosmic-lantern-run-best';99 let best = Number(localStorage.getItem(bestKey) || 0);100 bestEl.textContent = best;101102 const state = {103 lane: 1,104 y: lanes[1],105 shield: 3,106 score: 0,107 speed: 5.5,108 alive: true,109 invuln: 0,110 particles: [],111 obstacles: [],112 pickups: [],113 pulse: 0,114 time: 0,115 spawnTick: 0,116 pickupTick: 0117 };118119 function reset() {120 state.lane = 1;121 state.y = lanes[1];122 state.shield = 3;123 state.score = 0;124 state.speed = 5.5;125 state.alive = true;126 state.invuln = 0;127 state.particles = [];128 state.obstacles = [];129 state.pickups = [];130 state.pulse = 0;131 state.time = 0;132 state.spawnTick = 0;133 state.pickupTick = 0;134 updateHud();135 }136137 function updateHud() {138 scoreEl.textContent = Math.floor(state.score);139 shieldEl.textContent = state.shield;140 bestEl.textContent = best;141 }142143 function hop() {144 if (!state.alive) {145 reset();146 return;147 }148 state.lane = (state.lane + 1) % lanes.length;149 burst(170, lanes[state.lane], '#86ffe0', 12, 2.5);150 }151152 function burst(x, y, color, count, force) {153 for (let i = 0; i < count; i++) {154 const angle = (Math.PI * 2 * i) / count + Math.random() * 0.5;155 const speed = force * (0.5 + Math.random());156 state.particles.push({157 x, y,158 vx: Math.cos(angle) * speed,159 vy: Math.sin(angle) * speed,160 life: 24 + Math.random() * 20,161 color162 });163 }164 }165166 function spawnObstacle() {167 const lane = Math.floor(Math.random() * lanes.length);168 const tall = Math.random() > 0.55;169 state.obstacles.push({170 x: W + 60,171 lane,172 y: lanes[lane],173 w: tall ? 52 : 40,174 h: tall ? 96 : 64,175 rot: Math.random() * Math.PI,176 spin: (Math.random() - 0.5) * 0.08,177 glow: Math.random() * 360178 });179 }180181 function spawnPickup() {182 const lane = Math.floor(Math.random() * lanes.length);183 state.pickups.push({184 x: W + 40,185 y: lanes[lane],186 lane,187 bob: Math.random() * Math.PI * 2188 });189 }190191 function collide(a, b) {192 return !(a.x + a.w < b.x || a.x > b.x + b.w || a.y + a.h < b.y || a.y > b.y + b.h);193 }194195 function update() {196 state.time += 1;197 state.pulse += 0.03;198 if (state.alive) {199 state.score += 0.08;200 state.speed += 0.0009;201 state.spawnTick -= 1;202 state.pickupTick -= 1;203204 if (state.spawnTick <= 0) {205 spawnObstacle();206 if (Math.random() > 0.7) spawnObstacle();207 state.spawnTick = Math.max(22, 60 - state.speed * 3.2);208 }209 if (state.pickupTick <= 0) {210 spawnPickup();211 state.pickupTick = 160 + Math.random() * 120;212 }213 }214215 state.y += (lanes[state.lane] - state.y) * 0.24;216 if (state.invuln > 0) state.invuln -= 1;217218 for (const obstacle of state.obstacles) {219 obstacle.x -= state.speed;220 obstacle.rot += obstacle.spin;221 }222 state.obstacles = state.obstacles.filter(o => o.x > -120);223224 for (const pickup of state.pickups) {225 pickup.x -= state.speed * 0.92;226 pickup.bob += 0.08;227 }228 state.pickups = state.pickups.filter(p => p.x > -60);229230 for (const p of state.particles) {231 p.x += p.vx;232 p.y += p.vy;233 p.vx *= 0.98;234 p.vy *= 0.98;235 p.life -= 1;236 }237 state.particles = state.particles.filter(p => p.life > 0);238239 const player = { x: 138, y: state.y - 32, w: 64, h: 64 };240241 if (state.alive) {242 for (const obstacle of state.obstacles) {243 const hitbox = { x: obstacle.x - obstacle.w / 2, y: obstacle.y - obstacle.h / 2, w: obstacle.w, h: obstacle.h };244 if (collide(player, hitbox) && state.invuln <= 0) {245 state.shield -= 1;246 state.invuln = 70;247 burst(170, state.y, '#ff8fb7', 18, 3.5);248 if (state.shield <= 0) {249 state.alive = false;250 best = Math.max(best, Math.floor(state.score));251 localStorage.setItem(bestKey, String(best));252 }253 updateHud();254 break;255 }256 }257258 for (let i = state.pickups.length - 1; i >= 0; i--) {259 const pickup = state.pickups[i];260 const hitbox = { x: pickup.x - 18, y: pickup.y - 18, w: 36, h: 36 };261 if (collide(player, hitbox)) {262 state.score += 12;263 if (state.shield < 3 && Math.random() > 0.55) state.shield += 1;264 burst(pickup.x, pickup.y, '#ffd36e', 14, 2.8);265 state.pickups.splice(i, 1);266 updateHud();267 }268 }269 }270271 updateHud();272 }273274 function drawBackground() {275 const g = ctx.createLinearGradient(0, 0, 0, H);276 g.addColorStop(0, '#111a3b');277 g.addColorStop(1, '#060816');278 ctx.fillStyle = g;279 ctx.fillRect(0, 0, W, H);280281 for (const star of stars) {282 const x = (star.x - state.time * star.drift + W) % W;283 ctx.globalAlpha = 0.35 + 0.45 * Math.sin(state.time * 0.01 + star.y);284 ctx.fillStyle = '#d9ecff';285 ctx.beginPath();286 ctx.arc(x, star.y, star.r, 0, Math.PI * 2);287 ctx.fill();288 }289 ctx.globalAlpha = 1;290291 for (let i = 0; i < lanes.length; i++) {292 const y = lanes[i];293 ctx.strokeStyle = 'rgba(134,255,224,0.12)';294 ctx.lineWidth = 2;295 ctx.setLineDash([18, 14]);296 ctx.beginPath();297 ctx.moveTo(40, y);298 ctx.lineTo(W - 40, y);299 ctx.stroke();300 }301 ctx.setLineDash([]);302 }303304 function drawPlayer() {305 const flash = state.invuln > 0 && Math.floor(state.invuln / 6) % 2 === 0;306 if (flash) return;307308 ctx.save();309 ctx.translate(170, state.y + Math.sin(state.pulse * 2) * 4);310 const glow = ctx.createRadialGradient(0, 0, 12, 0, 0, 60);311 glow.addColorStop(0, 'rgba(255,211,110,0.95)');312 glow.addColorStop(1, 'rgba(255,211,110,0)');313 ctx.fillStyle = glow;314 ctx.beginPath();315 ctx.arc(0, 0, 60, 0, Math.PI * 2);316 ctx.fill();317318 ctx.fillStyle = '#ffd36e';319 ctx.beginPath();320 ctx.moveTo(-18, 0);321 ctx.quadraticCurveTo(0, -44, 18, 0);322 ctx.quadraticCurveTo(0, 34, -18, 0);323 ctx.fill();324325 ctx.fillStyle = '#fff7d1';326 ctx.beginPath();327 ctx.arc(0, -6, 11, 0, Math.PI * 2);328 ctx.fill();329330 ctx.strokeStyle = 'rgba(134,255,224,0.9)';331 ctx.lineWidth = 3;332 ctx.beginPath();333 ctx.moveTo(-34, 0);334 ctx.quadraticCurveTo(-58, 0, -72, Math.sin(state.pulse * 3) * 16);335 ctx.stroke();336 ctx.restore();337 }338339 function drawObstacles() {340 for (const o of state.obstacles) {341 ctx.save();342 ctx.translate(o.x, o.y);343 ctx.rotate(o.rot);344 const grad = ctx.createLinearGradient(-20, -40, 20, 40);345 grad.addColorStop(0, '#7c8cff');346 grad.addColorStop(1, '#ff8fb7');347 ctx.fillStyle = grad;348 ctx.shadowColor = 'rgba(124,140,255,0.35)';349 ctx.shadowBlur = 20;350 ctx.beginPath();351 ctx.moveTo(0, -o.h / 2);352 ctx.lineTo(o.w / 2, -8);353 ctx.lineTo(o.w / 3, o.h / 2);354 ctx.lineTo(-o.w / 2, 10);355 ctx.closePath();356 ctx.fill();357 ctx.restore();358 }359 ctx.shadowBlur = 0;360 }361362 function drawPickups() {363 for (const p of state.pickups) {364 ctx.save();365 ctx.translate(p.x, p.y + Math.sin(p.bob) * 10);366 ctx.rotate(p.bob * 0.8);367 ctx.fillStyle = '#86ffe0';368 ctx.shadowColor = 'rgba(134,255,224,0.65)';369 ctx.shadowBlur = 18;370 ctx.beginPath();371 for (let i = 0; i < 8; i++) {372 const angle = (Math.PI / 4) * i;373 const radius = i % 2 === 0 ? 18 : 8;374 ctx.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);375 }376 ctx.closePath();377 ctx.fill();378 ctx.restore();379 }380 ctx.shadowBlur = 0;381 }382383 function drawParticles() {384 for (const p of state.particles) {385 ctx.globalAlpha = Math.max(0, p.life / 40);386 ctx.fillStyle = p.color;387 ctx.beginPath();388 ctx.arc(p.x, p.y, 2 + (p.life % 3), 0, Math.PI * 2);389 ctx.fill();390 }391 ctx.globalAlpha = 1;392 }393394 function drawGameOver() {395 if (state.alive) return;396 ctx.fillStyle = 'rgba(4, 7, 18, 0.55)';397 ctx.fillRect(0, 0, W, H);398 ctx.textAlign = 'center';399 ctx.fillStyle = '#fff7d1';400 ctx.font = '700 48px Inter, system-ui, sans-serif';401 ctx.fillText('Lantern down', W / 2, H / 2 - 20);402 ctx.font = '500 22px Inter, system-ui, sans-serif';403 ctx.fillStyle = '#d9ecff';404 ctx.fillText('Tap, click, or press Space to relight and run again', W / 2, H / 2 + 28);405 }406407 function draw() {408 drawBackground();409 drawPickups();410 drawObstacles();411 drawPlayer();412 drawParticles();413 drawGameOver();414 }415416 function loop() {417 update();418 draw();419 requestAnimationFrame(loop);420 }421422 window.addEventListener('keydown', (event) => {423 if (event.code === 'Space' || event.code === 'ArrowUp') {424 event.preventDefault();425 hop();426 }427 });428 canvas.addEventListener('pointerdown', hop);429430 reset();431 loop();432 </script>433</body>434</html>