// loadingdecoding pod stream
15.7 KB · main
orbit-hopper/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>Orbit Hopper</title>7 <style>8 :root {9 color-scheme: dark;10 --bg1: #050816;11 --bg2: #0f1f3d;12 --panel: rgba(11, 18, 37, 0.78);13 --line: rgba(168, 213, 255, 0.18);14 --text: #eef6ff;15 --accent: #83f2ff;16 --warn: #ff8c69;17 --gold: #ffd76b;18 }19 * { box-sizing: border-box; }20 body {21 margin: 0;22 min-height: 100vh;23 display: grid;24 place-items: center;25 overflow: hidden;26 font-family: Inter, ui-sans-serif, system-ui, sans-serif;27 color: var(--text);28 background:29 radial-gradient(circle at top, rgba(90, 150, 255, 0.18), transparent 30%),30 radial-gradient(circle at bottom, rgba(0, 255, 217, 0.1), transparent 28%),31 linear-gradient(180deg, var(--bg2), var(--bg1));32 }33 .shell {34 width: min(100vw, 980px);35 padding: 16px;36 }37 .hud {38 display: flex;39 justify-content: space-between;40 gap: 12px;41 flex-wrap: wrap;42 margin-bottom: 12px;43 }44 .card {45 background: var(--panel);46 border: 1px solid var(--line);47 border-radius: 16px;48 padding: 10px 14px;49 backdrop-filter: blur(14px);50 box-shadow: 0 16px 40px rgba(0, 0, 0, 0.24);51 }52 .stats {53 display: flex;54 gap: 16px;55 align-items: center;56 flex-wrap: wrap;57 }58 .label {59 font-size: 12px;60 text-transform: uppercase;61 letter-spacing: 0.18em;62 opacity: 0.72;63 }64 .value {65 font-size: 22px;66 font-weight: 800;67 margin-top: 2px;68 }69 .hint {70 font-size: 14px;71 opacity: 0.84;72 max-width: 360px;73 line-height: 1.45;74 }75 .stage {76 position: relative;77 border-radius: 24px;78 overflow: hidden;79 border: 1px solid var(--line);80 box-shadow: 0 30px 80px rgba(0, 0, 0, 0.35);81 }82 canvas {83 display: block;84 width: 100%;85 height: auto;86 background: radial-gradient(circle at center, rgba(44, 78, 133, 0.32), rgba(3, 8, 23, 0.98));87 }88 .overlay {89 position: absolute;90 inset: 0;91 display: grid;92 place-items: center;93 pointer-events: none;94 padding: 20px;95 }96 .panel {97 text-align: center;98 width: min(92%, 420px);99 padding: 24px 22px;100 border-radius: 24px;101 background: rgba(4, 9, 22, 0.74);102 border: 1px solid rgba(169, 221, 255, 0.24);103 backdrop-filter: blur(20px);104 box-shadow: 0 24px 60px rgba(0, 0, 0, 0.34);105 }106 h1 {107 margin: 0 0 10px;108 font-size: clamp(30px, 6vw, 44px);109 letter-spacing: 0.04em;110 }111 p {112 margin: 8px 0;113 line-height: 1.5;114 opacity: 0.9;115 }116 .pill {117 display: inline-flex;118 gap: 8px;119 align-items: center;120 margin-top: 10px;121 padding: 10px 14px;122 border-radius: 999px;123 background: rgba(131, 242, 255, 0.12);124 border: 1px solid rgba(131, 242, 255, 0.24);125 color: var(--accent);126 font-weight: 700;127 font-size: 14px;128 }129 </style>130</head>131<body>132 <div class="shell">133 <div class="hud">134 <div class="card stats">135 <div><div class="label">Score</div><div class="value" id="score">0</div></div>136 <div><div class="label">Best</div><div class="value" id="best">0</div></div>137 <div><div class="label">Lives</div><div class="value" id="lives">3</div></div>138 </div>139 <div class="card hint">Circle the arena, grab starlight, and dodge the meteor swarm. Move with A/D or ←/→. Press Space to hop across rings.</div>140 </div>141 <div class="stage">142 <canvas id="game" width="960" height="640"></canvas>143 <div class="overlay" id="overlay">144 <div class="panel">145 <h1>Orbit Hopper</h1>146 <p>You are a tiny pulse rider surfing three orbital lanes. Collect shimmering stars to raise your score while meteor shards sweep around the core.</p>147 <p>Hit Space to start, then Space again any time to leap inward or outward one ring.</p>148 <div class="pill">Space to launch</div>149 </div>150 </div>151 </div>152 </div>153 <script>154155 const canvas = document.getElementById('game');156 const ctx = canvas.getContext('2d');157 const overlay = document.getElementById('overlay');158 const scoreEl = document.getElementById('score');159 const bestEl = document.getElementById('best');160 const livesEl = document.getElementById('lives');161162 const center = { x: canvas.width / 2, y: canvas.height / 2 };163 const rings = [118, 182, 246];164 const state = {165 running: false,166 score: 0,167 best: Number(localStorage.getItem('orbit-hopper-best') || 0),168 lives: 3,169 spawnTimer: 0,170 cometTimer: 0,171 time: 0,172 pulse: 0,173 message: 'Space to launch'174 };175176 const player = {177 lane: 1,178 angle: -Math.PI / 2,179 speed: 1.7,180 radius: 12,181 flash: 0,182 invincible: 0,183 trail: []184 };185186 const stars = [];187 const hazards = [];188 const particles = [];189 const keys = new Set();190191 function resetGame() {192 state.running = true;193 state.score = 0;194 state.lives = 3;195 state.spawnTimer = 0;196 state.cometTimer = 0;197 state.time = 0;198 state.message = 'Collect the stars';199 player.lane = 1;200 player.angle = -Math.PI / 2;201 player.speed = 1.7;202 player.flash = 0;203 player.invincible = 1.2;204 player.trail = [];205 stars.length = 0;206 hazards.length = 0;207 particles.length = 0;208 for (let i = 0; i < 3; i++) spawnStar();209 overlay.style.display = 'none';210 syncHud();211 }212213 function syncHud() {214 scoreEl.textContent = Math.floor(state.score);215 bestEl.textContent = Math.floor(state.best);216 livesEl.textContent = state.lives;217 }218219 function playerPos() {220 const r = rings[player.lane];221 return {222 x: center.x + Math.cos(player.angle) * r,223 y: center.y + Math.sin(player.angle) * r224 };225 }226227 function spawnStar() {228 let tries = 0;229 while (tries++ < 40) {230 const lane = Math.floor(Math.random() * rings.length);231 const angle = Math.random() * Math.PI * 2;232 const tooClose = stars.some(s => s.lane === lane && Math.abs(normalizeAngle(s.angle - angle)) < 0.45);233 if (!tooClose) {234 stars.push({ lane, angle, size: 8 + Math.random() * 6, spin: Math.random() * 6.28 });235 return;236 }237 }238 }239240 function spawnHazard() {241 const lane = Math.floor(Math.random() * rings.length);242 const dir = Math.random() > 0.5 ? 1 : -1;243 hazards.push({244 lane,245 angle: dir > 0 ? -0.35 : Math.PI + 0.35,246 speed: (0.8 + Math.random() * 0.7) * dir,247 size: 12 + Math.random() * 10,248 wobble: Math.random() * 6.28,249 hue: 16 + Math.random() * 18250 });251 }252253 function hop(direction) {254 const next = Math.max(0, Math.min(rings.length - 1, player.lane + direction));255 if (next !== player.lane) {256 player.lane = next;257 player.flash = 0.28;258 burst(playerPos(), '#83f2ff', 14, 2.5);259 }260 }261262 function burst(pos, color, count, speed) {263 for (let i = 0; i < count; i++) {264 const a = Math.random() * Math.PI * 2;265 const s = speed * (0.3 + Math.random());266 particles.push({267 x: pos.x,268 y: pos.y,269 vx: Math.cos(a) * s,270 vy: Math.sin(a) * s,271 life: 0.4 + Math.random() * 0.5,272 maxLife: 0.9,273 color274 });275 }276 }277278 function hitPlayer() {279 if (player.invincible > 0) return;280 state.lives -= 1;281 player.invincible = 2;282 player.flash = 0.45;283 const pos = playerPos();284 burst(pos, '#ff8c69', 22, 3.5);285 syncHud();286 if (state.lives <= 0) {287 state.running = false;288 state.best = Math.max(state.best, state.score);289 localStorage.setItem('orbit-hopper-best', String(Math.floor(state.best)));290 syncHud();291 overlay.innerHTML = `292 <div class="panel">293 <h1>Run complete</h1>294 <p>You banked <strong>${Math.floor(state.score)}</strong> starlight.</p>295 <p>Meteor density rises over time, so keep shifting lanes and plan two hazards ahead.</p>296 <div class="pill">Press Space to try again</div>297 </div>`;298 overlay.style.display = 'grid';299 }300 }301302 function normalizeAngle(a) {303 while (a > Math.PI) a -= Math.PI * 2;304 while (a < -Math.PI) a += Math.PI * 2;305 return a;306 }307308 function update(dt) {309 if (!state.running) return;310 state.time += dt;311 state.pulse += dt * 2.3;312 player.flash = Math.max(0, player.flash - dt);313 player.invincible = Math.max(0, player.invincible - dt);314315 const turn = (keys.has('ArrowLeft') || keys.has('KeyA') ? -1 : 0) + (keys.has('ArrowRight') || keys.has('KeyD') ? 1 : 0);316 player.angle += turn * player.speed * dt;317 player.speed = Math.min(3.25, 1.7 + state.time * 0.022);318319 const pos = playerPos();320 player.trail.push({ ...pos, life: 0.55 });321 if (player.trail.length > 22) player.trail.shift();322 for (const t of player.trail) t.life -= dt;323 while (player.trail.length && player.trail[0].life <= 0) player.trail.shift();324325 state.spawnTimer -= dt;326 state.cometTimer -= dt;327 if (state.spawnTimer <= 0 && stars.length < 5) {328 spawnStar();329 state.spawnTimer = 1.1;330 }331 if (state.cometTimer <= 0) {332 spawnHazard();333 if (state.time > 18 && Math.random() > 0.45) spawnHazard();334 state.cometTimer = Math.max(0.45, 1.7 - state.time * 0.035);335 }336337 for (const star of stars) star.spin += dt * 3;338 for (const hazard of hazards) {339 hazard.angle += hazard.speed * dt;340 hazard.wobble += dt * 4;341 }342343 for (let i = stars.length - 1; i >= 0; i--) {344 const star = stars[i];345 if (star.lane !== player.lane) continue;346 const delta = Math.abs(normalizeAngle(star.angle - player.angle));347 if (delta < 0.17) {348 stars.splice(i, 1);349 state.score += 10;350 state.best = Math.max(state.best, state.score);351 burst(pos, '#ffd76b', 16, 3.2);352 syncHud();353 }354 }355356 for (let i = hazards.length - 1; i >= 0; i--) {357 const hz = hazards[i];358 const delta = Math.abs(normalizeAngle(hz.angle - player.angle));359 if (hz.lane === player.lane && delta < 0.15) {360 hitPlayer();361 hazards.splice(i, 1);362 continue;363 }364 if (Math.abs(hz.angle) > Math.PI * 2 + 0.8) hazards.splice(i, 1);365 }366367 for (let i = particles.length - 1; i >= 0; i--) {368 const p = particles[i];369 p.x += p.vx;370 p.y += p.vy;371 p.vx *= 0.985;372 p.vy *= 0.985;373 p.life -= dt;374 if (p.life <= 0) particles.splice(i, 1);375 }376 }377378 function drawStar(x, y, size, spin) {379 ctx.save();380 ctx.translate(x, y);381 ctx.rotate(spin);382 ctx.beginPath();383 for (let i = 0; i < 5; i++) {384 const outer = size;385 const inner = size * 0.42;386 const a = -Math.PI / 2 + (i * Math.PI * 2) / 5;387 const b = a + Math.PI / 5;388 ctx.lineTo(Math.cos(a) * outer, Math.sin(a) * outer);389 ctx.lineTo(Math.cos(b) * inner, Math.sin(b) * inner);390 }391 ctx.closePath();392 ctx.fillStyle = '#ffd76b';393 ctx.shadowColor = 'rgba(255, 215, 107, 0.6)';394 ctx.shadowBlur = 18;395 ctx.fill();396 ctx.restore();397 }398399 function render() {400 ctx.clearRect(0, 0, canvas.width, canvas.height);401 const g = ctx.createRadialGradient(center.x, center.y, 40, center.x, center.y, 420);402 g.addColorStop(0, 'rgba(96, 151, 255, 0.22)');403 g.addColorStop(0.55, 'rgba(17, 30, 60, 0.22)');404 g.addColorStop(1, 'rgba(2, 6, 18, 0.96)');405 ctx.fillStyle = g;406 ctx.fillRect(0, 0, canvas.width, canvas.height);407408 for (let i = 0; i < 44; i++) {409 const x = (i * 89) % canvas.width;410 const y = (i * 173) % canvas.height;411 ctx.fillStyle = `rgba(255,255,255,${0.15 + ((i * 7) % 10) / 50})`;412 ctx.fillRect(x, y, 2, 2);413 }414415 rings.forEach((r, idx) => {416 ctx.beginPath();417 ctx.arc(center.x, center.y, r, 0, Math.PI * 2);418 ctx.strokeStyle = idx === player.lane ? 'rgba(131,242,255,0.26)' : 'rgba(168,213,255,0.12)';419 ctx.lineWidth = idx === player.lane ? 4 : 2;420 ctx.setLineDash([10, 12]);421 ctx.stroke();422 });423 ctx.setLineDash([]);424425 ctx.beginPath();426 ctx.arc(center.x, center.y, 54 + Math.sin(state.pulse) * 3, 0, Math.PI * 2);427 ctx.fillStyle = 'rgba(92, 133, 255, 0.24)';428 ctx.shadowColor = 'rgba(110, 169, 255, 0.38)';429 ctx.shadowBlur = 30;430 ctx.fill();431 ctx.shadowBlur = 0;432433 for (const star of stars) {434 const r = rings[star.lane];435 drawStar(center.x + Math.cos(star.angle) * r, center.y + Math.sin(star.angle) * r, star.size, star.spin);436 }437438 for (const hz of hazards) {439 const r = rings[hz.lane];440 const x = center.x + Math.cos(hz.angle) * r;441 const y = center.y + Math.sin(hz.angle) * r;442 const size = hz.size + Math.sin(hz.wobble) * 2;443 ctx.save();444 ctx.translate(x, y);445 ctx.rotate(hz.angle * 2.4);446 ctx.fillStyle = `hsla(${hz.hue}, 100%, 64%, 0.95)`;447 ctx.shadowColor = 'rgba(255, 120, 72, 0.46)';448 ctx.shadowBlur = 22;449 ctx.beginPath();450 for (let i = 0; i < 6; i++) {451 const a = (i / 6) * Math.PI * 2;452 const rr = i % 2 === 0 ? size : size * 0.48;453 ctx.lineTo(Math.cos(a) * rr, Math.sin(a) * rr);454 }455 ctx.closePath();456 ctx.fill();457 ctx.restore();458 }459460 for (const t of player.trail) {461 ctx.beginPath();462 ctx.arc(t.x, t.y, 4 + t.life * 8, 0, Math.PI * 2);463 ctx.fillStyle = `rgba(131,242,255,${t.life * 0.35})`;464 ctx.fill();465 }466467 const pos = playerPos();468 ctx.save();469 ctx.translate(pos.x, pos.y);470 ctx.rotate(player.angle + Math.PI / 2);471 ctx.fillStyle = player.invincible > 0 && Math.floor(player.invincible * 10) % 2 === 0 ? '#ffffff' : '#83f2ff';472 ctx.shadowColor = player.flash > 0 ? 'rgba(255,255,255,0.85)' : 'rgba(131,242,255,0.5)';473 ctx.shadowBlur = 24;474 ctx.beginPath();475 ctx.moveTo(0, -18);476 ctx.lineTo(12, 10);477 ctx.lineTo(0, 4);478 ctx.lineTo(-12, 10);479 ctx.closePath();480 ctx.fill();481 ctx.restore();482483 for (const p of particles) {484 ctx.beginPath();485 ctx.arc(p.x, p.y, 2 + p.life * 4, 0, Math.PI * 2);486 ctx.fillStyle = p.color.replace(')', `, ${Math.max(0, p.life)})`).replace('rgb', 'rgba');487 if (!p.color.startsWith('rgb')) ctx.fillStyle = p.color;488 ctx.globalAlpha = Math.max(0, p.life);489 ctx.fill();490 ctx.globalAlpha = 1;491 }492493 ctx.fillStyle = 'rgba(238,246,255,0.9)';494 ctx.font = '700 16px Inter, sans-serif';495 ctx.fillText(state.message, 24, 34);496 }497498 let last = performance.now();499 function frame(now) {500 const dt = Math.min(0.033, (now - last) / 1000);501 last = now;502 update(dt);503 render();504 requestAnimationFrame(frame);505 }506507 window.addEventListener('keydown', (e) => {508 if (['ArrowLeft','ArrowRight','Space','KeyA','KeyD'].includes(e.code)) e.preventDefault();509 keys.add(e.code);510 if (e.code === 'Space') {511 if (!state.running) {512 resetGame();513 } else {514 hop(player.lane < 2 ? 1 : -1);515 }516 }517 if (e.code === 'ArrowUp' || e.code === 'KeyW') hop(-1);518 if (e.code === 'ArrowDown' || e.code === 'KeyS') hop(1);519 });520 window.addEventListener('keyup', (e) => keys.delete(e.code));521522 bestEl.textContent = state.best;523 requestAnimationFrame(frame);524 </script>525</body>526</html>