← Canyon Run results

Canyon Run

thinkingcap qwen3.6 27b · 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 67,129 bytes · SHA-256 668f34af301c
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canyon Racer — High-Speed Procedural Racing</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 { width: 100%; height: 100%; overflow: hidden; background: #0a0215; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; }
#app { position: relative; width: 100%; height: 100%; }
canvas { display: block; }
/* ── css: hud ── */
#hud {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;
  z-index: 10;
}

/* Speed bar - left side */
.speed-container {
  position: absolute;
  left: 24px;
  top: 50%;
  transform: translateY(-50%);
  width: 6px;
  height: 200px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 3px;
  overflow: hidden;
}

.speed-bar {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 50%;
  background: linear-gradient(to top, #ff6b35, #ffaa00);
  border-radius: 3px;
  transition: height 0.1s ease-out;
}

.speed-bar.boost {
  background: linear-gradient(to top, #ff4500, #ffdd00);
  box-shadow: 0 0 12px rgba(255, 69, 0, 0.8);
}

/* Multiplier */
#multiplier {
  position: absolute;
  top: 30px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 48px;
  font-weight: 900;
  color: #ffaa00;
  text-shadow: 0 0 20px rgba(255, 170, 0, 0.6), 0 0 40px rgba(255, 107, 53, 0.3);
  letter-spacing: 4px;
  transition: transform 0.1s ease-out;
}

#multiplier.pulse {
  animation: multiplierPulse 0.3s ease-out;
}

@keyframes multiplierPulse {
  0% { transform: translateX(-50%) scale(1); }
  50% { transform: translateX(-50%) scale(1.4); color: #ffdd00; }
  100% { transform: translateX(-50%) scale(1); }
}

/* Score */
#score {
  position: absolute;
  top: 85px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 20px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
  letter-spacing: 3px;
}

/* Energy bar - bottom center */
.energy-container {
  position: absolute;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  width: 200px;
  height: 8px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 4px;
  overflow: hidden;
}

.energy-bar {
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, #ff6b35, #ffaa00);
  border-radius: 4px;
  transition: width 0.2s ease-out;
}

.energy-bar.critical {
  background: linear-gradient(90deg, #ff0040, #ff4500);
  animation: criticalPulse 0.5s infinite alternate;
}

@keyframes criticalPulse {
  from { opacity: 1; }
  to { opacity: 0.5; }
}

/* Turbo bar - below energy */
.turbo-container {
  position: absolute;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  width: 120px;
  height: 4px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 2px;
  overflow: hidden;
}

.turbo-bar {
  width: 0%;
  height: 100%;
  background: linear-gradient(90deg, #7b3fa0, #c084fc);
  border-radius: 2px;
  transition: width 0.15s ease-out;
}

.turbo-bar.active {
  background: linear-gradient(90deg, #ff6b35, #ffdd00);
  box-shadow: 0 0 8px rgba(255, 170, 0, 0.6);
}

/* Game Over Screen */
#game-over {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(10, 2, 21, 0.85);
  pointer-events: auto;
}

.go-title {
  font-size: 64px;
  font-weight: 900;
  color: #ff4500;
  text-shadow: 0 0 30px rgba(255, 69, 0, 0.8);
  letter-spacing: 12px;
  margin-bottom: 30px;
}

.go-score {
  font-size: 28px;
  color: #ffaa00;
  letter-spacing: 4px;
  margin-bottom: 10px;
}

.go-speed {
  font-size: 18px;
  color: rgba(255, 255, 255, 0.6);
  letter-spacing: 3px;
  margin-bottom: 40px;
}

.restart-btn {
  padding: 14px 48px;
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  background: linear-gradient(135deg, #ff6b35, #7b3fa0);
  border: none;
  border-radius: 6px;
  cursor: pointer;
  letter-spacing: 4px;
  text-transform: uppercase;
  transition: transform 0.2s, box-shadow 0.2s;
  pointer-events: auto;
}

.restart-btn:hover {
  transform: scale(1.05);
  box-shadow: 0 0 20px rgba(255, 107, 53, 0.6);
}

/* Menu overlay */
#menu-overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(10, 2, 21, 0.9);
  z-index: 20;
}

#menu-overlay h1 {
  font-size: 56px;
  font-weight: 900;
  color: #ffaa00;
  text-shadow: 0 0 30px rgba(255, 170, 0, 0.6);
  letter-spacing: 8px;
  margin-bottom: 12px;
}

#menu-overlay .subtitle {
  font-size: 16px;
  color: rgba(255, 255, 255, 0.5);
  letter-spacing: 6px;
  text-transform: uppercase;
  margin-bottom: 40px;
}

#menu-overlay .controls {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.35);
  letter-spacing: 2px;
  line-height: 2;
  text-align: center;
}

#menu-overlay .start-hint {
  margin-top: 40px;
  font-size: 18px;
  color: #ff6b35;
  letter-spacing: 4px;
  animation: blink 1.5s infinite alternate;
}

@keyframes blink {
  from { opacity: 0.4; }
  to { opacity: 1; }
}
/* ── css: flash ── */
#flash-overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;
  z-index: 5;
}
/* ── css: vignette ── */
/* Vignette overlay for cinematic feel */
#app::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;
  z-index: 4;
  background: radial-gradient(ellipse at center, transparent 50%, rgba(10, 2, 21, 0.6) 100%);
}
/* ── css: turbo-text ── */
#turbo-text {
  position: absolute;
  bottom: 52px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 14px;
  font-weight: 900;
  color: #ff6b35;
  letter-spacing: 8px;
  opacity: 0;
  transition: opacity 0.2s ease-out;
  text-shadow: 0 0 10px rgba(255, 107, 53, 0.6);
}
</style>
</head>
<body>
<div id="app"><div id="menu-overlay"><h1>CANYON RACER</h1><div class="subtitle">High-Speed Procedural Racing</div><div class="controls">WASD / Arrow Keys — Steer<br>SPACE — Boost (when turbo charged)</div><div class="start-hint">PRESS SPACE TO START</div></div></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./game":"src/game.ts"},"src/player.ts":{"./config":"src/config.ts"},"src/world.ts":{"./config":"src/config.ts"},"src/collision.ts":{"./world":"src/world.ts","./player":"src/player.ts","./config":"src/config.ts"},"src/particles.ts":{"./config":"src/config.ts"},"src/hud.ts":{"./player":"src/player.ts","./config":"src/config.ts"},"src/game.ts":{"./player":"src/player.ts","./world":"src/world.ts","./collision":"src/collision.ts","./particles":"src/particles.ts","./hud":"src/hud.ts","./audio":"src/audio.ts","./config":"src/config.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 });
const game_1 = require("./game");
let game = null;
function init() {
    game = new game_1.Game();
    // Restart handler
    window.addEventListener('restart', () => {
        if (game)
            game.restart();
    });
    // Game loop
    function loop() {
        requestAnimationFrame(loop);
        if (game) {
            game.update();
            game.render();
        }
    }
    loop();
}
init();
};

// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};

// ── module: src/config.ts ──
__mods["src/config.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONFIG = void 0;
// Game configuration constants
exports.CONFIG = {
    // Player physics
    maxSpeed: 2.5,
    boostMaxSpeed: 4.5,
    acceleration: 0.03,
    deceleration: 0.015,
    turnSpeed: 0.04,
    bankAngle: 0.6,
    pitchAccelFactor: 0.02,
    // Energy system
    energyDecayRate: 0.8,
    energyMax: 100,
    orbRechargeAmount: 25,
    // Turbo system
    turboBuildRate: 0.3,
    turboDrainRate: 1.5,
    turboBoostMultiplier: 1.8,
    turboMax: 100,
    // Near-miss scoring
    nearMissDistance: 4.0,
    nearMissMultiplierGain: 2,
    maxMultiplier: 10,
    multiplierDecayRate: 0.5,
    // Chunk system
    chunkLength: 80,
    renderDistance: 6,
    canyonWidthBase: 30,
    canyonWidthMin: 12,
    // Difficulty scaling
    difficultySpeedIncrease: 0.0001,
    difficultyObstacleDensity: 0.00005,
    // Camera
    cameraDistance: 8,
    cameraHeight: 3,
    cameraLerpFactor: 0.08,
    boostFov: 75,
    normalFov: 60,
    // Visuals
    bloomIntensity: 1.2,
    shakeAmountNearMiss: 0.3,
    shakeAmountBoost: 0.15,
    // Colors (cyber-sunset palette)
    colors: {
        skyTop: 0x1a0533,
        skyBottom: 0xff6b35,
        canyonWall: 0x4a2c6e,
        canyonAccent: 0x7b3fa0,
        monolith: 0x8b5cf6,
        playerBody: 0xc0c0c0,
        playerGlow: 0xff6b35,
        orbColor: 0xffaa00,
        ground: 0x2d1b4e,
        fog: 0x1a0533,
    },
};
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioSystem = void 0;
// Procedural sound synthesis using Web Audio API
class AudioSystem {
    constructor() {
        this.ctx = null;
        this.engineOsc = null;
        this.engineGain = null;
        this.masterGain = null;
        this.initialized = false;
    }
    init() {
        if (this.initialized)
            return;
        try {
            this.ctx = new AudioContext();
            this.masterGain = this.ctx.createGain();
            this.masterGain.gain.value = 0.3;
            this.masterGain.connect(this.ctx.destination);
            // Engine hum - low 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.15;
            // Low-pass filter for engine rumble
            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();
            this.initialized = true;
        }
        catch (e) {
            console.warn('Audio init failed:', e);
        }
    }
    setEnginePitch(speedRatio) {
        if (!this.engineOsc || !this.engineGain)
            return;
        const freq = 50 + speedRatio * 120;
        this.engineOsc.frequency.setTargetAtTime(freq, this.ctx.currentTime, 0.1);
        this.engineGain.gain.setTargetAtTime(0.1 + speedRatio * 0.15, this.ctx.currentTime, 0.1);
    }
    playCollect() {
        if (!this.ctx || !this.masterGain)
            return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(800, this.ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(1200, this.ctx.currentTime + 0.15);
        gain.gain.setValueAtTime(0.3, 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);
        // Second harmonic ping
        const osc2 = this.ctx.createOscillator();
        const gain2 = this.ctx.createGain();
        osc2.type = 'triangle';
        osc2.frequency.setValueAtTime(1600, this.ctx.currentTime + 0.05);
        osc2.frequency.exponentialRampToValueAtTime(2400, this.ctx.currentTime + 0.2);
        gain2.gain.setValueAtTime(0.15, this.ctx.currentTime + 0.05);
        gain2.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.35);
        osc2.connect(gain2);
        gain2.connect(this.masterGain);
        osc2.start(this.ctx.currentTime + 0.05);
        osc2.stop(this.ctx.currentTime + 0.35);
    }
    playNearMiss() {
        if (!this.ctx || !this.masterGain)
            return;
        // Whoosh sound - filtered noise burst
        const bufferSize = this.ctx.sampleRate * 0.4;
        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) * 0.5;
        }
        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'bandpass';
        filter.frequency.setValueAtTime(600, this.ctx.currentTime);
        filter.frequency.exponentialRampToValueAtTime(2000, this.ctx.currentTime + 0.15);
        filter.Q.value = 2;
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.4, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.35);
        noise.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        noise.start();
    }
    playBoost() {
        if (!this.ctx || !this.masterGain)
            return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(100, this.ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(400, this.ctx.currentTime + 0.3);
        gain.gain.setValueAtTime(0.25, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.5);
        osc.connect(gain);
        gain.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.5);
    }
    playCrash() {
        if (!this.ctx || !this.masterGain)
            return;
        // Explosion-like crash sound
        const bufferSize = this.ctx.sampleRate * 0.8;
        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 / (this.ctx.sampleRate * 0.3));
        }
        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'lowpass';
        filter.frequency.setValueAtTime(2000, this.ctx.currentTime);
        filter.frequency.exponentialRampToValueAtTime(100, this.ctx.currentTime + 0.6);
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.8, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.7);
        noise.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        noise.start();
        // Low boom
        const osc = this.ctx.createOscillator();
        const g2 = this.ctx.createGain();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(80, this.ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(30, this.ctx.currentTime + 0.5);
        g2.gain.setValueAtTime(0.6, this.ctx.currentTime);
        g2.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.5);
        osc.connect(g2);
        g2.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.6);
    }
    stopEngine() {
        if (this.engineOsc) {
            this.engineOsc.stop();
            this.engineOsc = null;
        }
    }
    resume() {
        if (this.ctx && this.ctx.state === 'suspended') {
            this.ctx.resume();
        }
    }
}
exports.AudioSystem = AudioSystem;
};

// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlayerCraft = void 0;
const config_1 = require("./config");
class PlayerCraft {
    constructor() {
        this.position = new THREE.Vector3(0, 2, 0);
        this.velocity = new THREE.Vector3(0, 0, 0);
        this.speed = 0.5;
        this.bankAngle = 0;
        this.pitchAngle = 0;
        // Energy & turbo
        this.energy = config_1.CONFIG.energyMax;
        this.turbo = 0;
        this.isBoosting = false;
        // Near-miss scoring
        this.multiplier = 1;
        this.nearMissTimer = 0;
        // Trail points for visual effect
        this.trailPositions = [];
        this.mesh = new THREE.Group();
        this.buildCraft();
    }
    buildCraft() {
        // Main body - sleek elongated wedge
        const bodyGeo = new THREE.ConeGeometry(0.35, 2.2, 6);
        bodyGeo.rotateX(Math.PI / 2);
        const bodyMat = new THREE.MeshPhongMaterial({
            color: config_1.CONFIG.colors.playerBody,
            flatShading: true,
            shininess: 100,
        });
        const body = new THREE.Mesh(bodyGeo, bodyMat);
        this.mesh.add(body);
        // Cockpit accent - smaller cone on top
        const cockpitGeo = new THREE.ConeGeometry(0.15, 0.8, 6);
        cockpitGeo.rotateX(Math.PI / 2);
        const cockpitMat = new THREE.MeshPhongMaterial({
            color: config_1.CONFIG.colors.playerGlow,
            flatShading: true,
            emissive: config_1.CONFIG.colors.playerGlow,
            emissiveIntensity: 0.3,
        });
        const cockpit = new THREE.Mesh(cockpitGeo, cockpitMat);
        cockpit.position.y = 0.2;
        this.mesh.add(cockpit);
        // Wings - swept back delta wings
        const wingShape = new THREE.CylinderGeometry(0.03, 1.4, 0.25, 4);
        wingShape.rotateZ(Math.PI / 4);
        const wingMat = new THREE.MeshPhongMaterial({
            color: config_1.CONFIG.colors.playerBody,
            flatShading: true,
            shininess: 60,
        });
        const leftWing = new THREE.Mesh(wingShape, wingMat);
        leftWing.position.set(-0.7, -0.15, 0.2);
        this.mesh.add(leftWing);
        const rightWing = new THREE.Mesh(wingShape.clone(), wingMat);
        rightWing.position.set(0.7, -0.15, 0.2);
        this.mesh.add(rightWing);
        // Tail fins
        const tailGeo = new THREE.ConeGeometry(0.3, 0.6, 4);
        tailGeo.rotateX(Math.PI / 2);
        const tailMat = new THREE.MeshPhongMaterial({ color: config_1.CONFIG.colors.playerBody, flatShading: true });
        const leftTail = new THREE.Mesh(tailGeo, tailMat);
        leftTail.position.set(-0.35, -0.1, 0.8);
        this.mesh.add(leftTail);
        const rightTail = new THREE.Mesh(tailGeo.clone(), tailMat);
        rightTail.position.set(0.35, -0.1, 0.8);
        this.mesh.add(rightTail);
        // Engine glow sphere (index 6)
        const engineGeo = new THREE.SphereGeometry(0.2, 8, 8);
        const engineMat = new THREE.MeshBasicMaterial({
            color: config_1.CONFIG.colors.playerGlow,
            transparent: true,
            opacity: 0.9,
        });
        const engine = new THREE.Mesh(engineGeo, engineMat);
        engine.position.z = 1.1;
        this.mesh.add(engine);
        // Engine light
        const engineLight = new THREE.PointLight(config_1.CONFIG.colors.playerGlow, 2, 8);
        engineLight.position.z = 1.3;
        this.mesh.add(engineLight);
    }
    update(dt, input) {
        // Speed management
        const maxSpd = this.isBoosting ? config_1.CONFIG.boostMaxSpeed : config_1.CONFIG.maxSpeed;
        if (input.up) {
            this.speed += config_1.CONFIG.acceleration * dt * 60;
        }
        else if (input.down) {
            this.speed -= config_1.CONFIG.deceleration * dt * 60;
        }
        else {
            // Natural cruise - slight acceleration toward max speed
            if (this.speed < maxSpd * 0.7) {
                this.speed += config_1.CONFIG.acceleration * 0.3 * dt * 60;
            }
        }
        this.speed = Math.max(0.2, Math.min(this.speed, maxSpd));
        // Pitch angle based on input (affects vertical movement and acceleration feel)
        const targetPitch = input.up ? -0.3 : input.down ? 0.3 : 0;
        this.pitchAngle += (targetPitch - this.pitchAngle) * 0.15;
        // Lateral movement with momentum
        let turnInput = 0;
        if (input.left)
            turnInput -= 1;
        if (input.right)
            turnInput += 1;
        const lateralAccel = config_1.CONFIG.turnSpeed * this.speed * dt * 60;
        this.velocity.x += turnInput * lateralAccel;
        this.velocity.x *= 0.92; // damping
        // Vertical movement
        const verticalAccel = config_1.CONFIG.turnSpeed * 0.7 * this.speed * dt * 60;
        if (input.up)
            this.velocity.y += verticalAccel;
        if (input.down)
            this.velocity.y -= verticalAccel;
        this.velocity.y *= 0.92;
        // Apply velocity to position
        this.position.x += this.velocity.x;
        this.position.y += this.velocity.y;
        // Clamp position within reasonable bounds
        const canyonHalf = config_1.CONFIG.canyonWidthBase * 0.45;
        this.position.x = Math.max(-canyonHalf, Math.min(canyonHalf, this.position.x));
        this.position.y = Math.max(0.5, Math.min(12, this.position.y));
        // Banking animation (roll during turns)
        const targetBank = -turnInput * config_1.CONFIG.bankAngle;
        this.bankAngle += (targetBank - this.bankAngle) * 0.12;
        // Apply to mesh
        this.mesh.position.copy(this.position);
        this.mesh.rotation.z = this.bankAngle;
        this.mesh.rotation.x = this.pitchAngle;
        // Engine glow intensity based on speed
        const engineMesh = this.mesh.children[3];
        if (engineMesh) {
            const scale = 0.8 + this.speed / config_1.CONFIG.maxSpeed * 0.6;
            engineMesh.scale.set(scale, scale, scale);
        }
        // Energy decay
        this.energy -= config_1.CONFIG.energyDecayRate * dt;
        this.energy = Math.max(0, this.energy);
        // Turbo management
        if (this.isBoosting) {
            this.turbo -= config_1.CONFIG.turboDrainRate * dt * 60;
            if (this.turbo <= 0) {
                this.turbo = 0;
                this.isBoosting = false;
            }
        }
        else {
            // Build turbo at high speed on straightaways
            if (Math.abs(turnInput) < 0.5 && this.speed > config_1.CONFIG.maxSpeed * 0.8) {
                this.turbo += config_1.CONFIG.turboBuildRate * dt * 60;
            }
        }
        this.turbo = Math.min(config_1.CONFIG.turboMax, this.turbo);
        // Near-miss multiplier decay
        if (this.nearMissTimer > 0) {
            this.nearMissTimer -= dt;
            if (this.nearMissTimer <= 0) {
                this.multiplier = Math.max(1, this.multiplier - config_1.CONFIG.multiplierDecayRate * dt);
            }
        }
        // Trail positions for particle effect
        this.trailPositions.push(this.position.clone());
        if (this.trailPositions.length > 30) {
            this.trailPositions.shift();
        }
    }
    triggerNearMiss() {
        this.multiplier = Math.min(config_1.CONFIG.maxMultiplier, this.multiplier + config_1.CONFIG.nearMissMultiplierGain);
        this.nearMissTimer = 2.0;
    }
    recharge(amount) {
        this.energy = Math.min(config_1.CONFIG.energyMax, this.energy + amount);
    }
    activateBoost() {
        if (this.turbo > 20 && !this.isBoosting) {
            this.isBoosting = true;
            return true;
        }
        return false;
    }
    getSpeedRatio() {
        const maxSpd = this.isBoosting ? config_1.CONFIG.boostMaxSpeed : config_1.CONFIG.maxSpeed;
        return this.speed / maxSpd;
    }
}
exports.PlayerCraft = PlayerCraft;
};

// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorldGenerator = void 0;
const config_1 = require("./config");
class WorldGenerator {
    constructor(scene) {
        this.scene = scene;
        this.chunks = [];
        this.nextChunkZ = 0;
        this.seed = Math.random() * 10000;
        this.difficultyScale = 1;
        this.time = 0;
        this._lastPlayerZ = 0;
    }
    setDifficulty(scale) {
        this.difficultyScale = scale;
    }
    update(playerZ) {
        // Generate new chunks ahead
        while (this.nextChunkZ < playerZ + config_1.CONFIG.renderDistance * config_1.CONFIG.chunkLength) {
            this.generateChunk(this.nextChunkZ);
            this.nextChunkZ += config_1.CONFIG.chunkLength;
        }
        // Remove old chunks behind
        const removeThreshold = playerZ - 2 * config_1.CONFIG.chunkLength;
        while (this.chunks.length > 0 && this.chunks[0].z < removeThreshold) {
            const chunk = this.chunks.shift();
            this.scene.remove(chunk.group);
            // Dispose geometries and materials
            chunk.group.traverse((obj) => {
                if (obj.geometry)
                    obj.geometry.dispose();
                if (obj.material)
                    obj.material.dispose();
            });
        }
    }
    getObstaclesInRange(playerZ, range) {
        const obstacles = [];
        for (const chunk of this.chunks) {
            if (chunk.z > playerZ - range && chunk.z < playerZ + range) {
                obstacles.push(...chunk.obstacles);
            }
        }
        return obstacles;
    }
    generateChunk(z) {
        const group = new THREE.Group();
        const obstacles = [];
        // Pseudo-random based on chunk position
        const rng = this.seededRng(Math.floor(z / config_1.CONFIG.chunkLength));
        // Canyon width varies with difficulty
        const widthVariation = (rng() - 0.5) * config_1.CONFIG.canyonWidthBase * 0.4;
        const canyonWidth = Math.max(config_1.CONFIG.canyonWidthMin, config_1.CONFIG.canyonWidthBase + widthVariation);
        // Generate canyon walls for this chunk
        this.generateCanyonWalls(group, obstacles, z, canyonWidth, rng);
        // Generate floating monoliths
        const monolithCount = Math.floor(1 + rng() * 2 * this.difficultyScale);
        for (let i = 0; i < monolithCount; i++) {
            this.generateMonolith(group, obstacles, z, canyonWidth, rng);
        }
        // Generate tight squeezes at higher difficulty
        if (this.difficultyScale > 1.3 && rng() > 0.5) {
            this.generateSqueeze(group, obstacles, z, canyonWidth, rng);
        }
        // Ground plane for the chunk
        const groundGeo = new THREE.PlaneGeometry(canyonWidth * 2, config_1.CONFIG.chunkLength);
        const groundMat = new THREE.MeshLambertMaterial({ color: config_1.CONFIG.colors.ground, flatShading: true });
        const ground = new THREE.Mesh(groundGeo, groundMat);
        ground.rotation.x = -Math.PI / 2;
        ground.position.set(0, -0.5, z + config_1.CONFIG.chunkLength / 2);
        group.add(ground);
        // Energy orbs scattered through the chunk
        const orbCount = Math.floor(2 + rng() * 3);
        for (let i = 0; i < orbCount; i++) {
            this.generateOrb(group, z, canyonWidth, rng);
        }
        group.position.z = 0;
        this.scene.add(group);
        this.chunks.push({ group, z, obstacles });
    }
    generateCanyonWalls(group, obstacles, chunkZ, width, rng) {
        const wallSegments = 5;
        const segLength = config_1.CONFIG.chunkLength / wallSegments;
        for (let side = -1; side <= 1; side += 2) {
            let prevHeight = 6 + rng() * 8;
            let prevWidthOffset = width * 0.45;
            for (let i = 0; i < wallSegments; i++) {
                const segZ = chunkZ + i * segLength + segLength / 2;
                const height = 6 + rng() * 12;
                const wOffset = Math.max(width * 0.3, prevWidthOffset + (rng() - 0.5) * width * 0.25);
                // Main wall segment with jagged top
                const wallGeo = new THREE.BoxGeometry(6 + rng() * 4, height, segLength * 0.85);
                const shade = rng();
                let color;
                if (shade > 0.7) {
                    color = config_1.CONFIG.colors.canyonAccent;
                }
                else if (shade > 0.3) {
                    color = config_1.CONFIG.colors.canyonWall;
                }
                else {
                    color = 0x2d1b4e; // Darker variant
                }
                const wallMat = new THREE.MeshLambertMaterial({ color, flatShading: true });
                const wall = new THREE.Mesh(wallGeo, wallMat);
                const xPos = side * wOffset;
                const yPos = height / 2 - 1;
                wall.position.set(xPos, yPos, segZ);
                group.add(wall);
                // Add rocky protrusions on top of walls
                if (rng() > 0.4) {
                    const spikeGeo = new THREE.ConeGeometry(1 + rng() * 2, 3 + rng() * 3, 5);
                    const spikeMat = new THREE.MeshLambertMaterial({ color: config_1.CONFIG.colors.canyonAccent, flatShading: true });
                    const spike = new THREE.Mesh(spikeGeo, spikeMat);
                    spike.position.set(xPos + (rng() - 0.5) * 3, height + 1, segZ);
                    group.add(spike);
                }
                obstacles.push({
                    mesh: wall,
                    center: new THREE.Vector3(xPos, yPos, segZ),
                    radius: Math.max(4, wOffset * 0.25) + 1,
                    type: 'wall',
                });
                prevHeight = height;
                prevWidthOffset = wOffset;
            }
        }
    }
    generateMonolith(group, obstacles, chunkZ, width, rng) {
        const zPos = chunkZ + rng() * config_1.CONFIG.chunkLength;
        const xPos = (rng() - 0.5) * width * 0.6;
        const yPos = 3 + rng() * 4;
        const height = 2 + rng() * 4;
        const radius = 0.8 + rng() * 1.2;
        const monoGeo = new THREE.CylinderGeometry(radius * 0.5, radius, height, 6);
        const monoMat = new THREE.MeshPhongMaterial({
            color: config_1.CONFIG.colors.monolith,
            flatShading: true,
            emissive: config_1.CONFIG.colors.monolith,
            emissiveIntensity: 0.15,
        });
        const monolith = new THREE.Mesh(monoGeo, monoMat);
        monolith.position.set(xPos, yPos, zPos);
        // Slight random rotation for visual interest
        monolith.rotation.y = rng() * Math.PI;
        group.add(monolith);
        obstacles.push({
            mesh: monolith,
            center: new THREE.Vector3(xPos, yPos, zPos),
            radius: radius + 0.5,
            type: 'monolith',
        });
    }
    generateSqueeze(group, obstacles, chunkZ, width, rng) {
        // Create a narrow gap - two walls close together with a passage through
        const zPos = chunkZ + config_1.CONFIG.chunkLength * 0.5;
        const gapX = (rng() - 0.5) * width * 0.3;
        const gapWidth = 4;
        // Left wall of squeeze
        const leftGeo = new THREE.BoxGeometry(2, 10, 6);
        const squeezeMat = new THREE.MeshLambertMaterial({ color: config_1.CONFIG.colors.canyonAccent, flatShading: true });
        const leftWall = new THREE.Mesh(leftGeo, squeezeMat);
        leftWall.position.set(gapX - gapWidth / 2, 4, zPos);
        group.add(leftWall);
        // Right wall of squeeze
        const rightWall = new THREE.Mesh(leftGeo.clone(), squeezeMat);
        rightWall.position.set(gapX + gapWidth / 2, 4, zPos);
        group.add(rightWall);
        obstacles.push({
            mesh: leftWall,
            center: new THREE.Vector3(gapX - gapWidth / 2, 4, zPos),
            radius: 2.5,
            type: 'squeeze',
        });
        obstacles.push({
            mesh: rightWall,
            center: new THREE.Vector3(gapX + gapWidth / 2, 4, zPos),
            radius: 2.5,
            type: 'squeeze',
        });
        // Top barrier (ceiling)
        const ceilGeo = new THREE.BoxGeometry(gapWidth + 1, 1, 6);
        const ceiling = new THREE.Mesh(ceilGeo, squeezeMat);
        ceiling.position.set(gapX, 9.5, zPos);
        group.add(ceiling);
        obstacles.push({
            mesh: ceiling,
            center: new THREE.Vector3(gapX, 9.5, zPos),
            radius: 1.5,
            type: 'squeeze',
        });
    }
    generateOrb(group, chunkZ, width, rng) {
        const zPos = chunkZ + rng() * config_1.CONFIG.chunkLength;
        const xPos = (rng() - 0.5) * width * 0.5;
        const yPos = 1.5 + rng() * 5;
        // Glowing orb
        const orbGeo = new THREE.SphereGeometry(0.4, 8, 8);
        const orbMat = new THREE.MeshBasicMaterial({
            color: config_1.CONFIG.colors.orbColor,
            transparent: true,
            opacity: 0.9,
        });
        const orb = new THREE.Mesh(orbGeo, orbMat);
        orb.position.set(xPos, yPos, zPos);
        orb.userData.isOrb = true;
        group.add(orb);
        // Outer glow ring
        const glowGeo = new THREE.SphereGeometry(0.7, 8, 8);
        const glowMat = new THREE.MeshBasicMaterial({
            color: config_1.CONFIG.colors.orbColor,
            transparent: true,
            opacity: 0.3,
        });
        const glow = new THREE.Mesh(glowGeo, glowMat);
        orb.add(glow);
        // Point light for the orb
        const orbLight = new THREE.PointLight(config_1.CONFIG.colors.orbColor, 1, 5);
        orb.add(orbLight);
    }
    collectOrbs(playerZ) {
        let collected = false;
        const collectRange = 2.0;
        for (const chunk of this.chunks) {
            if (Math.abs(chunk.z - playerZ) > config_1.CONFIG.chunkLength * 1.5)
                continue;
            chunk.group.traverse((obj) => {
                if (!obj.userData.isOrb || !obj.visible)
                    return;
                const dist = obj.position.distanceTo(new THREE.Vector3(0, 0, playerZ));
                // We need to compare with actual player position - this is handled in collision module
            });
        }
        return collected;
    }
    seededRng(seed) {
        let s = seed;
        return () => {
            s = (s * 16807 + 0) % 2147483647;
            return (s - 1) / 2147483646;
        };
    }
    animate(dt) {
        this.time += dt;
        for (const chunk of this.chunks) {
            if (Math.abs(chunk.z - this._lastPlayerZ) > config_1.CONFIG.chunkLength * 2)
                continue;
            chunk.group.traverse((obj) => {
                if (!obj.userData?.isOrb || !obj.visible)
                    return;
                // Pulse effect
                const pulse = 1 + Math.sin(this.time * 4 + obj.position.z * 0.5) * 0.2;
                obj.scale.set(pulse, pulse, pulse);
                // Gentle bobbing
                obj.position.y += Math.sin(this.time * 2 + obj.position.x) * 0.003;
            });
        }
    }
    dispose() {
        for (const chunk of this.chunks) {
            this.scene.remove(chunk.group);
            chunk.group.traverse((obj) => {
                if (obj.geometry)
                    obj.geometry.dispose();
                if (obj.material)
                    obj.material.dispose();
            });
        }
        this.chunks = [];
    }
}
exports.WorldGenerator = WorldGenerator;
};

// ── module: src/collision.ts ──
__mods["src/collision.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CollisionSystem = void 0;
const config_1 = require("./config");
class CollisionSystem {
    constructor() {
        this.playerRadius = 0.8;
    }
    checkCollisions(player, obstacles) {
        let crashed = false;
        let nearMiss = false;
        for (const obs of obstacles) {
            const dx = player.position.x - obs.center.x;
            const dy = player.position.y - obs.center.y;
            const dz = player.position.z - obs.center.z;
            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
            // Collision check
            if (dist < this.playerRadius + obs.radius * 0.5) {
                crashed = true;
                break;
            }
            // Near-miss detection
            if (dist < config_1.CONFIG.nearMissDistance + obs.radius * 0.6 && dist > this.playerRadius + obs.radius * 0.5) {
                nearMiss = true;
            }
        }
        return { crashed, nearMiss };
    }
    checkOrbCollection(player, scene) {
        let collected = false;
        const collectRadius = 2.0;
        scene.traverse((obj) => {
            if (!obj.userData?.isOrb || !obj.visible)
                return;
            const dx = player.position.x - obj.position.x;
            const dy = player.position.y - obj.position.y;
            const dz = player.position.z - obj.position.z;
            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
            if (dist < collectRadius) {
                // Collect the orb
                obj.visible = false;
                collected = true;
            }
        });
        return collected;
    }
}
exports.CollisionSystem = CollisionSystem;
};

// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParticleSystem = void 0;
const config_1 = require("./config");
class ParticleSystem {
    constructor(scene) {
        this.scene = scene;
        this.trailPoints = null;
        this.debrisPoints = null;
        this.trailPositions = [];
        this.debrisParticles = [];
    }
    init() {
        // Trail particles - behind the player craft
        const trailGeo = new THREE.BufferGeometry();
        const maxTrail = 200;
        const positions = new Float32Array(maxTrail * 3);
        const colors = new Float32Array(maxTrail * 3);
        const sizes = new Float32Array(maxTrail);
        trailGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        trailGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
        trailGeo.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
        const trailMat = new THREE.PointsMaterial({
            color: config_1.CONFIG.colors.playerGlow,
            size: 0.3,
            transparent: true,
            opacity: 0.7,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            vertexColors: true,
            sizeAttenuation: true,
        });
        this.trailPoints = new THREE.Points(trailGeo, trailMat);
        this.scene.add(this.trailPoints);
        // Debris particles - speed lines flying past
        const debrisGeo = new THREE.BufferGeometry();
        const maxDebris = 300;
        const dPositions = new Float32Array(maxDebris * 3);
        const dColors = new Float32Array(maxDebris * 3);
        debrisGeo.setAttribute('position', new THREE.BufferAttribute(dPositions, 3));
        debrisGeo.setAttribute('color', new THREE.BufferAttribute(dColors, 3));
        const debrisMat = new THREE.PointsMaterial({
            color: 0xffffff,
            size: 0.15,
            transparent: true,
            opacity: 0.4,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            vertexColors: true,
            sizeAttenuation: true,
        });
        this.debrisPoints = new THREE.Points(debrisGeo, debrisMat);
        this.scene.add(this.debrisPoints);
    }
    addTrailPoint(x, y, z) {
        this.trailPositions.push({ x, y, z, life: 1.0 });
        if (this.trailPositions.length > 200) {
            this.trailPositions.shift();
        }
    }
    spawnDebris(playerZ, speedRatio) {
        const count = Math.floor(speedRatio * 5);
        for (let i = 0; i < count; i++) {
            this.debrisParticles.push({
                x: (Math.random() - 0.5) * 20,
                y: Math.random() * 10 + 1,
                z: playerZ + 10 + Math.random() * 30,
                vx: (Math.random() - 0.5) * 0.5,
                vy: (Math.random() - 0.5) * 0.2,
                vz: -(speedRatio * 8 + 5),
                life: 1.0,
            });
        }
        if (this.debrisParticles.length > 300) {
            this.debrisParticles.splice(0, count);
        }
    }
    update(dt, playerZ, speedRatio) {
        // Update trail particles
        for (let i = this.trailPositions.length - 1; i >= 0; i--) {
            const p = this.trailPositions[i];
            p.life -= dt * 2.5;
            if (p.life <= 0) {
                this.trailPositions.splice(i, 1);
            }
        }
        // Update debris particles
        for (let i = this.debrisParticles.length - 1; i >= 0; i--) {
            const p = this.debrisParticles[i];
            p.x += p.vx * dt;
            p.y += p.vy * dt;
            p.z += p.vz * dt;
            p.life -= dt * 2;
            if (p.life <= 0) {
                this.debrisParticles.splice(i, 1);
            }
        }
        // Spawn debris based on speed
        if (Math.random() < speedRatio * 0.8) {
            this.spawnDebris(playerZ, speedRatio);
        }
        this.renderTrail();
        this.renderDebris();
    }
    renderTrail() {
        if (!this.trailPoints)
            return;
        const geo = this.trailPoints.geometry;
        const posAttr = geo.attributes.position;
        const colAttr = geo.attributes.color;
        const maxCount = posAttr.array.length / 3;
        for (let i = 0; i < maxCount; i++) {
            if (i < this.trailPositions.length) {
                const p = this.trailPositions[i];
                posAttr.array[i * 3] = p.x;
                posAttr.array[i * 3 + 1] = p.y;
                posAttr.array[i * 3 + 2] = p.z;
                // Color fades from orange to purple as trail ages
                const r = 1.0 - p.life * 0.5;
                const g = p.life * 0.4;
                const b = (1 - p.life) * 0.6;
                colAttr.array[i * 3] = r;
                colAttr.array[i * 3 + 1] = g;
                colAttr.array[i * 3 + 2] = b;
            }
            else {
                posAttr.array[i * 3] = 0;
                posAttr.array[i * 3 + 1] = -100;
                posAttr.array[i * 3 + 2] = 0;
            }
        }
        posAttr.needsUpdate = true;
        colAttr.needsUpdate = true;
    }
    renderDebris() {
        if (!this.debrisPoints)
            return;
        const geo = this.debrisPoints.geometry;
        const posAttr = geo.attributes.position;
        const colAttr = geo.attributes.color;
        const maxCount = posAttr.array.length / 3;
        for (let i = 0; i < maxCount; i++) {
            if (i < this.debrisParticles.length) {
                const p = this.debrisParticles[i];
                posAttr.array[i * 3] = p.x;
                posAttr.array[i * 3 + 1] = p.y;
                posAttr.array[i * 3 + 2] = p.z;
                colAttr.array[i * 3] = p.life;
                colAttr.array[i * 3 + 1] = p.life * 0.8;
                colAttr.array[i * 3 + 2] = p.life * 0.6;
            }
            else {
                posAttr.array[i * 3] = 0;
                posAttr.array[i * 3 + 1] = -100;
                posAttr.array[i * 3 + 2] = 0;
            }
        }
        posAttr.needsUpdate = true;
        colAttr.needsUpdate = true;
    }
    dispose() {
        if (this.trailPoints) {
            this.scene.remove(this.trailPoints);
            this.trailPoints.geometry.dispose();
            this.trailPoints.material.dispose();
        }
        if (this.debrisPoints) {
            this.scene.remove(this.debrisPoints);
            this.debrisPoints.geometry.dispose();
            this.debrisPoints.material.dispose();
        }
    }
}
exports.ParticleSystem = ParticleSystem;
};

// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HUD = void 0;
const config_1 = require("./config");
class HUD {
    constructor() {
        this.finalScoreEl = null;
        this.maxSpeedEl = null;
        this.restartBtn = null;
        this.container = document.createElement('div');
        this.container.id = 'hud';
        this.buildHUD();
    }
    buildHUD() {
        // Speed bar (left side)
        const speedContainer = document.createElement('div');
        speedContainer.className = 'speed-container';
        this.speedBar = document.createElement('div');
        this.speedBar.className = 'speed-bar';
        speedContainer.appendChild(this.speedBar);
        this.container.appendChild(speedContainer);
        // Multiplier (center top)
        this.multiplierEl = document.createElement('div');
        this.multiplierEl.id = 'multiplier';
        this.multiplierEl.textContent = 'x1';
        this.container.appendChild(this.multiplierEl);
        // Score (top center, below multiplier)
        this.scoreEl = document.createElement('div');
        this.scoreEl.id = 'score';
        this.scoreEl.textContent = '0';
        this.container.appendChild(this.scoreEl);
        // Energy bar (bottom center)
        const energyContainer = document.createElement('div');
        energyContainer.className = 'energy-container';
        this.energyBar = document.createElement('div');
        this.energyBar.className = 'energy-bar';
        energyContainer.appendChild(this.energyBar);
        this.container.appendChild(energyContainer);
        // Turbo bar (bottom, below energy)
        const turboContainer = document.createElement('div');
        turboContainer.className = 'turbo-container';
        this.turboBar = document.createElement('div');
        this.turboBar.className = 'turbo-bar';
        turboContainer.appendChild(this.turboBar);
        // Turbo text indicator (hidden by default)
        this.turboText = document.createElement('div');
        this.turboText.id = 'turbo-text';
        this.turboText.textContent = 'TURBO';
        this.container.appendChild(this.turboText);
        this.container.appendChild(turboContainer);
        // Game over screen (hidden by default)
        this.gameOverScreen = document.createElement('div');
        this.gameOverScreen.id = 'game-over';
        this.gameOverScreen.style.display = 'none';
        const goTitle = document.createElement('h1');
        goTitle.textContent = 'CRASHED';
        goTitle.className = 'go-title';
        this.gameOverScreen.appendChild(goTitle);
        this.finalScoreEl = document.createElement('div');
        this.finalScoreEl.className = 'go-score';
        this.finalScoreEl.textContent = '';
        this.gameOverScreen.appendChild(this.finalScoreEl);
        this.maxSpeedEl = document.createElement('div');
        this.maxSpeedEl.className = 'go-speed';
        this.maxSpeedEl.textContent = '';
        this.gameOverScreen.appendChild(this.maxSpeedEl);
        this.restartBtn = document.createElement('button');
        this.restartBtn.className = 'restart-btn';
        this.restartBtn.textContent = 'RESTART';
        this.restartBtn.addEventListener('click', () => {
            window.dispatchEvent(new CustomEvent('restart'));
        });
        this.gameOverScreen.appendChild(this.restartBtn);
        this.container.appendChild(this.gameOverScreen);
    }
    getElement() {
        return this.container;
    }
    update(player, score) {
        // Speed bar
        const speedRatio = player.getSpeedRatio();
        this.speedBar.style.height = `${speedRatio * 100}%`;
        if (player.isBoosting) {
            this.speedBar.classList.add('boost');
        }
        else {
            this.speedBar.classList.remove('boost');
        }
        // Multiplier
        const multDisplay = Math.max(1, Math.floor(player.multiplier));
        this.multiplierEl.textContent = `x${multDisplay}`;
        if (player.nearMissTimer > 0) {
            this.multiplierEl.classList.add('pulse');
        }
        else {
            this.multiplierEl.classList.remove('pulse');
        }
        // Score
        this.scoreEl.textContent = Math.floor(score).toLocaleString();
        // Energy bar
        const energyPct = player.energy / config_1.CONFIG.energyMax;
        this.energyBar.style.width = `${energyPct * 100}%`;
        if (energyPct < 0.2) {
            this.energyBar.classList.add('critical');
        }
        else {
            this.energyBar.classList.remove('critical');
        }
        // Turbo bar
        const turboPct = player.turbo / config_1.CONFIG.turboMax;
        this.turboBar.style.width = `${turboPct * 100}%`;
        if (player.isBoosting) {
            this.turboBar.classList.add('active');
            this.turboText.style.opacity = '1';
        }
        else {
            this.turboBar.classList.remove('active');
            this.turboText.style.opacity = turboPct > 0.5 ? `${turboPct}` : '0';
        }
    }
    showGameOver(score, maxSpeed) {
        this.gameOverScreen.style.display = 'flex';
        if (this.finalScoreEl)
            this.finalScoreEl.textContent = `SCORE: ${Math.floor(score).toLocaleString()}`;
        if (this.maxSpeedEl)
            this.maxSpeedEl.textContent = `TOP SPEED: ${Math.floor(maxSpeed)} U/S`;
    }
    hideGameOver() {
        this.gameOverScreen.style.display = 'none';
    }
}
exports.HUD = HUD;
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Game = void 0;
const player_1 = require("./player");
const world_1 = require("./world");
const collision_1 = require("./collision");
const particles_1 = require("./particles");
const hud_1 = require("./hud");
const audio_1 = require("./audio");
const config_1 = require("./config");
class Game {
    constructor() {
        this.state = 'menu';
        this.score = 0;
        this.maxSpeed = 0;
        this.distanceTraveled = 0;
        // Camera shake
        this.shakeX = 0;
        this.shakeY = 0;
        this.shakeIntensity = 0;
        // Input state
        this.input = { left: false, right: false, up: false, down: false, boost: false };
        this._lastTap = 0;
        // Flash effect state
        this.flashAlpha = 0;
        this.flashColor = '#ffffff';
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(config_1.CONFIG.normalFov, window.innerWidth / window.innerHeight, 0.1, 500);
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.clock = new THREE.Clock();
        this.setupScene();
        this.setupInput();
    }
    setupScene() {
        // Fog for atmosphere
        this.scene.fog = new THREE.FogExp2(config_1.CONFIG.colors.fog, 0.012);
        // Sky gradient background using a large sphere
        const skyGeo = new THREE.SphereGeometry(300, 16, 16);
        const skyMat = new THREE.ShaderMaterial({
            uniforms: {
                topColor: { value: new THREE.Color(config_1.CONFIG.colors.skyTop) },
                bottomColor: { value: new THREE.Color(config_1.CONFIG.colors.skyBottom) },
            },
            vertexShader: `
        varying vec3 vWorldPosition;
        void main() {
          vec4 worldPos = modelMatrix * vec4(position, 1.0);
          vWorldPosition = worldPos.xyz;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
            fragmentShader: `
        uniform vec3 topColor;
        uniform vec3 bottomColor;
        varying vec3 vWorldPosition;
        void main() {
          float h = normalize(vWorldPosition).y * 0.5 + 0.5;
          gl_FragColor = vec4(mix(bottomColor, topColor, h), 1.0);
        }
      `,
            side: THREE.BackSide,
        });
        const sky = new THREE.Mesh(skyGeo, skyMat);
        this.scene.add(sky);
        // Renderer settings
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        document.getElementById('app').appendChild(this.renderer.domElement);
        // Lighting
        const ambientLight = new THREE.AmbientLight(0x6b3fa0, 0.5);
        this.scene.add(ambientLight);
        // Main directional light (sun) - golden hour feel
        const sunLight = new THREE.DirectionalLight(0xff8c42, 1.8);
        sunLight.position.set(10, 20, -30);
        this.scene.add(sunLight);
        // Secondary fill light from behind
        const fillLight = new THREE.DirectionalLight(0x7b3fa0, 0.5);
        fillLight.position.set(-5, 10, 20);
        this.scene.add(fillLight);
        // Stars in the sky (distant particles)
        const starCount = 200;
        const starGeo = new THREE.BufferGeometry();
        const starPositions = new Float32Array(starCount * 3);
        for (let i = 0; i < starCount; i++) {
            const theta = Math.random() * Math.PI * 2;
            const phi = Math.random() * Math.PI * 0.4 + 0.1;
            const r = 250;
            starPositions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
            starPositions[i * 3 + 1] = r * Math.cos(phi);
            starPositions[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
        }
        const starPosAttr = new THREE.BufferAttribute(starPositions, 3);
        starGeo.setAttribute('position', starPosAttr);
        const starMat = new THREE.PointsMaterial({
            color: 0xffffff,
            size: 1.5,
            transparent: true,
            opacity: 0.6,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
        });
        const stars = new THREE.Points(starGeo, starMat);
        this.scene.add(stars);
        // Sun disc on the horizon
        const sunGeo = new THREE.SphereGeometry(15, 16, 16);
        const sunMat = new THREE.MeshBasicMaterial({
            color: 0xff8c42,
            transparent: true,
            opacity: 0.9,
        });
        this.sunMesh = new THREE.Mesh(sunGeo, sunMat);
        this.sunMesh.position.set(30, 15, -250);
        this.scene.add(this.sunMesh);
        // Sun glow ring
        const glowGeo = new THREE.SphereGeometry(22, 16, 16);
        const glowMat = new THREE.MeshBasicMaterial({
            color: 0xffaa00,
            transparent: true,
            opacity: 0.3,
        });
        this.sunGlow = new THREE.Mesh(glowGeo, glowMat);
        this.sunGlow.position.copy(this.sunMesh.position);
        this.scene.add(this.sunGlow);
        // Handle resize
        window.addEventListener('resize', () => {
            this.camera.aspect = window.innerWidth / window.innerHeight;
            this.camera.updateProjectionMatrix();
            this.renderer.setSize(window.innerWidth, window.innerHeight);
            if (this.flashCanvas) {
                this.flashCanvas.width = window.innerWidth;
                this.flashCanvas.height = window.innerHeight;
            }
        });
        // Screen flash overlay for near-miss effects
        this.flashCanvas = document.createElement('canvas');
        this.flashCanvas.id = 'flash-overlay';
        this.flashCtx = this.flashCanvas.getContext('2d');
        document.getElementById('app').appendChild(this.flashCanvas);
    }
    setupInput() {
        const keyDown = (e) => {
            switch (e.code) {
                case 'ArrowLeft':
                case 'KeyA':
                    this.input.left = true;
                    break;
                case 'ArrowRight':
                case 'KeyD':
                    this.input.right = true;
                    break;
                case 'ArrowUp':
                case 'KeyW':
                    this.input.up = true;
                    break;
                case 'ArrowDown':
                case 'KeyS':
                    this.input.down = true;
                    break;
                case 'Space':
                    e.preventDefault();
                    if (this.state === 'menu') {
                        this.startGame();
                    }
                    else if (this.state === 'playing') {
                        this.player.activateBoost();
                        if (this.player.isBoosting)
                            this.audio.playBoost();
                    }
                    break;
            }
        };
        const keyUp = (e) => {
            switch (e.code) {
                case 'ArrowLeft':
                case 'KeyA':
                    this.input.left = false;
                    break;
                case 'ArrowRight':
                case 'KeyD':
                    this.input.right = false;
                    break;
                case 'ArrowUp':
                case 'KeyW':
                    this.input.up = false;
                    break;
                case 'ArrowDown':
                case 'KeyS':
                    this.input.down = false;
                    break;
            }
        };
        window.addEventListener('keydown', keyDown);
        window.addEventListener('keyup', keyUp);
        // Touch controls for mobile
        let touchStartX = 0, touchStartY = 0;
        const canvas = this.renderer.domElement;
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            if (this.state === 'menu') {
                this.startGame();
                return;
            }
            touchStartX = e.touches[0].clientX;
            touchStartY = e.touches[0].clientY;
            // Double tap for boost
            const now = Date.now();
            if (this._lastTap && now - this._lastTap < 300) {
                this.player.activateBoost();
                if (this.player.isBoosting)
                    this.audio.playBoost();
            }
            this._lastTap = now;
        }, { passive: false });
        canvas.addEventListener('touchmove', (e) => {
            e.preventDefault();
            const dx = e.touches[0].clientX - touchStartX;
            const dy = e.touches[0].clientY - touchStartY;
            this.input.left = dx < -20;
            this.input.right = dx > 20;
            this.input.up = dy < -20;
            this.input.down = dy > 20;
        }, { passive: false });
        canvas.addEventListener('touchend', () => {
            this.input.left = false;
            this.input.right = false;
            this.input.up = false;
            this.input.down = false;
        });
    }
    startGame() {
        // Hide menu overlay
        const menuOverlay = document.getElementById('menu-overlay');
        if (menuOverlay)
            menuOverlay.style.display = 'none';
        // Initialize audio on user gesture
        if (!this.audio)
            this.audio = new audio_1.AudioSystem();
        this.audio.init();
        // Reset game state
        this.player = new player_1.PlayerCraft();
        this.scene.add(this.player.mesh);
        this.world = new world_1.WorldGenerator(this.scene);
        this.collision = new collision_1.CollisionSystem();
        this.particles = new particles_1.ParticleSystem(this.scene);
        this.particles.init();
        this.hud = new hud_1.HUD();
        document.getElementById('app').appendChild(this.hud.getElement());
        this.state = 'playing';
        this.score = 0;
        this.maxSpeed = 0;
        this.distanceTraveled = 0;
        this.shakeIntensity = 0;
        // Set initial camera position
        this.camera.position.set(0, config_1.CONFIG.cameraHeight + 2, -config_1.CONFIG.cameraDistance);
        this.camera.lookAt(this.player.position.x, this.player.position.y, this.player.position.z + 10);
    }
    update() {
        if (this.state !== 'playing')
            return;
        const dt = Math.min(this.clock.getDelta(), 0.05); // Cap delta to prevent spiral of death
        // Update player physics
        this.player.update(dt, this.input);
        // Move player forward
        this.player.position.z += this.player.speed * dt * 60;
        this.distanceTraveled = this.player.position.z;
        // Track max speed
        const currentSpeedKmh = this.player.speed * 100;
        if (currentSpeedKmh > this.maxSpeed) {
            this.maxSpeed = currentSpeedKmh;
        }
        // Update world generation and animation
        this.world.update(this.player.position.z);
        this.world.animate(dt);
        this.world.setDifficulty(1 + this.score * config_1.CONFIG.difficultyObstacleDensity);
        // Collision detection
        const obstaclesInRange = this.world.getObstaclesInRange(this.player.position.z, 60);
        const { crashed, nearMiss } = this.collision.checkCollisions(this.player, obstaclesInRange);
        if (crashed) {
            this.gameOver();
            return;
        }
        // Near-miss scoring
        if (nearMiss && this.player.nearMissTimer <= 0) {
            this.player.triggerNearMiss();
            this.audio.playNearMiss();
            this.shakeIntensity = config_1.CONFIG.shakeAmountNearMiss;
            this.triggerFlash(0.4, '#ffaa00');
        }
        // Orb collection
        const orbCollected = this.collision.checkOrbCollection(this.player, this.scene);
        if (orbCollected) {
            this.player.recharge(config_1.CONFIG.orbRechargeAmount);
            this.audio.playCollect();
        }
        // Score accumulation
        this.score += this.player.speed * this.player.multiplier * dt * 10;
        // Energy depletion = game over
        if (this.player.energy <= 0) {
            this.gameOver();
            return;
        }
        // Update particles
        const speedRatio = this.player.getSpeedRatio();
        this.particles.addTrailPoint(this.player.position.x, this.player.position.y - 0.3, this.player.position.z + 1);
        this.particles.update(dt, this.player.position.z, speedRatio);
        // Update audio engine pitch
        this.audio.setEnginePitch(speedRatio);
        // Camera follow with shake
        this.updateCamera(dt);
        // FOV effect for boost
        const targetFov = this.player.isBoosting ? config_1.CONFIG.boostFov : config_1.CONFIG.normalFov;
        this.camera.fov += (targetFov - this.camera.fov) * 0.05;
        this.camera.updateProjectionMatrix();
        // Update HUD
        this.hud.update(this.player, this.score);
    }
    updateCamera(dt) {
        const targetX = this.player.position.x * 0.7;
        const targetY = this.player.position.y + config_1.CONFIG.cameraHeight;
        // Camera shake decay
        if (this.shakeIntensity > 0) {
            this.shakeX = (Math.random() - 0.5) * this.shakeIntensity * 2;
            this.shakeY = (Math.random() - 0.5) * this.shakeIntensity * 2;
            this.shakeIntensity *= 0.9;
            if (this.shakeIntensity < 0.01) {
                this.shakeIntensity = 0;
                this.shakeX = 0;
                this.shakeY = 0;
            }
        }
        // Boost shake
        if (this.player.isBoosting && this.shakeIntensity === 0) {
            this.shakeX = (Math.random() - 0.5) * config_1.CONFIG.shakeAmountBoost;
            this.shakeY = (Math.random() - 0.5) * config_1.CONFIG.shakeAmountBoost;
        }
        const camZ = this.player.position.z - config_1.CONFIG.cameraDistance;
        this.camera.position.x += (targetX + this.shakeX - this.camera.position.x) * config_1.CONFIG.cameraLerpFactor;
        this.camera.position.y += (targetY + this.shakeY - this.camera.position.y) * config_1.CONFIG.cameraLerpFactor;
        this.camera.position.z = camZ;
        // Look ahead of player
        const lookAhead = 15 + this.player.speed * 3;
        this.camera.lookAt(this.player.position.x * 0.5, this.player.position.y + 1, this.player.position.z + lookAhead);
        // Move sun to follow camera (stays on horizon)
        if (this.sunMesh && this.sunGlow) {
            this.sunMesh.position.set(30, 15, this.camera.position.z - 250);
            this.sunGlow.position.copy(this.sunMesh.position);
        }
    }
    triggerFlash(alpha, color) {
        this.flashAlpha = alpha;
        this.flashColor = color;
    }
    updateFlash() {
        if (this.flashAlpha <= 0 || !this.flashCtx)
            return;
        const ctx = this.flashCtx;
        ctx.clearRect(0, 0, this.flashCanvas.width, this.flashCanvas.height);
        ctx.fillStyle = this.flashColor + Math.floor(this.flashAlpha * 255).toString(16).padStart(2, '0');
        ctx.fillRect(0, 0, this.flashCanvas.width, this.flashCanvas.height);
        this.flashAlpha *= 0.85;
        if (this.flashAlpha < 0.01)
            this.flashAlpha = 0;
    }
    gameOver() {
        this.state = 'gameover';
        this.audio.playCrash();
        this.audio.stopEngine();
        if (this.hud) {
            this.hud.showGameOver(this.score, this.maxSpeed);
        }
        // Dramatic camera shake on crash
        this.shakeIntensity = 1.5;
    }
    render() {
        this.updateFlash();
        this.renderer.render(this.scene, this.camera);
    }
    restart() {
        // Clean up old game objects
        if (this.player) {
            this.scene.remove(this.player.mesh);
        }
        if (this.world) {
            this.world.dispose();
        }
        if (this.particles) {
            this.particles.dispose();
        }
        if (this.hud) {
            const el = this.hud.getElement();
            if (el.parentNode)
                el.parentNode.removeChild(el);
        }
        // Reset input
        this.input = { left: false, right: false, up: false, down: false, boost: false };
        // Start new game
        this.startGame();
    }
    dispose() {
        if (this.player)
            this.scene.remove(this.player.mesh);
        if (this.world)
            this.world.dispose();
        if (this.particles)
            this.particles.dispose();
        if (this.hud) {
            const el = this.hud.getElement();
            if (el.parentNode)
                el.parentNode.removeChild(el);
        }
        this.audio.stopEngine();
        this.renderer.dispose();
    }
}
exports.Game = Game;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"a572a3a6-1f32-4888-ad06-ebc9ab9e561d","tokensIn":2755950,"tokensOut":36130,"tokensTotal":2792080,"cost":0,"turns":80,"toolCalls":79,"failedToolCalls":3,"timestamp":"2026-07-31T19:46:26.790Z"} -->