← Canyon Run results

Canyon Run

Qwen 3.5 122b a10b · 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 63,306 bytes · SHA-256 bb6066a6fb25
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>High-Speed Canyon Racer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* ── css: layout ── */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  overflow: hidden;
  background: #0a0a1a;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

#game-container {
  position: relative;
  width: 100vw;
  height: 100vh;
}

#game-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* ── css: hud ── */
#hud {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 10;
}

/* Speedometer - bottom left */
#speedometer {
  position: absolute;
  bottom: 30px;
  left: 30px;
  display: flex;
  align-items: center;
  gap: 15px;
}

#speed-bar {
  width: 200px;
  height: 8px;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 4px;
  overflow: hidden;
}

#speed-bar::after {
  content: '';
  display: block;
  width: var(--speed-pct, 0%);
  height: 100%;
  background: linear-gradient(90deg, #ff6b35, #f7c59f);
  transition: width 0.1s ease-out;
}

#speed-text {
  color: #f7c59f;
  font-size: 24px;
  font-weight: bold;
  text-shadow: 0 0 10px rgba(247, 197, 159, 0.5);
}

/* Multiplier display - top center */
#multiplier-display {
  position: absolute;
  top: 30px;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
}

#mult-value {
  display: block;
  font-size: 64px;
  font-weight: bold;
  color: #ffd700;
  text-shadow: 0 0 20px rgba(255, 215, 0, 0.8), 0 0 40px rgba(255, 215, 0, 0.4);
  transition: transform 0.15s ease-out;
}

#mult-value.pulse {
  animation: pulse-glow 0.3s ease-out;
}

@keyframes pulse-glow {
  0% { transform: scale(1); }
  50% { transform: scale(1.3); }
  100% { transform: scale(1); }
}

.mult-label {
  display: block;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.6);
  letter-spacing: 3px;
  margin-top: 5px;
}

/* Energy bar - bottom center */
#energy-container {
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
}

#energy-bar {
  width: 300px;
  height: 12px;
  background: rgba(0, 0, 0, 0.5);
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-radius: 6px;
  overflow: hidden;
}

#energy-fill {
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, #00d4ff, #00ff88);
  transition: width 0.1s ease-out;
  box-shadow: 0 0 15px rgba(0, 212, 255, 0.6);
}

.energy-label {
  display: block;
  font-size: 10px;
  color: rgba(255, 255, 255, 0.6);
  letter-spacing: 2px;
  margin-top: 5px;
}

/* Boost meter - bottom right */
#boost-meter {
  position: absolute;
  bottom: 30px;
  right: 30px;
  text-align: right;
}

#boost-fill {
  width: 0%;
  height: 15px;
  background: linear-gradient(90deg, #ff0066, #ff6b35);
  border-radius: 3px;
  transition: width 0.1s ease-out;
  box-shadow: 0 0 20px rgba(255, 0, 102, 0.6);
  margin-bottom: 5px;
}

.boost-label {
  display: block;
  font-size: 10px;
  color: rgba(255, 255, 255, 0.6);
  letter-spacing: 2px;
}

/* Score display - top right */
#score-display {
  position: absolute;
  top: 30px;
  right: 30px;
  text-align: right;
}

#score-value {
  display: block;
  font-size: 36px;
  font-weight: bold;
  color: #f7c59f;
  text-shadow: 0 0 10px rgba(247, 197, 159, 0.5);
}

.score-label {
  display: block;
  font-size: 10px;
  color: rgba(255, 255, 255, 0.6);
  letter-spacing: 2px;
}
/* ── css: screens ── */
.hidden {
  display: none !important;
}

/* Start screen */
#start-screen {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(135deg, rgba(10, 10, 26, 0.95) 0%, rgba(40, 20, 40, 0.95) 100%);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 100;
  pointer-events: auto;
}

#start-screen h1 {
  font-size: 72px;
  color: #ffd700;
  text-shadow: 0 0 30px rgba(255, 215, 0, 0.8), 0 0 60px rgba(255, 107, 53, 0.5);
  letter-spacing: 10px;
  margin-bottom: 10px;
}

#start-screen .subtitle {
  font-size: 20px;
  color: rgba(247, 197, 159, 0.8);
  letter-spacing: 5px;
  margin-bottom: 50px;
}

.controls-info {
  background: rgba(0, 0, 0, 0.3);
  padding: 30px 50px;
  border-radius: 10px;
  margin-bottom: 40px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.controls-info p {
  color: rgba(255, 255, 255, 0.7);
  font-size: 14px;
  margin: 10px 0;
  line-height: 1.8;
}

.controls-info strong {
  color: #f7c59f;
}

#start-btn, #restart-btn {
  background: linear-gradient(135deg, #ff6b35 0%, #ff0066 100%);
  border: none;
  color: white;
  font-size: 24px;
  font-weight: bold;
  letter-spacing: 5px;
  padding: 20px 60px;
  border-radius: 50px;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 5px 30px rgba(255, 107, 53, 0.4);
}

#start-btn:hover, #restart-btn:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 40px rgba(255, 107, 53, 0.6);
}

#start-btn:active, #restart-btn:active {
  transform: translateY(-1px);
}

/* Game over screen */
#game-over {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(135deg, rgba(10, 10, 26, 0.98) 0%, rgba(40, 10, 20, 0.98) 100%);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 100;
  pointer-events: auto;
}

#game-over h1 {
  font-size: 64px;
  color: #ff0066;
  text-shadow: 0 0 30px rgba(255, 0, 102, 0.8), 0 0 60px rgba(255, 0, 102, 0.4);
  letter-spacing: 15px;
  margin-bottom: 40px;
}

#final-score, #max-speed-display {
  font-size: 28px;
  color: #f7c59f;
  margin: 10px 0;
  text-shadow: 0 0 10px rgba(247, 197, 159, 0.5);
}

#final-score {
  font-size: 36px;
  margin-bottom: 20px;
}
</style>
</head>
<body>
<div id="game-container">
  <canvas id="game-canvas"></canvas>
  <div id="hud">
    <div id="speedometer">
      <div id="speed-bar"></div>
      <span id="speed-text">0 km/h</span>
    </div>
    <div id="multiplier-display">
      <span id="mult-value">1.0x</span>
      <span class="mult-label">MULTIPLIER</span>
    </div>
    <div id="energy-container">
      <div id="energy-bar">
        <div id="energy-fill"></div>
      </div>
      <span class="energy-label">ENERGY</span>
    </div>
    <div id="boost-meter">
      <div id="boost-fill"></div>
      <span class="boost-label">BOOST READY</span>
    </div>
    <div id="score-display">
      <span id="score-value">0</span>
      <span class="score-label">SCORE</span>
    </div>
  </div>
  <div id="game-over" class="hidden">
    <h1>CRASHED!</h1>
    <div id="final-score">Score: 0</div>
    <div id="max-speed-display">Max Speed: 0 km/h</div>
    <button id="restart-btn">RESTART</button>
  </div>
  <div id="start-screen">
    <h1>CANYON RACER</h1>
    <p class="subtitle">High-Speed Procedural Adventure</p>
    <div class="controls-info">
      <p><strong>Arrow Keys / WASD</strong> - Steer &amp; Pitch</p>
      <p><strong>Space</strong> - Boost (when meter full)</p>
      <p>Collect orbs for energy • Pass close to walls for multiplier</p>
    </div>
    <button id="start-btn">START RUN</button>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./player":"src/player.ts","./world":"src/world.ts","./particles":"src/particles.ts","./audio":"src/audio.ts","./hud":"src/hud.ts","./state":"src/state.ts"},"src/player.ts":{"./state":"src/state.ts"},"src/world.ts":{"./state":"src/state.ts"},"src/particles.ts":{"./state":"src/state.ts"},"src/hud.ts":{"./state":"src/state.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.game = void 0;
const player_1 = require("./player");
const world_1 = require("./world");
const particles_1 = require("./particles");
const audio_1 = require("./audio");
const hud_1 = require("./hud");
const state_1 = require("./state");
// Three.js globals (declared in globals.d.ts)
class Game {
    constructor() {
        this.scene = null;
        this.camera = null;
        this.renderer = null;
        this.directionalLight = null;
        this.ambientLight = null;
        this.fogColor = 0x1a0f2e;
        this.lastTime = 0;
        this.animationId = null;
        this.cameraShake = 0;
        this.baseFOV = 75;
        this.initThree();
        this.setupLights();
        this.setupInputs();
        this.setupEventListeners();
    }
    initThree() {
        const canvas = document.getElementById('game-canvas');
        // Scene
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(this.fogColor);
        const fogObj = { color: new THREE.Color(this.fogColor), near: 50, far: 300 };
        this.scene.fog = fogObj;
        // Camera
        this.camera = new THREE.PerspectiveCamera(this.baseFOV, window.innerWidth / window.innerHeight, 0.1, 1000);
        this.camera.position.set(0, 8, 15);
        // Renderer
        this.renderer = new THREE.WebGLRenderer({
            canvas,
            antialias: true,
            powerPreference: 'high-performance'
        });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap || 2;
        // Handle resize
        window.addEventListener('resize', () => this.onResize());
    }
    setupLights() {
        if (!this.scene)
            return;
        // Ambient light for base illumination
        this.ambientLight = new THREE.AmbientLight(0xffd4a3, 0.4);
        if (this.ambientLight && this.scene)
            this.scene.add(this.ambientLight);
        // Directional light (sun) - golden hour angle
        this.directionalLight = new THREE.DirectionalLight(0xff9933, 1.2);
        this.directionalLight.position.set(50, 30, -50);
        this.directionalLight.castShadow = true;
        this.directionalLight.shadow.mapSize.x = 2048;
        this.directionalLight.shadow.mapSize.y = 2048;
        const shadowCam = this.directionalLight.shadow.camera;
        shadowCam.near = 10;
        shadowCam.far = 200;
        shadowCam.left = -50;
        shadowCam.right = 50;
        shadowCam.top = 50;
        shadowCam.bottom = -50;
        this.scene.add(this.directionalLight);
        // Add a point light near player for engine glow effect
        const playerLight = new THREE.PointLight(0x00ffff, 0.5, 20);
        playerLight.position.set(0, 5, 0);
        this.scene.add(playerLight);
    }
    setupInputs() {
        document.addEventListener('keydown', (e) => this.onKeyDown(e));
        document.addEventListener('keyup', (e) => this.onKeyUp(e));
    }
    setupEventListeners() {
        window.addEventListener('gameStart', () => this.startGame());
        window.addEventListener('gameRestart', () => this.restartGame());
    }
    onKeyDown(e) {
        const state = (0, state_1.getState)();
        if (state.isGameOver)
            return;
        switch (e.code) {
            case 'ArrowLeft':
            case 'KeyA':
            case 'ArrowRight':
            case 'KeyD':
            case 'ArrowUp':
            case 'KeyW':
            case 'ArrowDown':
            case 'KeyS':
                e.preventDefault();
                player_1.player.setInput(e.code, true);
                break;
            case 'Space':
                e.preventDefault();
                player_1.player.setInput('Space', true);
                if ((0, state_1.canBoost)()) {
                    this.activateBoost();
                }
                break;
        }
    }
    onKeyUp(e) {
        player_1.player.setInput(e.code, false);
    }
    activateBoost() {
        (0, state_1.consumeBoost)();
        player_1.player.isBoosting = true;
        player_1.player.boostTimer = 1.5;
        // FOV increase for speed sensation
        if (this.camera) {
            this.camera.fov = this.baseFOV + 30;
            this.camera.updateProjectionMatrix();
        }
        // Camera shake
        this.cameraShake = 0.3;
        // Audio feedback
        audio_1.audio.playBoost();
        // Boost sound continues during boost
    }
    onResize() {
        if (!this.camera || !this.renderer)
            return;
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }
    startGame() {
        (0, state_1.resetGame)();
        (0, state_1.setState)({
            isRunning: true,
            isGameOver: false,
            score: 0,
            multiplier: 1.0,
            energy: 100,
            boostAmount: 0,
            speed: 50,
            maxSpeed: 50,
            distance: 0,
            time: 0
        });
        player_1.player.reset();
        world_1.world.cleanup();
        hud_1.hud.hideStartScreen();
        hud_1.hud.hideGameOver();
        // Initialize audio
        audio_1.audio.init();
        audio_1.audio.setEngineVolume(1);
        // Start game loop
        if (!this.animationId) {
            this.lastTime = performance.now();
            this.animate();
        }
    }
    restartGame() {
        this.startGame();
    }
    gameOver() {
        const state = (0, state_1.getState)();
        (0, state_1.setState)({
            isRunning: false,
            isGameOver: true
        });
        audio_1.audio.setEngineVolume(0);
        audio_1.audio.playCrash();
        hud_1.hud.showGameOver(state.score, state.maxSpeed);
    }
    animate() {
        const currentTime = performance.now();
        const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
        this.lastTime = currentTime;
        const state = (0, state_1.getState)();
        if (state.isRunning && !state.isGameOver) {
            this.update(deltaTime);
        }
        this.render();
        this.animationId = requestAnimationFrame(() => this.animate());
    }
    update(deltaTime) {
        const state = (0, state_1.getState)();
        // Update player
        player_1.player.update(deltaTime);
        // Update world generation based on player position
        world_1.world.update(player_1.player.physics.position.z);
        // Energy decay
        const energyDecay = 2 * deltaTime; // 2% per second
        (0, state_1.setEnergy)(state.energy - energyDecay);
        if (state.energy <= 0) {
            this.gameOver();
            return;
        }
        // Collision detection
        const collisionResult = world_1.world.checkCollision(player_1.player.physics.position, 1.5);
        if (collisionResult.collided) {
            this.gameOver();
            return;
        }
        // Check near-miss for multiplier
        if (collisionResult.distance < 5 && collisionResult.distance > 1.5) {
            const proximityBonus = (5 - collisionResult.distance) / 3.5;
            const newMultiplier = Math.min(5, (0, state_1.getMultiplier)() + proximityBonus * 0.5);
            (0, state_1.updateMultiplier)(newMultiplier);
            if (proximityBonus > 0.5) {
                hud_1.hud.pulseMultiplier();
                (0, state_1.triggerMultiplierPulse)();
                // Near miss bonus points
                (0, state_1.addScore)(50 * proximityBonus);
                // Audio feedback for near miss
                audio_1.audio.playNearMiss((0, state_1.getMultiplier)());
                // Subtle camera shake
                this.cameraShake += proximityBonus * 0.1;
            }
        }
        // Check orb collection
        const collectedOrb = world_1.world.checkOrbCollection(player_1.player.physics.position);
        if (collectedOrb) {
            // Energy recharge
            (0, state_1.setEnergy)(state.energy + 25);
            // Score bonus
            (0, state_1.addScore)(100 * (0, state_1.getMultiplier)());
            // Boost charge
            (0, state_1.modifyBoost)(20);
            // Audio feedback
            audio_1.audio.playCollectible();
            // Visual feedback
            this.cameraShake += 0.1;
        }
        // Passive boost charging at high speeds
        if (state.speed > 100) {
            (0, state_1.modifyBoost)(deltaTime * 5);
        }
        // Update score based on distance
        const distanceTraveled = -player_1.player.physics.position.z;
        const speedBonus = state.speed / 100;
        (0, state_1.addScore)(deltaTime * 10 * speedBonus * (0, state_1.getMultiplier)());
        // Update speed in state
        state.speed = Math.floor(player_1.player.physics.speed * (player_1.player.isBoosting ? 2 : 1));
        if (state.speed > state.maxSpeed) {
            state.maxSpeed = state.speed;
        }
        // Gradually increase speed over time
        if (!player_1.player.isBoosting && state.speed < 150) {
            player_1.player.physics.speed += deltaTime * 2;
        }
        // Reduce boost when active
        if (player_1.player.isBoosting) {
            player_1.player.boostTimer -= deltaTime;
            if (player_1.player.boostTimer <= 0) {
                player_1.player.isBoosting = false;
                // Restore FOV
                if (this.camera) {
                    this.camera.fov = this.baseFOV;
                    this.camera.updateProjectionMatrix();
                }
            }
        }
        // Update camera to follow player with smooth lag
        if (this.camera) {
            const targetX = player_1.player.physics.position.x * 0.3;
            const targetY = player_1.player.physics.position.y + 6;
            const targetZ = player_1.player.physics.position.z + 15;
            this.camera.position.x += (targetX - this.camera.position.x) * deltaTime * 2;
            this.camera.position.y += (targetY - this.camera.position.y) * deltaTime * 2;
            this.camera.position.z += (targetZ - this.camera.position.z) * deltaTime * 3;
            // Camera looks slightly ahead of player
            const lookTarget = new THREE.Vector3(player_1.player.physics.position.x, player_1.player.physics.position.y, player_1.player.physics.position.z - 20);
            this.camera.lookAt(lookTarget);
            // Apply camera shake
            if (this.cameraShake > 0) {
                this.camera.position.x += (Math.random() - 0.5) * this.cameraShake;
                this.camera.position.y += (Math.random() - 0.5) * this.cameraShake;
                this.cameraShake *= 0.95;
                if (this.cameraShake < 0.01)
                    this.cameraShake = 0;
            }
        }
        // Update particle systems
        particles_1.particles.update(deltaTime, player_1.player.physics.position);
        // Update audio
        audio_1.audio.setSpeed(state.speed);
        // Update HUD
        hud_1.hud.update();
    }
    render() {
        if (!this.scene || !this.camera || !this.renderer)
            return;
        // Update player mesh position in scene
        const playerMesh = player_1.player.getMesh();
        if (playerMesh) {
            // Remove old player mesh if exists
            const existingPlayer = this.scene.children.find(c => c === playerMesh);
            if (!existingPlayer) {
                this.scene.add(playerMesh);
            }
        }
        // Add trail particles
        const trail = player_1.player.getTrail();
        if (trail && !this.scene.children.includes(trail)) {
            this.scene.add(trail);
        }
        // Add world meshes (only new ones to avoid duplicates)
        const worldMeshes = world_1.world.getAllMeshes();
        for (const mesh of worldMeshes) {
            if (!this.scene.children.includes(mesh)) {
                this.scene.add(mesh);
            }
        }
        // Add particle systems
        const dust = particles_1.particles.getDustPoints();
        const debris = particles_1.particles.getDebrisPoints();
        const sparks = particles_1.particles.getSparkPoints();
        if (dust && !this.scene.children.includes(dust)) {
            this.scene.add(dust);
        }
        if (debris && !this.scene.children.includes(debris)) {
            this.scene.add(debris);
        }
        if (sparks && !this.scene.children.includes(sparks)) {
            this.scene.add(sparks);
        }
        // Render
        this.renderer.render(this.scene, this.camera);
    }
}
// Create and start the game
const game = new Game();
exports.game = game;
// Show start screen initially
hud_1.hud.showStartScreen();
};

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

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// Game state management
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultState = void 0;
exports.getState = getState;
exports.setState = setState;
exports.resetGame = resetGame;
exports.addScore = addScore;
exports.updateMultiplier = updateMultiplier;
exports.setEnergy = setEnergy;
exports.modifyBoost = modifyBoost;
exports.canBoost = canBoost;
exports.consumeBoost = consumeBoost;
exports.getMultiplier = getMultiplier;
exports.triggerMultiplierPulse = triggerMultiplierPulse;
exports.defaultState = {
    isRunning: false,
    isGameOver: false,
    score: 0,
    multiplier: 1.0,
    energy: 100,
    boostAmount: 0,
    speed: 0,
    maxSpeed: 0,
    distance: 0,
    time: 0
};
let currentState = { ...exports.defaultState };
function getState() {
    return { ...currentState };
}
function setState(newState) {
    currentState = { ...currentState, ...newState };
}
function resetGame() {
    currentState = { ...exports.defaultState };
    currentState.speed = 50; // Starting speed
    currentState.maxSpeed = 50;
}
function addScore(points) {
    currentState.score += points * currentState.multiplier;
}
function updateMultiplier(value) {
    currentState.multiplier = Math.max(1, Math.min(5, value));
}
function setEnergy(value) {
    currentState.energy = Math.max(0, Math.min(100, value));
}
function modifyBoost(amount) {
    currentState.boostAmount = Math.max(0, Math.min(100, currentState.boostAmount + amount));
}
function canBoost() {
    return currentState.boostAmount >= 100 && currentState.isRunning && !currentState.isGameOver;
}
function consumeBoost() {
    if (canBoost()) {
        currentState.boostAmount = 0;
    }
}
function getMultiplier() {
    return currentState.multiplier;
}
function triggerMultiplierPulse() {
    // Signal for UI pulse effect
    currentState.time += 0.1;
}
};

// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.player = exports.Player = void 0;
const state_1 = require("./state");
class Player {
    constructor() {
        this.mesh = null;
        this.engineGlow = null;
        this.trailParticles = null;
        this.isBoosting = false;
        this.boostTimer = 0;
        this.targetBanking = 0;
        this.inputState = {
            left: false,
            right: false,
            up: false,
            down: false,
            boost: false
        };
        this.particles = [];
        this.particleIndex = 0;
        this.physics = {
            position: new THREE.Vector3(0, 5, 0),
            velocity: new THREE.Vector3(0, 0, 0),
            rotation: new THREE.Euler(0, 0, 0),
            speed: 50,
            maxSpeed: 150,
            turnSpeed: 3,
            pitchSpeed: 2,
            bankingAngle: 0,
            targetBanking: 0
        };
        this.createCraft();
        this.createTrailSystem();
    }
    createCraft() {
        const group = new THREE.Group();
        // Main fuselage - sleek geometric shape (pointing down Z)
        const fuselageGeo = new THREE.ConeGeometry(0.3, 2, 8);
        const fuselageMat = new THREE.MeshPhongMaterial({
            color: 0xc0c0c0,
            shininess: 100,
            flatShading: true
        });
        const fuselage = new THREE.Mesh(fuselageGeo, fuselageMat);
        fuselage.rotation.x = Math.PI / 2;
        group.add(fuselage);
        // Wings
        const wingGeo = new THREE.BoxGeometry(2.5, 0.05, 0.8);
        const wingMat = new THREE.MeshPhongMaterial({
            color: 0xa0a0a0,
            shininess: 80,
            flatShading: true
        });
        const wings = new THREE.Mesh(wingGeo, wingMat);
        wings.position.z = 0.3;
        group.add(wings);
        // Tail fins
        const tailGeo = new THREE.BoxGeometry(0.8, 0.4, 0.05);
        const tail = new THREE.Mesh(tailGeo, wingMat);
        tail.position.set(0, 0.2, 0.8);
        group.add(tail);
        const tailVertGeo = new THREE.BoxGeometry(0.05, 0.5, 0.6);
        const tailVert = new THREE.Mesh(tailVertGeo, wingMat);
        tailVert.position.set(0, 0.25, 0.8);
        group.add(tailVert);
        // Engine glow
        const engineGeo = new THREE.SphereGeometry(0.2, 8, 8);
        const engineMat = new THREE.MeshBasicMaterial({
            color: 0x00ffff,
            transparent: true,
            opacity: 0.8
        });
        this.engineGlow = new THREE.Mesh(engineGeo, engineMat);
        this.engineGlow.position.z = 1.1;
        group.add(this.engineGlow);
        group.castShadow = true;
        this.mesh = group;
    }
    createTrailSystem() {
        // Create multiple particle buffers for continuous trail
        const particleCount = 50;
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(particleCount * 3);
        const sizes = new Float32Array(particleCount);
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
        const material = new THREE.PointsMaterial({
            color: 0x00ffff,
            size: 0.3,
            transparent: true,
            opacity: 0.6,
            blending: THREE.AdditiveBlending || 1
        });
        this.trailParticles = new THREE.Points(geometry, material);
        this.trailParticles.visible = false;
    }
    update(deltaTime) {
        const state = (0, state_1.getState)();
        // Handle boost
        if (this.inputState.boost && (0, state_1.canBoost)()) {
            this.isBoosting = true;
            (0, state_1.consumeBoost)();
            this.boostTimer = 1.5; // Boost duration in seconds
        }
        if (this.isBoosting) {
            this.boostTimer -= deltaTime;
            if (this.boostTimer <= 0) {
                this.isBoosting = false;
            }
        }
        // Calculate effective speed
        const baseSpeed = this.physics.speed;
        const currentSpeed = this.isBoosting ? baseSpeed * 2 : baseSpeed;
        // Update input-based movement
        const moveLeft = this.inputState.left ? 1 : 0;
        const moveRight = this.inputState.right ? 1 : 0;
        const moveUp = this.inputState.up ? 1 : 0;
        const moveDown = this.inputState.down ? 1 : 0;
        // Banking calculation
        const turnInput = moveRight - moveLeft;
        this.targetBanking = turnInput * 0.5; // Max 30 degrees
        this.physics.bankingAngle += (this.targetBanking - this.physics.bankingAngle) * deltaTime * 5;
        // Apply rotation based on banking
        this.physics.rotation.z = this.physics.bankingAngle;
        this.physics.rotation.x = (moveUp - moveDown) * 0.3;
        this.physics.rotation.y = -turnInput * 0.2;
        // Position updates
        this.physics.position.x += turnInput * this.physics.turnSpeed * deltaTime * 10;
        this.physics.position.y += (moveUp - moveDown) * this.physics.pitchSpeed * deltaTime * 10;
        // Forward movement (along negative Z)
        this.physics.position.z -= currentSpeed * deltaTime;
        // Bounds checking
        this.physics.position.x = Math.max(-25, Math.min(25, this.physics.position.x));
        this.physics.position.y = Math.max(2, Math.min(15, this.physics.position.y));
        // Update mesh if exists
        if (this.mesh) {
            this.mesh.position.copy(this.physics.position);
            this.mesh.rotation.copy(this.physics.rotation);
            // Engine glow pulsing
            if (this.engineGlow) {
                const pulse = 1 + Math.sin(Date.now() * 0.01) * 0.2;
                this.engineGlow.scale.set(pulse, pulse, pulse);
                this.engineGlow.material.opacity = 0.6 + Math.random() * 0.4;
            }
        }
        // Update trail particles
        this.updateTrail(deltaTime);
        // Update state
        state.speed = Math.floor(currentSpeed);
        if (state.speed > state.maxSpeed) {
            state.maxSpeed = state.speed;
        }
    }
    updateTrail(deltaTime) {
        if (!this.trailParticles || !this.mesh)
            return;
        const positions = this.trailParticles.geometry.attributes.position.array;
        // Shift all particles back
        for (let i = (this.particleIndex + 1) % 50; i !== this.particleIndex; i = (i + 1) % 50) {
            const idx = i * 3;
            positions[idx] = this.mesh.position.x + (Math.random() - 0.5) * 0.3;
            positions[idx + 1] = this.mesh.position.y + (Math.random() - 0.5) * 0.2;
            positions[idx + 2] = this.mesh.position.z + 1 + (50 - i) * 0.2;
        }
        this.trailParticles.geometry.attributes.position.needsUpdate = true;
        this.trailParticles.visible = true;
        this.particleIndex = (this.particleIndex + 1) % 50;
    }
    setInput(key, pressed) {
        switch (key) {
            case 'ArrowLeft':
            case 'KeyA':
                this.inputState.left = pressed;
                break;
            case 'ArrowRight':
            case 'KeyD':
                this.inputState.right = pressed;
                break;
            case 'ArrowUp':
            case 'KeyW':
                this.inputState.up = pressed;
                break;
            case 'ArrowDown':
            case 'KeyS':
                this.inputState.down = pressed;
                break;
            case 'Space':
                this.inputState.boost = pressed;
                break;
        }
    }
    getMesh() {
        return this.mesh;
    }
    getTrail() {
        return this.trailParticles;
    }
    reset() {
        this.physics.position.set(0, 5, 0);
        this.physics.velocity.set(0, 0, 0);
        this.physics.rotation.set(0, 0, 0);
        this.physics.bankingAngle = 0;
        this.isBoosting = false;
        this.boostTimer = 0;
    }
}
exports.Player = Player;
exports.player = new Player();
};

// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.world = exports.World = void 0;
const state_1 = require("./state");
class World {
    constructor() {
        this.chunks = [];
        this.chunkLength = 50;
        this.chunkCount = 8;
        this.currentZ = 0;
        this.canyonWidth = 60;
        this.wallHeight = 20;
        // Colors for golden hour aesthetic
        this.colors = {
            wall: 0x8B4513,
            wallDark: 0x5D2906,
            monolith: 0x4a4a4a,
            orb: 0x00ffff,
            ground: 0x2d1f1f
        };
        this.generateInitialChunks();
    }
    generateInitialChunks() {
        for (let i = 0; i < this.chunkCount; i++) {
            this.generateChunk();
        }
    }
    generateChunk() {
        const chunk = {
            meshes: [],
            obstacles: [],
            zPosition: this.currentZ - this.chunkLength * (this.chunks.length + 1)
        };
        const z = chunk.zPosition;
        // Generate canyon walls
        this.createWalls(chunk, z);
        // Generate obstacles based on difficulty
        const state = (0, state_1.getState)();
        const difficulty = Math.min(1, state.score / 10000);
        this.generateObstacles(chunk, z, difficulty);
        this.chunks.push(chunk);
    }
    createWalls(chunk, z) {
        const wallThickness = 5;
        const wallSegments = 10;
        const segmentLength = this.chunkLength / wallSegments;
        // Left and right walls with procedural variation
        for (let i = 0; i < wallSegments; i++) {
            const segmentZ = z - i * segmentLength;
            // Add some procedural variation to wall height and position
            const variation = Math.sin(segmentZ * 0.1) * 5;
            const wallH = this.wallHeight + variation;
            // Left wall
            const leftWallGeo = new THREE.BoxGeometry(wallThickness, wallH, segmentLength + 2);
            const wallMat = new THREE.MeshPhongMaterial({
                color: i % 2 === 0 ? this.colors.wall : this.colors.wallDark,
                flatShading: true
            });
            const leftWall = new THREE.Mesh(leftWallGeo, wallMat);
            leftWall.position.set(-this.canyonWidth / 2, wallH / 2, segmentZ);
            leftWall.castShadow = true;
            leftWall.receiveShadow = true;
            chunk.meshes.push(leftWall);
            // Right wall
            const rightWall = new THREE.Mesh((leftWall.geometry), (leftWall.material));
            rightWall.position.x = this.canyonWidth / 2;
            rightWall.rotation.copy(leftWall.rotation);
            rightWall.position.x = this.canyonWidth / 2;
            chunk.meshes.push(rightWall);
        }
        // Ground plane
        const groundGeo = new THREE.PlaneGeometry(this.canyonWidth + 20, this.chunkLength + 10);
        const groundMat = new THREE.MeshPhongMaterial({
            color: this.colors.ground,
            flatShading: true
        });
        const ground = new THREE.Mesh(groundGeo, groundMat);
        ground.rotation.x = -Math.PI / 2;
        ground.position.z = z - this.chunkLength / 2;
        ground.receiveShadow = true;
        chunk.meshes.push(ground);
    }
    generateObstacles(chunk, z, difficulty) {
        // Number of obstacles increases with difficulty
        const obstacleCount = 2 + Math.floor(difficulty * 5);
        for (let i = 0; i < obstacleCount; i++) {
            const obstacleType = this.randomObstacleType(difficulty);
            const xPos = (Math.random() - 0.5) * (this.canyonWidth - 10);
            const yPos = 2 + Math.random() * 8;
            const zPos = z - (Math.random() * this.chunkLength);
            switch (obstacleType) {
                case 'monolith':
                    this.createMonolith(chunk, xPos, yPos, zPos);
                    break;
                case 'squeeze':
                    this.createSqueeze(chunk, xPos, yPos, zPos);
                    break;
                case 'orb':
                    this.createOrb(chunk, xPos, yPos, zPos);
                    break;
            }
        }
    }
    randomObstacleType(difficulty) {
        const rand = Math.random();
        if (rand < 0.3)
            return 'orb';
        if (rand < 0.6 + difficulty * 0.2)
            return 'monolith';
        return 'squeeze';
    }
    createMonolith(chunk, x, y, z) {
        const width = 2 + Math.random() * 3;
        const depth = 2 + Math.random() * 3;
        const height = 5 + Math.random() * 10;
        const geo = new THREE.BoxGeometry(width, height, depth);
        const mat = new THREE.MeshPhongMaterial({
            color: this.colors.monolith,
            flatShading: true
        });
        const monolith = new THREE.Mesh(geo, mat);
        monolith.position.set(x, y + height / 2, z);
        monolith.castShadow = true;
        monolith.receiveShadow = true;
        const boundingBox = new THREE.Box3().setFromObject(monolith);
        chunk.meshes.push(monolith);
        chunk.obstacles.push({
            mesh: monolith,
            type: 'monolith',
            boundingBox,
            collected: false
        });
    }
    createSqueeze(chunk, x, y, z) {
        // Create a narrow passage with two blocks
        const blockWidth = 1.5;
        const blockHeight = 8;
        const gapSize = 3 + Math.random() * 2;
        const geo = new THREE.BoxGeometry(blockWidth, blockHeight, 4);
        const mat = new THREE.MeshPhongMaterial({
            color: this.colors.monolith,
            flatShading: true
        });
        const leftBlock = new THREE.Mesh(geo, mat);
        leftBlock.position.set(x - gapSize / 2 - blockWidth / 2, y + blockHeight / 2, z);
        leftBlock.castShadow = true;
        const rightBlock = new THREE.Mesh(geo, mat);
        rightBlock.position.set(x + gapSize / 2 + blockWidth / 2, y + blockHeight / 2, z);
        rightBlock.castShadow = true;
        const leftBox = new THREE.Box3().setFromObject(leftBlock);
        const rightBox = new THREE.Box3().setFromObject(rightBlock);
        chunk.meshes.push(leftBlock, rightBlock);
        chunk.obstacles.push({
            mesh: leftBlock,
            type: 'squeeze',
            boundingBox: leftBox,
            collected: false
        });
        chunk.obstacles.push({
            mesh: rightBlock,
            type: 'squeeze',
            boundingBox: rightBox,
            collected: false
        });
    }
    createOrb(chunk, x, y, z) {
        const radius = 0.5;
        const geo = new THREE.SphereGeometry(radius, 16, 16);
        const mat = new THREE.MeshBasicMaterial({
            color: this.colors.orb,
            transparent: true,
            opacity: 0.9
        });
        const orb = new THREE.Mesh(geo, mat);
        orb.position.set(x, y, z);
        const boundingBox = new THREE.Box3(new THREE.Vector3(x - radius, y - radius, z - radius), new THREE.Vector3(x + radius, y + radius, z + radius));
        chunk.meshes.push(orb);
        chunk.obstacles.push({
            mesh: orb,
            type: 'orb',
            boundingBox,
            collected: false
        });
    }
    update(playerZ) {
        // Remove chunks that are behind the player
        while (this.chunks.length > 0 && this.chunks[0].zPosition > playerZ + 100) {
            const oldChunk = this.chunks.shift();
            if (oldChunk) {
                // Clean up meshes
                for (const mesh of oldChunk.meshes) {
                    mesh.geometry.dispose?.();
                    if (Array.isArray(mesh.material)) {
                        for (const mat of mesh.material)
                            mat.dispose();
                    }
                    else {
                        mesh.material.dispose();
                    }
                }
            }
        }
        // Add new chunks as needed
        const lastChunkZ = this.chunks[this.chunks.length - 1]?.zPosition || 0;
        while (playerZ < lastChunkZ + this.chunkLength * 3) {
            this.generateChunk();
        }
    }
    getAllMeshes() {
        const meshes = [];
        for (const chunk of this.chunks) {
            meshes.push(...chunk.meshes);
        }
        return meshes;
    }
    getObstacles() {
        const obstacles = [];
        for (const chunk of this.chunks) {
            obstacles.push(...chunk.obstacles.filter(o => !o.collected));
        }
        return obstacles;
    }
    checkCollision(playerPos, playerRadius = 1) {
        const obstacles = this.getObstacles();
        let closestDistance = Infinity;
        let collidingObstacle;
        for (const obstacle of obstacles) {
            const dist = this.distanceToPoint(obstacle.boundingBox, playerPos);
            if (dist < playerRadius) {
                collidingObstacle = obstacle;
            }
            if (dist < closestDistance) {
                closestDistance = dist;
            }
        }
        return {
            collided: collidingObstacle !== undefined,
            obstacle: collidingObstacle,
            distance: closestDistance
        };
    }
    distanceToPoint(box, point) {
        const closestX = Math.max(box.min.x, Math.min(point.x, box.max.x));
        const closestY = Math.max(box.min.y, Math.min(point.y, box.max.y));
        const closestZ = Math.max(box.min.z, Math.min(point.z, box.max.z));
        const dx = point.x - closestX;
        const dy = point.y - closestY;
        const dz = point.z - closestZ;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }
    checkOrbCollection(playerPos) {
        const obstacles = this.getObstacles();
        for (const obstacle of obstacles) {
            if (obstacle.type === 'orb' && !obstacle.collected) {
                const dist = this.distanceToPoint(obstacle.boundingBox, playerPos);
                if (dist < 1.5) {
                    obstacle.collected = true;
                    return obstacle;
                }
            }
        }
        return null;
    }
    cleanup() {
        for (const chunk of this.chunks) {
            for (const mesh of chunk.meshes) {
                mesh.geometry.dispose?.();
                if (Array.isArray(mesh.material)) {
                    for (const mat of mesh.material)
                        mat.dispose();
                }
                else {
                    mesh.material.dispose();
                }
            }
        }
        this.chunks = [];
    }
}
exports.World = World;
exports.world = new World();
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
// Procedural audio using Web Audio API
Object.defineProperty(exports, "__esModule", { value: true });
exports.audio = exports.AudioSystem = void 0;
class AudioSystem {
    constructor() {
        this.context = null;
        this.engineOscillator = null;
        this.engineGain = null;
        this.isInitialized = false;
        this.baseEngineFreq = 80;
        this.currentSpeedRatio = 1;
        // Will be initialized on first user interaction
    }
    init() {
        if (this.isInitialized)
            return;
        const AudioCtx = (window.AudioContext || window.webkitAudioContext);
        if (!AudioCtx)
            return;
        this.context = new AudioCtx();
        this.createEngineSound();
        this.isInitialized = true;
    }
    createEngineSound() {
        if (!this.context)
            return;
        // Engine drone - low frequency oscillator
        this.engineOscillator = this.context.createOscillator();
        this.engineOscillator.type = 'sawtooth';
        this.engineOscillator.frequency.value = this.baseEngineFreq;
        // Lowpass filter to muffle the harsh sawtooth
        const filter = this.context.createBiquadFilter();
        filter.type = 'lowpass';
        filter.frequency.value = 400;
        // Gain for volume control
        this.engineGain = this.context.createGain();
        this.engineGain.gain.value = 0;
        // Connect nodes
        this.engineOscillator.connect(filter);
        filter.connect(this.engineGain);
        this.engineGain.connect(this.context.destination);
        this.engineOscillator.start();
    }
    setSpeed(speed) {
        if (!this.engineOscillator || !this.context)
            return;
        const targetRatio = Math.max(0.5, Math.min(3, speed / 100));
        this.currentSpeedRatio += (targetRatio - this.currentSpeedRatio) * 0.1;
        // Modulate pitch based on speed
        const targetFreq = this.baseEngineFreq * this.currentSpeedRatio;
        this.engineOscillator.frequency.setTargetAtTime(targetFreq, this.context.currentTime, 0.1);
        // Slight FM modulation for engine texture
        if (this.context) {
            const modFreq = 10 + Math.random() * 20;
            this.engineOscillator.frequency.setValueAtTime(targetFreq + Math.sin(Date.now() * 0.01) * modFreq, this.context.currentTime);
        }
    }
    setEngineVolume(volume) {
        if (!this.engineGain || !this.context)
            return;
        this.engineGain.gain.setTargetAtTime(volume * 0.15, this.context.currentTime, 0.1);
    }
    playCollectible() {
        if (!this.context)
            return;
        const osc = this.context.createOscillator();
        const gain = this.context.createGain();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(880, this.context.currentTime);
        osc.frequency.exponentialRampToValueAtTime(1760, this.context.currentTime + 0.1);
        gain.gain.setValueAtTime(0.3, this.context.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.3);
        osc.connect(gain);
        gain.connect(this.context.destination);
        osc.start();
        osc.stop(this.context.currentTime + 0.3);
    }
    playNearMiss(multiplier) {
        if (!this.context)
            return;
        const osc = this.context.createOscillator();
        const gain = this.context.createGain();
        // Whoosh sound - frequency sweep down
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(200 + multiplier * 50, this.context.currentTime);
        osc.frequency.exponentialRampToValueAtTime(100, this.context.currentTime + 0.2);
        gain.gain.setValueAtTime(0.2 * multiplier, this.context.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.2);
        osc.connect(gain);
        gain.connect(this.context.destination);
        osc.start();
        osc.stop(this.context.currentTime + 0.2);
    }
    playBoost() {
        if (!this.context)
            return;
        const osc = this.context.createOscillator();
        const gain = this.context.createGain();
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(150, this.context.currentTime);
        osc.frequency.linearRampToValueAtTime(300, this.context.currentTime + 0.5);
        gain.gain.setValueAtTime(0.2, this.context.currentTime);
        gain.gain.linearRampToValueAtTime(0.1, this.context.currentTime + 0.5);
        osc.connect(gain);
        gain.connect(this.context.destination);
        osc.start();
        osc.stop(this.context.currentTime + 0.5);
    }
    playCrash() {
        if (!this.context)
            return;
        // Noise burst for crash
        const bufferSize = this.context.sampleRate * 0.5;
        const buffer = this.context.createBuffer(1, bufferSize, this.context.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 noise = this.context.createBufferSource();
        noise.buffer = buffer;
        const gain = this.context.createGain();
        gain.gain.setValueAtTime(0.5, this.context.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.5);
        noise.connect(gain);
        gain.connect(this.context.destination);
        noise.start();
    }
    pause() {
        if (this.context && this.context.state === 'running') {
            this.context.suspend();
        }
    }
    resume() {
        if (this.context && this.context.state === 'suspended') {
            this.context.resume();
        }
    }
    stop() {
        if (this.engineOscillator && this.context) {
            this.engineOscillator.stop();
            this.engineOscillator = null;
        }
        this.setEngineVolume(0);
    }
}
exports.AudioSystem = AudioSystem;
exports.audio = new AudioSystem();
};

// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.particles = exports.ParticleSystem = void 0;
const state_1 = require("./state");
class ParticleSystem {
    constructor() {
        this.dustParticles = [];
        this.debrisParticles = [];
        this.sparkParticles = [];
        this.dustGeometry = null;
        this.debrisGeometry = null;
        this.sparkGeometry = null;
        this.dustPoints = null;
        this.debrisPoints = null;
        this.sparkPoints = null;
        this.maxDustParticles = 200;
        this.maxDebrisParticles = 50;
        this.maxSparkParticles = 30;
        this.initDustSystem();
        this.initDebrisSystem();
        this.initSparkSystem();
    }
    initDustSystem() {
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(this.maxDustParticles * 3);
        const sizes = new Float32Array(this.maxDustParticles);
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
        const material = new THREE.PointsMaterial({
            color: 0x8B7355,
            size: 0.5,
            transparent: true,
            opacity: 0.4,
            blending: THREE.AdditiveBlending || 1
        });
        this.dustGeometry = geometry;
        this.dustPoints = new THREE.Points(geometry, material);
        this.dustPoints.visible = false;
        // Initialize dust particles
        for (let i = 0; i < this.maxDustParticles; i++) {
            this.dustParticles.push({
                position: new THREE.Vector3((Math.random() - 0.5) * 60, Math.random() * 10, -Math.random() * 200),
                velocity: new THREE.Vector3((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 1, Math.random() * 50 + 20),
                life: 1,
                maxLife: 1,
                size: Math.random() * 0.5 + 0.2,
                color: new THREE.Color(0x8B7355)
            });
        }
    }
    initDebrisSystem() {
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(this.maxDebrisParticles * 3);
        const colors = new Float32Array(this.maxDebrisParticles * 3);
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
        const material = new THREE.PointsMaterial({
            size: 0.3,
            vertexColors: true,
            transparent: true,
            opacity: 0.8,
            blending: THREE.AdditiveBlending || 1
        });
        this.debrisGeometry = geometry;
        this.debrisPoints = new THREE.Points(geometry, material);
        this.debrisPoints.visible = false;
    }
    initSparkSystem() {
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(this.maxSparkParticles * 3);
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        const material = new THREE.PointsMaterial({
            color: 0x00ffff,
            size: 0.2,
            transparent: true,
            opacity: 0.9,
            blending: THREE.AdditiveBlending || 1
        });
        this.sparkGeometry = geometry;
        this.sparkPoints = new THREE.Points(geometry, material);
        this.sparkPoints.visible = false;
    }
    update(deltaTime, playerPosition) {
        const state = (0, state_1.getState)();
        const speedRatio = state.speed / 150;
        this.updateDust(deltaTime, playerPosition, speedRatio);
        this.updateDebris(deltaTime, playerPosition, speedRatio);
        this.updateSparks(deltaTime, playerPosition, speedRatio);
    }
    updateDust(deltaTime, playerPos, speedRatio) {
        if (!this.dustGeometry || !this.dustPoints)
            return;
        const positions = this.dustGeometry.attributes.position.array;
        for (let i = 0; i < this.dustParticles.length; i++) {
            const p = this.dustParticles[i];
            // Move particle towards player (simulating forward motion)
            p.position.z += p.velocity.z * deltaTime;
            p.position.x += p.velocity.x * deltaTime;
            p.position.y += p.velocity.y * deltaTime;
            // Reset if behind player
            if (p.position.z > playerPos.z + 10) {
                p.position.z = playerPos.z - 200;
                p.position.x = (Math.random() - 0.5) * 60;
                p.position.y = Math.random() * 10;
            }
            // Position relative to camera view
            const idx = i * 3;
            positions[idx] = p.position.x;
            positions[idx + 1] = p.position.y;
            positions[idx + 2] = p.position.z;
        }
        (this.dustGeometry.attributes.position).needsUpdate = true;
        this.dustPoints.visible = true;
    }
    updateDebris(deltaTime, playerPos, speedRatio) {
        if (!this.debrisGeometry || !this.debrisPoints)
            return;
        // Spawn new debris based on speed
        if (Math.random() < speedRatio * 0.3) {
            this.spawnDebris(playerPos);
        }
        const positions = this.debrisGeometry.attributes.position.array;
        const colors = this.debrisGeometry.attributes.color.array;
        let activeCount = 0;
        for (const p of this.debrisParticles) {
            if (p.life <= 0)
                continue;
            p.life -= deltaTime;
            p.position.add(p.velocity.clone().multiplyScalar(deltaTime));
            const idx = activeCount * 3;
            positions[idx] = p.position.x;
            positions[idx + 1] = p.position.y;
            positions[idx + 2] = p.position.z;
            colors[idx] = p.color.r;
            colors[idx + 1] = p.color.g;
            colors[idx + 2] = p.color.b;
            activeCount++;
        }
        // Update array size based on active particles
        if (activeCount > 0) {
            this.debrisGeometry.setDrawRange?.(0, activeCount);
            (this.debrisGeometry.attributes.position).needsUpdate = true;
            (this.debrisGeometry.attributes.color).needsUpdate = true;
            this.debrisPoints.visible = true;
        }
        else {
            this.debrisPoints.visible = false;
        }
    }
    spawnDebris(playerPos) {
        const pIndex = this.debrisParticles.findIndex(p => p.life <= 0);
        if (pIndex === -1)
            return;
        const p = this.debrisParticles[pIndex];
        p.position.copy(playerPos);
        p.position.x += (Math.random() - 0.5) * 40;
        p.position.y += Math.random() * 15;
        p.position.z -= Math.random() * 5;
        p.velocity.set((Math.random() - 0.5) * 10, (Math.random() - 0.5) * 5, Math.random() * 30 + 20);
        p.life = 1;
        p.color.setHex(Math.random() > 0.5 ? 0x8B4513 : 0x5D2906);
    }
    updateSparks(deltaTime, playerPos, speedRatio) {
        if (!this.sparkGeometry || !this.sparkPoints)
            return;
        // Spawn sparks when boosting or at high speed
        if (speedRatio > 0.8 && Math.random() < 0.5) {
            this.spawnSpark(playerPos);
        }
        const positions = this.sparkGeometry.attributes.position.array;
        let activeCount = 0;
        for (const p of this.sparkParticles) {
            if (p.life <= 0)
                continue;
            p.life -= deltaTime;
            p.position.add(p.velocity.clone().multiplyScalar(deltaTime));
            const idx = activeCount * 3;
            positions[idx] = p.position.x;
            positions[idx + 1] = p.position.y;
            positions[idx + 2] = p.position.z;
            activeCount++;
        }
        if (activeCount > 0) {
            this.sparkGeometry.setDrawRange?.(0, activeCount);
            (this.sparkGeometry.attributes.position).needsUpdate = true;
            this.sparkPoints.visible = true;
        }
        else {
            this.sparkPoints.visible = false;
        }
    }
    spawnSpark(playerPos) {
        const pIndex = this.sparkParticles.findIndex(p => p.life <= 0);
        if (pIndex === -1)
            return;
        const p = this.sparkParticles[pIndex];
        p.position.copy(playerPos);
        p.position.x += (Math.random() - 0.5) * 3;
        p.position.y += (Math.random() - 0.5) * 2;
        p.position.z += 1;
        p.velocity.set((Math.random() - 0.5) * 5, (Math.random() - 0.5) * 3, Math.random() * 20 + 10);
        p.life = 0.3;
    }
    addExplosion(position, count = 10) {
        for (let i = 0; i < count; i++) {
            const pIndex = this.debrisParticles.findIndex(p => p.life <= 0);
            if (pIndex === -1)
                break;
            const p = this.debrisParticles[pIndex];
            p.position.copy(position);
            p.velocity.set((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20);
            p.life = 0.5;
            p.color.setHex(0xff4400);
        }
    }
    getDustPoints() {
        return this.dustPoints;
    }
    getDebrisPoints() {
        return this.debrisPoints;
    }
    getSparkPoints() {
        return this.sparkPoints;
    }
}
exports.ParticleSystem = ParticleSystem;
exports.particles = new ParticleSystem();
};

// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hud = exports.HUD = void 0;
const state_1 = require("./state");
class HUD {
    constructor() {
        this.speedBar = null;
        this.speedText = null;
        this.multiplierValue = null;
        this.energyFill = null;
        this.boostFill = null;
        this.scoreValue = null;
        this.startScreen = null;
        this.gameOverScreen = null;
        this.finalScore = null;
        this.maxSpeedDisplay = null;
        this.restartBtn = null;
        this.startBtn = null;
        this.cacheElements();
        this.setupEventListeners();
    }
    cacheElements() {
        this.speedBar = document.getElementById('speed-bar');
        this.speedText = document.getElementById('speed-text');
        this.multiplierValue = document.getElementById('mult-value');
        this.energyFill = document.getElementById('energy-fill');
        this.boostFill = document.getElementById('boost-fill');
        this.scoreValue = document.getElementById('score-value');
        this.startScreen = document.getElementById('start-screen');
        this.gameOverScreen = document.getElementById('game-over');
        this.finalScore = document.getElementById('final-score');
        this.maxSpeedDisplay = document.getElementById('max-speed-display');
        this.restartBtn = document.getElementById('restart-btn');
        this.startBtn = document.getElementById('start-btn');
    }
    setupEventListeners() {
        if (this.startBtn) {
            this.startBtn.addEventListener('click', () => {
                this.onStartClick();
            });
        }
        if (this.restartBtn) {
            this.restartBtn.addEventListener('click', () => {
                this.onRestartClick();
            });
        }
    }
    onStartClick() {
        const event = new CustomEvent('gameStart');
        window.dispatchEvent(event);
    }
    onRestartClick() {
        const event = new CustomEvent('gameRestart');
        window.dispatchEvent(event);
    }
    update() {
        const state = (0, state_1.getState)();
        // Update speedometer
        if (this.speedBar && this.speedText) {
            const speedPct = Math.min(100, (state.speed / 200) * 100);
            this.speedBar.style.setProperty('--speed-pct', `${speedPct}%`);
            this.speedText.textContent = `${state.speed} km/h`;
        }
        // Update multiplier
        if (this.multiplierValue) {
            const mult = (0, state_1.getMultiplier)();
            this.multiplierValue.textContent = `${mult.toFixed(1)}x`;
        }
        // Update energy bar
        if (this.energyFill) {
            this.energyFill.style.width = `${state.energy}%`;
            // Change color based on energy level
            if (state.energy < 20) {
                this.energyFill.style.background = 'linear-gradient(90deg, #ff0000, #ff6600)';
            }
            else if (state.energy < 50) {
                this.energyFill.style.background = 'linear-gradient(90deg, #ffaa00, #ffff00)';
            }
            else {
                this.energyFill.style.background = 'linear-gradient(90deg, #00d4ff, #00ff88)';
            }
        }
        // Update boost meter
        if (this.boostFill) {
            this.boostFill.style.width = `${state.boostAmount}%`;
            if (state.boostAmount >= 100) {
                this.boostFill.style.boxShadow = '0 0 30px rgba(255, 0, 102, 0.8)';
            }
            else {
                this.boostFill.style.boxShadow = '0 0 15px rgba(255, 0, 102, 0.4)';
            }
        }
        // Update score
        if (this.scoreValue) {
            this.scoreValue.textContent = Math.floor(state.score).toLocaleString();
        }
    }
    pulseMultiplier() {
        if (this.multiplierValue) {
            this.multiplierValue.classList.remove('pulse');
            void this.multiplierValue.offsetWidth; // Trigger reflow
            this.multiplierValue.classList.add('pulse');
        }
    }
    showStartScreen() {
        if (this.startScreen) {
            this.startScreen.classList.remove('hidden');
        }
        if (this.gameOverScreen) {
            this.gameOverScreen.classList.add('hidden');
        }
    }
    hideStartScreen() {
        if (this.startScreen) {
            this.startScreen.classList.add('hidden');
        }
    }
    showGameOver(score, maxSpeed) {
        if (this.gameOverScreen) {
            this.gameOverScreen.classList.remove('hidden');
        }
        if (this.finalScore) {
            this.finalScore.textContent = `Score: ${Math.floor(score).toLocaleString()}`;
        }
        if (this.maxSpeedDisplay) {
            this.maxSpeedDisplay.textContent = `Max Speed: ${maxSpeed} km/h`;
        }
    }
    hideGameOver() {
        if (this.gameOverScreen) {
            this.gameOverScreen.classList.add('hidden');
        }
    }
    setBoostReady(ready) {
        if (this.boostFill) {
            if (ready) {
                this.boostFill.style.boxShadow = '0 0 30px rgba(255, 0, 102, 0.9), 0 0 60px rgba(255, 107, 53, 0.6)';
                this.boostFill.style.animation = 'pulse-glow 0.5s ease-in-out infinite';
            }
            else {
                this.boostFill.style.boxShadow = '0 0 15px rgba(255, 0, 102, 0.4)';
                this.boostFill.style.animation = '';
            }
        }
    }
}
exports.HUD = HUD;
exports.hud = new HUD();
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.5-122b-a10b","provider":"openrouter","persona":"typescript","sessionId":"e82167cf-abec-4609-a233-2ad86c1172db","tokensIn":3000997,"tokensOut":31282,"tokensTotal":3032279,"cost":0.84532578,"turns":79,"toolCalls":81,"failedToolCalls":0,"timestamp":"2026-08-05T05:47:47.832Z"} -->