Canyon Run
Qwen 3.7 Flash · typescript

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 48,219 bytes · SHA-256 d44f6ad92338
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canyon Run — Procedural Racer</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { overflow: hidden; background: #1a1a2e; width: 100%; height: 100%; }
canvas { display: block; width: 100vw; height: 100vh; touch-action: none; }
@keyframes pulse {
0% { transform: translateX(-50%) scale(1); }
50% { transform: translateX(-50%) scale(1.3); }
100% { transform: translateX(-50%) scale(1); }
}
/* ── css: hud ── */
#hud { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 50; font-family: 'Courier New', monospace; pointer-events: none; }
#hud-score-el {
position: fixed; top: 24px; left: 50%; transform: translateX(-50%);
font-size: 32px; color: #fff; letter-spacing: 4px;
text-shadow: 0 0 15px rgba(255,140,66,0.6), 0 0 30px rgba(255,140,66,0.3);
font-weight: bold;
}
#hud-multi-el {
position: fixed; top: 70px; left: 50%; transform: translateX(-50%);
color: #ccc; letter-spacing: 2px; font-size: 22px;
transition: all 0.15s ease-out;
}
@keyframes multi-pulse {
0% { transform: translateX(-50%) scale(1); opacity: 1; }
50% { transform: translateX(-50%) scale(1.4); opacity: 0.8; color: #ff8c42; text-shadow: 0 0 20px #ff8c42; }
100% { transform: translateX(-50%) scale(1); opacity: 1; }
}
#hud-multi-el.pulse-active { animation: multi-pulse 0.2s ease-out infinite !important; }
#hud-speed-el {
position: fixed; bottom: 55px; right: 25px; font-size: 13px;
color: rgba(255,255,255,0.4); letter-spacing: 1px;
}
#hud-energy-bar {
position: fixed; top: 105px; left: 50%; transform: translateX(-50%);
width: 200px; height: 4px; background: rgba(255,255,255,0.08); border-radius: 2px;
overflow: hidden;
}
#energy-fill-inner {
height: 100%; border-radius: 2px; transition: width 0.15s linear, background-color 0.3s;
}
#hud-boost {
position: fixed; bottom: 15px; left: 50%; transform: translateX(-50%);
width: 200px; height: 5px; background: rgba(255,255,255,0.08); border-radius: 3px;
overflow: hidden;
}
#boost-fill-inner {
height: 100%; width: 0%; border-radius: 3px;
transition: width 0.1s linear;
}
#hud-gameover-overlay {
display: none; position: fixed; inset: 0;
background: radial-gradient(ellipse at center, rgba(30,10,5,0.9) 0%, rgba(0,0,0,0.95) 100%);
justify-content: center; align-items: center; flex-direction: column; z-index: 100;
}
.go-title {
font-size: 42px; color: #ff3344; font-family: 'Courier New', monospace;
margin-bottom: 30px; letter-spacing: 8px; text-transform: uppercase;
text-shadow: 0 0 20px rgba(255,51,68,0.5);
}
.go-label { font-size: 12px; color: #888; font-family: monospace; margin-bottom: 4px; letter-spacing: 2px; text-transform: uppercase; }
.go-value { font-size: 48px; color: #ff8c42; font-family: monospace; margin-bottom: 20px; text-shadow: 0 0 15px rgba(255,140,66,0.4); }
.go-subtitle { font-size: 14px; color: #aaa; font-family: monospace; margin-bottom: 4px; }
.go-speed-val { font-size: 18px; color: #bbb; font-family: monospace; margin-bottom: 30px; }
#restart-btn {
padding: 14px 50px; font-size: 18px; background: transparent; color: #fff;
border: 2px solid #ff8c42; border-radius: 4px; cursor: pointer;
font-family: 'Courier New', monospace; letter-spacing: 3px;
pointer-events: auto; transition: all 0.2s;
}
#restart-btn:hover { background: rgba(255,140,66,0.15); box-shadow: 0 0 20px rgba(255,140,66,0.3); }
</style>
</head>
<body>
<div id="app"></div>
<div id="loading-overlay" style="position:fixed;inset:0;background:#1a1a2e;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:999;color:#ff8c42;font-family:'Courier New',monospace;">
<h1 style="font-size:36px;letter-spacing:6px;margin-bottom:20px;">CANYON RUN</h1>
<p style="color:#aaa;font-size:14px;">Mouse to steer · Collect energy orbs · Avoid walls</p>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./constants":"src/constants.ts","./player":"src/player.ts","./canyon":"src/canyon.ts","./particles":"src/particles.ts","./audio":"src/audio.ts","./hud":"src/hud.ts"},"src/player.ts":{"./constants":"src/constants.ts"},"src/canyon.ts":{"./constants":"src/constants.ts"},"src/particles.ts":{"./constants":"src/constants.ts"},"src/hud.ts":{"./constants":"src/constants.ts"}};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
const constants_1 = require("./constants");
const player_1 = require("./player");
const canyon_1 = require("./canyon");
const particles_1 = require("./particles");
const audio_1 = require("./audio");
const hud_1 = require("./hud");
// THREE is a global from the CDN import
let scene;
let camera;
let renderer;
let worldGroup;
let playerMesh;
let playerState;
let trailSystem;
let dustSystem;
let audioSynth;
let hud;
let gameState = 'playing';
let score = 0;
let gameTime = 0;
let inputX = 0;
let inputY = 0;
let lastTime = 0;
let camShakeX = 0;
let camShakeY = 0;
function setupInput() {
document.addEventListener('keydown', (e) => {
if (e.key === ' ' && gameState === 'gameover')
startGame();
});
document.addEventListener('mousemove', (e) => {
inputX = (e.clientX / window.innerWidth - 0.5) * 2;
inputY = -(e.clientY / window.innerHeight - 0.5) * 2;
});
document.addEventListener('touchmove', (e) => {
e.preventDefault();
const t = e.touches[0];
inputX = (t.clientX / window.innerWidth - 0.5) * 2;
inputY = -(t.clientY / window.innerHeight - 0.5) * 2;
}, { passive: false });
// Decay input when idle
setInterval(() => {
if (!document.querySelector(':focus')) {
inputX *= 0.9;
inputY *= 0.9;
if (Math.abs(inputX) < 0.01)
inputX = 0;
if (Math.abs(inputY) < 0.01)
inputY = 0;
}
}, 50);
}
function startGame() {
gameState = 'playing';
score = 0;
gameTime = 0;
inputX = 0;
inputY = 0;
camShakeX = 0;
camShakeY = 0;
(0, player_1.resetPlayer)(playerState);
// Clear old canyon
while (worldGroup.children.length > 0) {
const child = worldGroup.children[0];
child.traverse((o) => {
if (o.geometry)
o.geometry.dispose();
if (o.material) {
if (Array.isArray(o.material))
o.material.forEach((m) => { try {
m.dispose();
}
catch (_) { } });
else {
try {
o.material.dispose();
}
catch (_) { }
}
}
});
worldGroup.remove(child);
}
// Rebuild canyon
(0, canyon_1.rebuildCanyon)(worldGroup);
hud.hideGameOver();
}
function gameLoop(time) {
requestAnimationFrame(gameLoop);
const dt = Math.min((time - lastTime) / 1000, 0.05);
lastTime = time;
if (gameState !== 'playing')
return;
gameTime += dt;
const difficulty = 1 + gameTime * 0.02;
const effectiveSpeed = playerState.isBoosting
? constants_1.GAME.BASE_SPEED * constants_1.GAME.BOOST_SPEED_MULT * difficulty
: constants_1.GAME.BASE_SPEED * difficulty;
(0, player_1.updatePlayer)(playerState, { x: inputX, y: inputY }, dt);
playerState.speed = effectiveSpeed;
(0, player_1.applyPlayerMesh)(playerMesh, playerState);
// Trail particles
const pp = playerState.position;
trailSystem.emit(pp.x + (Math.random() - 0.5) * 0.3, pp.y + (Math.random() - 0.5) * 0.2, pp.z + 1.5, (Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2);
dustSystem.spawnNear(pp.x, pp.y, pp.z + 30, effectiveSpeed);
// Move world opposite to player
worldGroup.position.z = -pp.z;
// Collision
const coll = (0, canyon_1.checkCollision)(pp, 1.0);
if (coll.hit || playerState.energy <= 0) {
gameState = 'gameover';
audioSynth.playCrash();
hud.showGameOver(score, playerState.speed);
return;
}
// Near-miss scoring
const dist = coll.nearestDist;
const thr = constants_1.GAME.NEAR_MISS_THRESHOLD * difficulty;
if (dist < thr && dist > 1.5) {
const intensity = 1 - (dist / thr);
playerState.multiplier = Math.max(playerState.multiplier, 1 + intensity * 4);
playerState.nearMissTimer = 1;
score += Math.floor(intensity * 10 * playerState.multiplier);
camShakeX = (Math.random() - 0.5) * intensity * 0.3;
camShakeY = (Math.random() - 0.5) * intensity * 0.3;
if (audioSynth)
audioSynth.playNearMiss();
}
// Boost decay
if (playerState.boostMeter > 0 && !playerState.isBoosting) {
playerState.boostMeter = Math.max(0, playerState.boostMeter - dt * 0.15);
}
// Collect items
(0, canyon_1.collectFromChunks)(playerState, pp);
// Score ticks
score += effectiveSpeed * dt * 0.1 * playerState.multiplier;
// Update canyon
(0, canyon_1.updateCanyon)(pp.z);
// Particles
trailSystem.update(dt);
dustSystem.update(dt);
// Audio
audioSynth.updateEngine(effectiveSpeed, playerState.isBoosting);
// Camera
const baseFov = playerState.isBoosting ? 90 : 70;
camera.fov = baseFov + playerState.speed * 0.05;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix?.();
const ctX = pp.x * 0.4 + camShakeX;
const ctY = pp.y * 0.3 + camShakeY + 5;
camera.position.x += (ctX - camera.position.x) * 0.05;
camera.position.y += (ctY - camera.position.y) * 0.05;
camera.position.z = pp.z + 12;
camera.lookAt(pp.x * 0.6, pp.y * 0.5, pp.z - 30);
camShakeX *= 0.9;
camShakeY *= 0.9;
// Background color
renderer.setClearColor(constants_1.COLORS.deepBlue ?? 0x1a1a2e);
// Render
renderer.render(scene, camera);
// HUD
hud.update(score, playerState.multiplier, effectiveSpeed, playerState.energy, playerState.isBoosting, playerState.nearMissTimer > 0);
hud.boostingVal = playerState.isBoosting;
hud.boostVal = playerState.boostMeter;
}
function start() {
// Scene with golden hour atmosphere
scene = new THREE.Scene();
scene.background = new THREE.Color(constants_1.COLORS.deepOrange);
const sunGradTop = new THREE.Color(0xff6b35);
const sunGradBot = new THREE.Color(0x1a1a2e);
scene.fog = new THREE.FogExp2(sunGradTop, 0.006);
// Camera
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 12);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(constants_1.COLORS.deepBlue);
document.body.appendChild(renderer.domElement);
// Lights
const ambient = new THREE.AmbientLight(new THREE.Color(0x404060), 0.6);
scene.add(ambient);
const sun = new THREE.DirectionalLight(new THREE.Color(constants_1.COLORS.sunsetOr), 1.2);
sun.position.set(-50, 30, -100);
scene.add(sun);
const fill = new THREE.DirectionalLight(new THREE.Color(constants_1.COLORS.sunsetPurple), 0.4);
fill.position.set(50, 20, 50);
scene.add(fill);
const hemi = new THREE.HemisphereLight(new THREE.Color(constants_1.COLORS.warmWhite), new THREE.Color(constants_1.COLORS.deepBlue), 0.3);
scene.add(hemi);
// World
worldGroup = new THREE.Group();
scene.add(worldGroup);
// Player
const playerObj = (0, player_1.createPlayer)();
playerMesh = playerObj.mesh;
playerState = playerObj.state;
worldGroup.add(playerMesh);
playerMesh.position.copy(playerState.position);
// Particles
trailSystem = new particles_1.TrailSystem(scene);
dustSystem = new particles_1.DustSystem(scene);
// Audio
audioSynth = new audio_1.AudioSynth();
try {
audioSynth.init();
}
catch (_e) { }
window.__audioCtx = audioSynth;
// HUD
hud = new hud_1.HUD();
// Input
setupInput();
// Resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix?.();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Hook restart button
window.__onRestart = () => startGame();
// Hide loading overlay after a brief delay
setTimeout(() => {
const overlay = document.getElementById('loading-overlay');
if (overlay)
overlay.style.display = 'none';
}, 600);
// Start game loop
lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
// Auto-start on load
start();
};
// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HUD = exports.GAME = exports.COLORS = void 0;
exports.COLORS = {
deepOrange: 0xe8751a,
sunsetOr: 0xff6b35,
sunsetPurple: 0x9b5de5,
silver: 0xc0c0c0,
darkSilver: 0x888888,
warmWhite: 0xfff5e1,
glowOrange: 0xff8c42,
glowPurple: 0xb829dd,
deepBlue: 0x1a1a2e,
darkCanyon: 0x2d1b4e,
midCanyon: 0x4a2c7a,
lightCanyon: 0x6b3fa0,
energyGreen: 0x00ff88,
boostYellow: 0xffd700,
};
exports.GAME = {
CHUNK_LENGTH: 200,
CHUNKS_AHEAD: 6,
CHUNKS_BEHIND: 2,
BASE_SPEED: 80,
MAX_SPEED: 200,
PLAYER_Z: 0,
PLAYER_Y: 8,
PLAYER_X: 0,
CANYON_HALF_WIDTH: 60,
CANYON_CEILING: 40,
ENERGY_DECAY: 1.5,
ENERGY_RECHARGE: 25,
NEAR_MISS_THRESHOLD: 6,
NEAR_MISS_FAR_THRESHOLD: 15,
BOOST_DURATION: 3,
BOOST_SPEED_MULT: 2.2,
SENSITIVITY: 0.04,
FRICTION: 0.92,
RETURN_FORCE: 0.03,
WALL_PUSHER: 0.8,
};
exports.HUD = {
ENERGY_BAR_WIDTH: 200,
ENERGY_BAR_HEIGHT: 4,
MULTIPLIER_SCALE: 1.2,
};
};
// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayer = createPlayer;
exports.updatePlayer = updatePlayer;
exports.applyPlayerMesh = applyPlayerMesh;
exports.resetPlayer = resetPlayer;
const constants_1 = require("./constants");
function createPlayer() {
// Sleek low-poly craft: a delta-wing shape
const geo = new THREE.ConeGeometry(0.6, 2.4, 4);
geo.rotateX(Math.PI / 2);
const mat = new THREE.MeshPhongMaterial({
color: constants_1.COLORS.silver,
flatShading: true,
emissive: constants_1.COLORS.darkSilver,
emissiveIntensity: 0.3,
});
const mesh = new THREE.Mesh(geo, mat);
// Wing extensions
const wingGeo = new THREE.BoxGeometry(5, 0.08, 1.2);
const wingMat = new THREE.MeshPhongMaterial({
color: constants_1.COLORS.silver,
flatShading: true,
emissive: constants_1.COLORS.darkSilver,
emissiveIntensity: 0.15,
});
const wings = new THREE.Mesh(wingGeo, wingMat);
wings.position.set(0, -0.05, 0.4);
mesh.add(wings);
// Engine glow (point light)
const engineLight = new THREE.PointLight(constants_1.COLORS.glowOrange, 2, 15);
engineLight.position.set(0, 0, 1.5);
mesh.add(engineLight);
// Tail engine glow
const tailGlow = new THREE.SphereGeometry(0.15, 6, 4);
const tailMat = new THREE.MeshBasicMaterial({ color: constants_1.COLORS.glowOrange });
const tailMesh = new THREE.Mesh(tailGlow, tailMat);
tailMesh.position.set(0, 0, 1.3);
mesh.add(tailMesh);
const state = {
position: new THREE.Vector3(constants_1.GAME.PLAYER_X, constants_1.GAME.PLAYER_Y, constants_1.GAME.PLAYER_Z),
velocity: new THREE.Vector3(0, 0, 0),
euler: new THREE.Euler(0, 0, 0, 'YXZ'),
energy: 100,
speed: constants_1.GAME.BASE_SPEED,
multiplier: 1,
nearMissTimer: 0,
boostMeter: 0,
isBoosting: false,
boosting: false,
};
return { mesh, state };
}
function updatePlayer(state, input, dt) {
const targetVelX = input.x * 35;
const targetVelY = input.y * 25;
// Smooth acceleration toward target lateral velocities
state.velocity.x += (targetVelX - state.velocity.x) * constants_1.GAME.SENSITIVITY * 60 * dt;
state.velocity.y += (targetVelY - state.velocity.y) * constants_1.GAME.SENSITIVITY * 60 * dt;
// Energy decay forces forward movement for recharge
state.energy -= constants_1.GAME.ENERGY_DECAY * dt;
// Pitch controls speed: down = accelerate, up = slow down
const pitchInput = input.y;
state.speed += pitchInput * 60 * dt;
state.speed = Math.max(constants_1.GAME.BASE_SPEED * 0.5, Math.min(constants_1.GAME.MAX_SPEED * 1.5, state.speed));
if (state.isBoosting) {
state.speed = Math.min(state.speed + constants_1.GAME.MAX_SPEED * dt * 0.1, constants_1.GAME.MAX_SPEED * constants_1.GAME.BOOST_SPEED_MULT);
state.boostMeter -= constants_1.GAME.BOOST_DURATION * dt;
if (state.boostMeter <= 0) {
state.isBoosting = false;
state.boostMeter = 0;
}
}
else {
// Natural speed variation
state.speed += (constants_1.GAME.BASE_SPEED * 1.1 - state.speed) * 0.01 * dt * 60;
// Boost meter builds at high speeds and near misses
if (state.speed > constants_1.GAME.BASE_SPEED * 1.2) {
state.boostMeter = Math.min(state.boostMeter + dt * 0.3, 1);
}
if (state.nearMissTimer > 0) {
state.boostMeter = Math.min(state.boostMeter + dt * 0.5, 1);
}
}
// Apply velocity
state.position.x += state.velocity.x * dt;
state.position.y += state.velocity.y * dt;
state.position.z -= state.speed * dt;
// Clamp to canyon bounds
const halfW = constants_1.GAME.CANYON_HALF_WIDTH * 0.85;
state.position.x = Math.max(-halfW, Math.min(halfW, state.position.x));
const minY = 1;
const maxY = constants_1.GAME.CANYON_CEILING * 0.9;
state.position.y = Math.max(minY, Math.min(maxY, state.position.y));
// Banking/roll from lateral velocity
const targetRoll = -state.velocity.x * 0.025;
const targetPitch = pitchInput * 0.3;
state.euler.x += (targetPitch - state.euler.x) * 0.08 * 60 * dt;
state.euler.y = -state.velocity.x * 0.008; // subtle yaw on turns
state.euler.z += (targetRoll - state.euler.z) * 0.06 * 60 * dt;
// Near-miss timer
if (state.nearMissTimer > 0) {
state.nearMissTimer -= dt;
if (state.nearMissTimer <= 0) {
state.multiplier = Math.max(1, state.multiplier - dt * 0.5);
}
}
// Check boost activation
if (!state.isBoosting && state.boostMeter >= 1 && input.x === 0 && input.y === 0) {
// Auto-boost when full and no input
}
}
function applyPlayerMesh(mesh, state) {
mesh.position.copy(state.position);
mesh.rotation.copy(state.euler);
// Visual boost effect
if (state.isBoosting) {
mesh.scale.setScalar(1.05 + Math.sin(Date.now() * 0.01) * 0.02);
mesh.material.emissive.setHex(constants_1.COLORS.boostYellow);
mesh.material.emissiveIntensity = 0.6;
}
else {
mesh.scale.setScalar(1);
mesh.material.emissive.setHex(constants_1.COLORS.darkSilver);
mesh.material.emissiveIntensity = 0.3 + state.multiplier * 0.05;
}
}
function resetPlayer(state) {
state.position.set(constants_1.GAME.PLAYER_X, constants_1.GAME.PLAYER_Y, constants_1.GAME.PLAYER_Z);
state.velocity.set(0, 0, 0);
state.euler.set(0, 0, 0);
state.energy = 100;
state.speed = constants_1.GAME.BASE_SPEED;
state.multiplier = 1;
state.nearMissTimer = 0;
state.boostMeter = 0;
state.isBoosting = false;
}
};
// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};
// ── module: src/canyon.ts ──
__mods["src/canyon.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initCanyon = initCanyon;
exports.rebuildCanyon = rebuildCanyon;
exports.updateCanyon = updateCanyon;
exports.checkCollision = checkCollision;
exports.collectNearby = collectNearby;
exports.collectFromChunks = collectFromChunks;
const constants_1 = require("./constants");
const chunks = [];
let chunkIdCounter = 0;
function hash(x, z) {
const n = Math.sin(x * 127.1 + z * 311.7) * 43758.5453;
return n - Math.floor(n);
}
function initCanyon(parent) {
window.__worldGroup = parent;
chunks.length = 0;
chunkIdCounter = 0;
for (let i = -constants_1.GAME.CHUNKS_BEHIND; i < constants_1.GAME.CHUNKS_AHEAD; i++) {
spawnChunk(parent, i * constants_1.GAME.CHUNK_LENGTH);
}
addStarfield(parent);
}
let starfieldAdded = false;
function rebuildCanyon(parent) {
// Only keep stars
const stars = parent.children.find((c) => c.userData?.isStars);
while (parent.children.length > 0) {
const child = parent.children[0];
child.traverse((o) => {
if (o.geometry)
o.geometry.dispose();
if (o.material) {
if (Array.isArray(o.material))
o.material.forEach((m) => { try {
m.dispose();
}
catch (_) { } });
else {
try {
o.material.dispose();
}
catch (_) { }
}
}
});
parent.remove(child);
}
chunks.length = 0;
chunkIdCounter = 0;
if (stars)
parent.add(stars);
for (let i = -constants_1.GAME.CHUNKS_BEHIND; i < constants_1.GAME.CHUNKS_AHEAD; i++) {
spawnChunk(parent, i * constants_1.GAME.CHUNK_LENGTH);
}
if (!stars)
addStarfield(parent);
}
function addStarfield(parent) {
const verts = [];
for (let i = 0; i < 600; i++) {
verts.push((Math.random() - 0.5) * 500, (Math.random() - 0.5) * 500, -100 - Math.random() * 400);
}
const g = new THREE.BufferGeometry();
g.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(verts), 3));
const m = new THREE.PointsMaterial({
color: constants_1.COLORS.warmWhite, size: 0.5, transparent: true, opacity: 0.6,
depthWrite: false, sizeAttenuation: true,
});
const stars = new THREE.Points(g, m);
stars.renderOrder = 999;
stars.userData.isStars = true;
parent.add(stars);
}
function updateCanyon(playerZ) {
if (!chunks.length)
return;
const minZ = playerZ - constants_1.GAME.CHUNK_LENGTH * (constants_1.GAME.CHUNKS_BEHIND + 1);
const maxZ = playerZ + constants_1.GAME.CHUNK_LENGTH * (constants_1.GAME.CHUNKS_AHEAD + 1);
// Remove old chunks
while (chunks.length > 0 && chunks[0].z < minZ) {
const ch = chunks.shift();
ch.group.traverse((o) => {
if (o.geometry)
o.geometry.dispose();
if (o.material) {
if (Array.isArray(o.material))
o.material.forEach((m) => { try {
m.dispose();
}
catch (_) { } });
else {
try {
o.material.dispose();
}
catch (_) { }
}
}
});
if (ch.group.parent)
ch.group.parent.remove(ch.group);
}
const wg = window.__worldGroup;
if (!wg)
return;
// Fill at back
let lastZ = chunks[chunks.length - 1].z;
while (lastZ < maxZ) {
lastZ += constants_1.GAME.CHUNK_LENGTH;
spawnChunk(wg, lastZ);
}
// Fill gaps at front
while (chunks.length > 0 && chunks[0].z < minZ) {
const first = chunks.shift();
const before = first.z - constants_1.GAME.CHUNK_LENGTH;
spawnChunk(wg, before);
chunks.unshift({ group: first.group, obstacles: first.obstacles, collectibles: first.collectibles, z: before });
}
}
function spawnChunk(parent, worldZ) {
const id = chunkIdCounter++;
const grp = new THREE.Group();
const hw = constants_1.GAME.CANYON_HALF_WIDTH;
const ceilH = constants_1.GAME.CANYON_CEILING;
const segs = 14;
const verts = [], norms = [];
for (let side = -1; side <= 1; side += 2) {
for (let s = 0; s < segs; s++) {
const f = s / segs;
const sl = constants_1.GAME.CHUNK_LENGTH / segs;
const n1 = hash(side * f * 3, worldZ * 0.01);
const n2 = hash(side * f * 5, worldZ * 0.005);
const yB = (0.3 + n1 * 0.7) * ceilH;
const wOff = (n2 - 0.5) * 20;
const zi = worldZ - f * sl;
const zf = worldZ - (f + 1) * sl;
const xi = side * (hw * 0.7 + wOff);
const xo = side * (hw + wOff + 15);
verts.push(xi, yB, zi, xi, 0, zi, xo, yB * 0.5, zf);
verts.push(xi, yB, zi, xo, yB * 0.5, zf, xo, 0, zf);
const nx = side * -1;
for (let v = 0; v < 6; v++)
norms.push(nx * 0.9, 0.4, 0);
}
}
// Ceiling with stalactite-like drops
for (let s = 0; s < segs; s++) {
const f = s / segs;
const sl = constants_1.GAME.CHUNK_LENGTH / segs;
const ceO = (hash(f * 4, worldZ * 0.01) - 0.5) * 15;
const zy = ceilH + 5 + ceO;
const zi = worldZ - f * sl;
const zf = worldZ - (f + 1) * sl;
// Main ceiling plane
verts.push(-hw * 0.7 + ceO * 0.2, zy, zi, hw * 0.7 + ceO * 0.2, zy, zi, -hw * 0.7 + ceO * 0.1, zy * 0.85, zf);
verts.push(-hw * 0.7 + ceO * 0.2, zy, zi, hw * 0.7 + ceO * 0.2, zy * 0.85, zf, hw * 0.7 + ceO * 0.1, zy * 0.85, zf);
for (let v = 0; v < 6; v++)
norms.push(0, -1, 0);
// Stalactites
if (hash(s, worldZ * 0.03) > 0.6) {
const sx = (-0.5 + hash(s + 1, worldZ * 0.02)) * hw * 1.2;
const stLen = 5 + hash(s + 2, worldZ * 0.02) * 15;
verts.push(sx, zy, zi, sx - 1, zy, zf, sx, zy - stLen, zf);
verts.push(sx, zy, zi, sx, zy - stLen, zf, sx + 1, zy, zf);
for (let v = 0; v < 6; v++)
norms.push(0, 0, -1);
}
}
// Build wall geometry
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(verts), 3));
geo.setAttribute('normal', new THREE.Float32BufferAttribute(new Float32Array(norms), 3));
geo.computeBoundingSphere();
const wallMat = new THREE.MeshLambertMaterial({ color: constants_1.COLORS.darkCanyon, flatShading: true });
const walls = new THREE.Mesh(geo, wallMat);
grp.add(walls);
// Wall edge glow lines
const lVerts = [];
for (let side = -1; side <= 1; side += 2) {
for (let s = 0; s < segs; s++) {
const f = s / segs;
const sl = constants_1.GAME.CHUNK_LENGTH / segs;
const n1 = hash(side * f * 3, worldZ * 0.01);
const n2 = hash(side * f * 5, worldZ * 0.005);
const yB = (0.3 + n1 * 0.7) * ceilH;
const wO = (n2 - 0.5) * 20;
const xE = side * (hw * 0.7 + wO);
const z0 = worldZ - f * sl;
const z1 = worldZ - (f + 1) * sl;
lVerts.push(xE, yB, z0, xE, yB, z1);
}
}
const lGeo = new THREE.BufferGeometry();
lGeo.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(lVerts), 3));
const lMat = new THREE.LineBasicMaterial({ color: constants_1.COLORS.sunsetPurple, transparent: true, opacity: 0.3 });
grp.add(new THREE.LineSegments(lGeo, lMat));
// Obstacles
const obsList = [];
const numObs = 1 + Math.floor(hash(0, worldZ * 0.1) * 2.5);
for (let oi = 0; oi < numObs; oi++) {
const ox = (hash(oi + 1, worldZ * 0.08) - 0.5) * hw * 0.7;
const oy = 3 + hash(oi + 2, worldZ * 0.08) * ceilH * 0.4;
const oz = worldZ - hash(oi + 3, worldZ * 0.08) * constants_1.GAME.CHUNK_LENGTH * 0.8;
const oType = Math.floor(hash(oi + 4, worldZ * 0.05) * 3);
let ob;
if (oType === 0) {
const g = new THREE.BoxGeometry(2 + hash(oi, worldZ) * 2, 4 + hash(oi + 10, worldZ) * 6, 1.5);
const m = new THREE.MeshPhongMaterial({ color: constants_1.COLORS.midCanyon, emissive: constants_1.COLORS.glowPurple, emissiveIntensity: 0.2, flatShading: true });
ob = new THREE.Mesh(g, m);
}
else if (oType === 1) {
const g = new THREE.CylinderGeometry(0.5, 1.0, 5, 6);
const m = new THREE.MeshPhongMaterial({ color: constants_1.COLORS.lightCanyon, emissive: constants_1.COLORS.deepOrange, emissiveIntensity: 0.15, flatShading: true });
ob = new THREE.Mesh(g, m);
}
else {
const g = new THREE.OctahedronGeometry(1.2, 0);
const m = new THREE.MeshPhongMaterial({ color: constants_1.COLORS.sunsetOr, emissive: constants_1.COLORS.sunsetOr, emissiveIntensity: 0.25, flatShading: true });
ob = new THREE.Mesh(g, m);
}
ob.position.set(ox, oy, oz);
ob.userData.isObstacle = true;
ob.computeBoundingSphere();
grp.add(ob);
obsList.push(ob);
}
// Collectibles
const collList = [];
const numColl = 3 + Math.floor(hash(id % 50, worldZ * 0.2) * 3);
for (let ci = 0; ci < numColl; ci++) {
const cx = (hash(ci + 10, worldZ * 0.06) - 0.5) * hw * 0.6;
const cy = 2 + hash(ci + 11, worldZ * 0.06) * ceilH * 0.4;
const cz = worldZ - hash(ci + 12, worldZ * 0.06) * constants_1.GAME.CHUNK_LENGTH * 0.7;
const isBoost = hash(ci + 20, worldZ * 0.03) > 0.85;
const colColor = isBoost ? constants_1.COLORS.boostYellow : constants_1.COLORS.energyGreen;
const orbG = new THREE.SphereGeometry(0.5, 8, 6);
const orbM = new THREE.MeshBasicMaterial({ color: colColor, transparent: true, opacity: 0.9 });
const orb = new THREE.Mesh(orbG, orbM);
orb.position.set(cx, cy, cz);
orb.userData.isCollectible = true;
orb.userData.collected = false;
grp.add(orb);
const glowG = new THREE.SphereGeometry(0.9, 5, 4);
const glowM = new THREE.MeshBasicMaterial({ color: colColor, transparent: true, opacity: 0.15 });
orb.add(new THREE.Mesh(glowG, glowM));
collList.push({
mesh: orb, pos: new THREE.Vector3(cx, cy, cz),
radius: 0.8, type: isBoost ? 'boost' : 'energy',
bobPhase: hash(ci + 30, worldZ) * Math.PI * 2, collected: false,
});
}
parent.add(grp);
chunks.push({ group: grp, obstacles: obsList, collectibles: collList, z: worldZ });
}
function checkCollision(pPos, pRad) {
let nearestDist = Infinity;
let hit = false;
for (const ch of chunks) {
for (const ob of ch.obstacles) {
const dx = pPos.x - ob.position.x;
const dy = pPos.y - ob.position.y;
const dz = pPos.z - ob.position.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
const rad = ob.geometry?.boundingSphere?.radius || 3;
if (dist < pRad + rad)
hit = true;
if (dist < nearestDist)
nearestDist = dist;
}
}
const wallDist = hw - Math.abs(pPos.x);
if (wallDist < nearestDist)
nearestDist = wallDist;
if (wallDist < 1.5)
hit = true;
return { hit, nearestDist };
}
function collectNearby(state, pPos) {
for (const ch of chunks) {
for (const col of ch.collectibles) {
if (col.collected)
continue;
const dx = pPos.x - col.pos.x;
const dy = pPos.y - col.pos.y;
const dz = pPos.z - col.pos.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist < col.radius + 1.5) {
col.collected = true;
col.mesh.visible = false;
if (col.type === 'boost') {
state.isBoosting = true;
state.boostMeter = 1;
}
else {
state.energy = Math.min(100, state.energy + 25);
}
}
}
}
}
function collectFromChunks(state, pPos) {
collectNearby(state, pPos);
}
// Move variable outside so checkCollision can use it
const hw = constants_1.GAME.CANYON_HALF_WIDTH;
};
// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DustSystem = exports.TrailSystem = void 0;
const constants_1 = require("./constants");
const MAX_TRAIL = 80;
const MAX_DUST = 120;
class TrailSystem {
constructor(scene) {
this.pts = [];
this.geo = new THREE.BufferGeometry();
const posArr = new Float32Array(MAX_TRAIL * 3);
const colArr = new Float32Array(MAX_TRAIL * 3);
this.geo.setAttribute('position', new THREE.Float32BufferAttribute(posArr, 3));
this.geo.setAttribute('color', new THREE.Float32BufferAttribute(colArr, 3));
this.mat = new THREE.PointsMaterial({
size: 0.15, vertexColors: true, transparent: true, opacity: 0.7,
blending: THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true,
});
this.obj = new THREE.Points(this.geo, this.mat);
scene.add(this.obj);
}
emit(x, y, z, vx, vy) {
if (this.pts.length >= MAX_TRAIL)
return;
this.pts.push({
pos: new THREE.Vector3(x, y, z),
vel: new THREE.Vector3(vx, vy + Math.random() * 0.5, -Math.random() * 2),
life: 0.6 + Math.random() * 0.4, maxLife: 1,
size: 0.1 + Math.random() * 0.15,
color: Math.random() > 0.5 ? constants_1.COLORS.glowOrange : constants_1.COLORS.boostYellow,
});
}
update(dt) {
for (let i = this.pts.length - 1; i >= 0; i--) {
const p = this.pts[i];
p.life -= dt;
p.pos.x += p.vel.x * dt;
p.pos.y += p.vel.y * dt;
p.pos.z += p.vel.z * dt;
p.vel.x *= 0.95;
p.vel.y *= 0.95;
if (p.life <= 0)
this.pts.splice(i, 1);
}
const posA = this.geo.attributes.position.array;
const colA = this.geo.attributes.color.array;
const count = this.pts.length;
for (let i = 0; i < count; i++) {
const p = this.pts[i];
posA[i * 3] = p.pos.x;
posA[i * 3 + 1] = p.pos.y;
posA[i * 3 + 2] = p.pos.z;
const c = new THREE.Color(p.color);
const a = p.life / p.maxLife;
colA[i * 3] = c.r;
colA[i * 3 + 1] = c.g * a;
colA[i * 3 + 2] = c.b * a;
}
this.geo.attributes.position.needsUpdate = true;
this.geo.attributes.color.needsUpdate = true;
this.geo.setDrawRange(0, count);
}
dispose() {
this.geo.dispose();
this.mat.dispose();
}
}
exports.TrailSystem = TrailSystem;
class DustSystem {
constructor(scene) {
this.parts = [];
this.geo = new THREE.BufferGeometry();
this.geo.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(MAX_DUST * 3), 3));
this.mat = new THREE.PointsMaterial({
color: constants_1.COLORS.warmWhite, size: 0.08, transparent: true, opacity: 0.4,
depthWrite: false, sizeAttenuation: true,
});
this.obj = new THREE.Points(this.geo, this.mat);
scene.add(this.obj);
}
spawnNear(px, py, pz, speed) {
const rate = Math.floor(speed * 0.04);
for (let i = 0; i < rate; i++) {
if (this.parts.length >= MAX_DUST)
break;
const angle = Math.random() * Math.PI * 2;
const dist = 10 + Math.random() * 30;
this.parts.push({
pos: new THREE.Vector3(px + Math.cos(angle) * dist, py + (Math.random() - 0.5) * 20, pz + 20 + Math.random() * 40),
vel: new THREE.Vector3(-speed * 0.8 + (Math.random() - 0.5) * 5, (Math.random() - 0.5) * 2, 0),
life: 0.5 + Math.random() * 1.5, maxLife: 2, size: 0.05 + Math.random() * 0.1, color: constants_1.COLORS.warmWhite,
});
}
}
update(dt) {
for (let i = this.parts.length - 1; i >= 0; i--) {
const p = this.parts[i];
p.life -= dt;
p.pos.x += p.vel.x * dt;
p.pos.y += p.vel.y * dt;
p.pos.z += p.vel.z * dt;
if (p.life <= 0)
this.parts.splice(i, 1);
}
const arr = this.geo.attributes.position.array;
const n = Math.min(this.parts.length, MAX_DUST);
for (let i = 0; i < n; i++) {
arr[i * 3] = this.parts[i].pos.x;
arr[i * 3 + 1] = this.parts[i].pos.y;
arr[i * 3 + 2] = this.parts[i].pos.z;
}
this.geo.attributes.position.needsUpdate = true;
}
dispose() {
this.geo.dispose();
this.mat.dispose();
}
}
exports.DustSystem = DustSystem;
};
// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioSynth = void 0;
class AudioSynth {
constructor() {
this.ctx = null;
this.masterGain = null;
this.engineOsc = null;
this.engineGain = null;
}
init() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.masterGain = this.ctx.createGain();
this.masterGain.gain.value = 0.15;
this.masterGain.connect(this.ctx.destination);
// Engine hum - low frequency oscillator
this.engineOsc = this.ctx.createOscillator();
this.engineOsc.type = 'sawtooth';
this.engineOsc.frequency.value = 60;
this.engineGain = this.ctx.createGain();
this.engineGain.gain.value = 0;
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 200;
this.engineOsc.connect(filter);
filter.connect(this.engineGain);
this.engineGain.connect(this.masterGain);
this.engineOsc.start();
}
updateEngine(speed, boosting) {
if (!this.ctx || !this.engineOsc || !this.engineGain)
return;
const baseFreq = 50 + speed * 0.5;
this.engineOsc.frequency.setTargetAtTime(boosting ? baseFreq * 2 : baseFreq, this.ctx.currentTime, 0.1);
this.engineGain.gain.setTargetAtTime(boosting ? 0.08 : 0.03, this.ctx.currentTime, 0.1);
}
playNearMiss() {
if (!this.ctx || !this.masterGain)
return;
const osc = this.ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(800, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1200, this.ctx.currentTime + 0.1);
const gain = this.ctx.createGain();
gain.gain.setValueAtTime(0.12, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start();
osc.stop(this.ctx.currentTime + 0.3);
}
playCollect(type) {
if (!this.ctx || !this.masterGain)
return;
const osc = this.ctx.createOscillator();
osc.type = 'sine';
const t = this.ctx.currentTime;
if (type === 'boost') {
osc.frequency.setValueAtTime(400, t);
osc.frequency.exponentialRampToValueAtTime(1600, t + 0.2);
}
else {
osc.frequency.setValueAtTime(600, t);
osc.frequency.exponentialRampToValueAtTime(1000, t + 0.15);
}
const gain = this.ctx.createGain();
gain.gain.setValueAtTime(0.15, t);
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start(t);
osc.stop(t + 0.3);
}
playCrash() {
if (!this.ctx || !this.masterGain)
return;
const bufferSize = this.ctx.sampleRate * 0.5;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.1));
}
const source = this.ctx.createBufferSource();
source.buffer = buffer;
const gain = this.ctx.createGain();
gain.gain.value = 0.25;
source.connect(gain);
gain.connect(this.masterGain);
source.start();
}
resume() {
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
}
exports.AudioSynth = AudioSynth;
};
// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HUD = void 0;
class HUD {
constructor() {
this.boostingVal = false;
this.boostVal = 0;
this.maxSpeed = 0;
// Score display
this.scoreEl = document.createElement('div');
this.scoreEl.id = 'hud-score-el';
// Multiplier display
this.multiplierEl = document.createElement('div');
this.multiplierEl.id = 'hud-multi-el';
this.multiplierEl.textContent = 'x1';
// Speed display
this.speedEl = document.createElement('div');
this.speedEl.id = 'hud-speed-el';
this.speedEl.textContent = '0 km/h';
// Energy bar - outer wrapper (CSS-controlled) + inner fill
const energyBarOuter = document.createElement('div');
energyBarOuter.id = 'hud-energy-bar';
this.energyFillInner = document.createElement('div');
this.energyFillInner.id = 'energy-fill-inner';
energyBarOuter.appendChild(this.energyFillInner);
// Boost meter
const boostOuter = document.createElement('div');
boostOuter.id = 'hud-boost';
this.boostFillInner = document.createElement('div');
this.boostFillInner.id = 'boost-fill-inner';
boostOuter.appendChild(this.boostFillInner);
// Game over overlay
this.gameOverOverlay = document.createElement('div');
this.gameOverOverlay.id = 'hud-gameover-overlay';
// Restart button
const restartBtn = document.createElement('button');
restartBtn.id = 'restart-btn';
restartBtn.textContent = 'RESTART';
restartBtn.addEventListener('click', () => {
try {
if (window.__onRestart)
window.__onRestart();
}
catch (_) { }
});
this.gameOverOverlay.appendChild(restartBtn);
// Assemble
this.container = document.createElement('div');
this.container.id = 'hud';
this.container.appendChild(this.scoreEl);
this.container.appendChild(this.multiplierEl);
this.container.appendChild(this.speedEl);
this.container.appendChild(energyBarOuter);
this.container.appendChild(boostOuter);
this.container.appendChild(this.gameOverOverlay);
document.body.appendChild(this.container);
// Resume audio on interaction
const resumeAudio = () => {
try {
const ctx = window.__audioCtx;
if (ctx?.resume)
ctx.resume();
}
catch (_) { }
};
document.addEventListener('click', resumeAudio, { once: true });
document.addEventListener('keydown', resumeAudio, { once: true });
document.addEventListener('touchstart', resumeAudio, { once: true });
}
update(score, mult, speed, energy, boosting, nearMiss) {
this.maxSpeed = Math.max(this.maxSpeed, speed);
this.scoreEl.textContent = Math.floor(score).toLocaleString();
this.multiplierEl.textContent = `x${Math.floor(mult)}`;
if (nearMiss && mult > 1.5) {
this.multiplierEl.classList.add('pulse-active');
this.multiplierEl.style.color = '#ff8c42';
}
else {
this.multiplierEl.classList.remove('pulse-active');
this.multiplierEl.style.color = mult > 2 ? '#ff8c42' : '#aaa';
}
this.multiplierEl.style.fontSize = `${mult > 2 ? '26px' : '20px'}`;
this.speedEl.textContent = `${Math.floor(speed * 3.6)} km/h`;
const normEnergy = Math.max(0, Math.min(100, energy));
this.energyFillInner.style.width = `${normEnergy}%`;
if (normEnergy > 50) {
this.energyFillInner.style.background = '#00ff88';
this.energyFillInner.style.boxShadow = '0 0 6px #00ff88';
}
else if (normEnergy > 25) {
this.energyFillInner.style.background = '#ffaa00';
this.energyFillInner.style.boxShadow = '0 0 6px #ffaa00';
}
else {
this.energyFillInner.style.background = '#ff3344';
this.energyFillInner.style.boxShadow = '0 0 6px #ff3344';
}
const boostWidth = boosting ? 100 : this.boostVal * 100;
this.boostFillInner.style.width = `${Math.min(100, boostWidth)}%`;
this.boostFillInner.style.background = boosting ? '#ffd700' : '#ff8c42';
}
showGameOver(score, maxSpd) {
// Clear and rebuild game over content
while (this.gameOverOverlay.firstChild) {
if (this.gameOverOverlay.firstChild.id === 'restart-btn')
break;
this.gameOverOverlay.removeChild(this.gameOverOverlay.firstChild);
}
// Insert before restart button
const infoHTML = `
<div class="go-title">GAME OVER</div>
<div class="go-label">SCORE</div>
<div class="go-value">${Math.floor(score)}</div>
<div class="go-subtitle">MAX SPEED</div>
<div class="go-speed-val">${Math.floor(maxSpd * 3.6)} km/h</div>
`;
const temp = document.createElement('div');
temp.innerHTML = infoHTML;
const restartBtn = this.gameOverOverlay.querySelector('#restart-btn');
while (temp.firstChild) {
this.gameOverOverlay.insertBefore(temp.firstChild, restartBtn);
}
this.gameOverOverlay.style.display = 'flex';
}
hideGameOver() {
this.gameOverOverlay.style.display = 'none';
this.maxSpeed = 0;
}
destroy() {
if (this.container.parentNode)
this.container.parentNode.removeChild(this.container);
}
}
exports.HUD = HUD;
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.7-flash","provider":"openrouter","persona":"typescript","sessionId":"fb51cda9-1520-4f84-a084-b33adef085c0","tokensIn":11025690,"tokensOut":93225,"tokensTotal":11118915,"cost":0.43860496599999993,"turns":125,"toolCalls":124,"failedToolCalls":10,"timestamp":"2026-08-02T03:41:01.249Z"} -->