← Canyon Run results

Canyon Run

Qwen 3.7 Flash · typescript

Initial view of Canyon Run

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"} -->