← Canyon Run results

Canyon Run

Qwen 3.6 27B MTP GGUF · typescript

◈Preview unavailable — try playing the result

Browser check: Preview unavailable or timed out. This is a technical check, not a quality review.

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 64,441 bytes · SHA-256 5f6143d83934
<!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 Arcade</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; }
body { overflow: hidden; background: #000; font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; }
#app { position: relative; width: 100vw; height: 100vh; }
#gameCanvas { display: block; width: 100%; height: 100%; }

#hud {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 10;
}

#multiplier {
  position: absolute; top: 25px; left: 50%; transform: translateX(-50%);
  font-size: 42px; font-weight: 900; color: #ff8c00;
  text-shadow: 0 0 20px rgba(255, 140, 0, 0.6), 0 0 40px rgba(255, 140, 0, 0.3);
  letter-spacing: 4px; transition: transform 0.1s;
  font-variant-numeric: tabular-nums;
}

#energyContainer {
  position: absolute; bottom: 35px; left: 50%; transform: translateX(-50%);
  width: 280px; height: 6px; background: rgba(255,255,255,0.08);
  border-radius: 3px; overflow: hidden;
  box-shadow: 0 0 10px rgba(255, 100, 0, 0.15);
}
#energyBar {
  width: 100%; height: 100%;
  background: linear-gradient(90deg, #ff4444, #ff8c00, #ffcc00);
  border-radius: 3px; transition: width 0.15s, background 0.3s;
  box-shadow: 0 0 10px rgba(255, 140, 0, 0.5);
}

#speedContainer {
  position: absolute; right: 25px; top: 50%; transform: translateY(-50%);
  width: 5px; height: 160px; background: rgba(255,255,255,0.06);
  border-radius: 3px; overflow: hidden;
}
#speedBar {
  position: absolute; bottom: 0; width: 100%; height: 50%;
  background: linear-gradient(to top, #4488ff, #88ccff);
  border-radius: 3px;
  box-shadow: 0 0 8px rgba(68, 136, 255, 0.5);
  transition: height 0.1s;
}

#turboContainer {
  position: absolute; right: 45px; top: 50%; transform: translateY(-50%);
  width: 5px; height: 160px; background: rgba(255,255,255,0.04);
  border-radius: 3px; overflow: hidden;
}
#turboBar {
  position: absolute; bottom: 0; width: 100%; height: 0%;
  background: linear-gradient(to top, #ff2200, #ff6600, #ffcc00);
  border-radius: 3px;
  box-shadow: 0 0 8px rgba(255, 68, 0, 0.5);
  transition: height 0.1s;
}

#startScreen {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: radial-gradient(ellipse at center, rgba(20,10,30,0.95), rgba(5,5,15,0.98));
  z-index: 100; color: #fff;
}
#startScreen h1 {
  font-size: 60px; font-weight: 900; letter-spacing: 8px;
  background: linear-gradient(135deg, #ff8c00, #ff4444, #cc44ff);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text; margin-bottom: 10px;
}
.subtitle { font-size: 16px; color: rgba(255,255,255,0.4); letter-spacing: 4px; margin-bottom: 40px; }
.controls-info { text-align: center; margin-bottom: 40px; }
.controls-info p { font-size: 15px; color: rgba(255,255,255,0.6); margin: 8px 0; letter-spacing: 1px; }
.tip { color: rgba(255, 140, 0, 0.8) !important; font-style: italic; font-size: 13px !important; }

#gameOver {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: rgba(5,5,15,0.92); z-index: 100; color: #fff;
}
#gameOver h1 { font-size: 52px; font-weight: 900; color: #ff4444; letter-spacing: 6px; margin-bottom: 30px; }
#finalScore, #finalSpeed, #finalMaxSpeed {
  font-size: 24px; margin: 8px 0; color: rgba(255,255,255,0.7); letter-spacing: 2px;
}

button#startBtn, button#restartBtn {
  margin-top: 30px; padding: 14px 55px;
  font-size: 18px; font-weight: 900; letter-spacing: 4px;
  border: 2px solid #ff8c00; background: transparent; color: #ff8c00;
  cursor: pointer; pointer-events: all;
  transition: all 0.2s;
}
button#startBtn:hover, button#restartBtn:hover {
  background: #ff8c00; color: #000;
  box-shadow: 0 0 30px rgba(255, 140, 0, 0.4);
}
</style>
</head>
<body>
<div id="app">
  <canvas id="gameCanvas"></canvas>
  <div id="hud">
    <div id="energyContainer">
      <div id="energyBar"></div>
    </div>
    <div id="multiplier">x1</div>
    <div id="speedContainer">
      <div id="speedBar"></div>
    </div>
    <div id="turboContainer">
      <div id="turboBar"></div>
    </div>
  </div>
  <div id="gameOver" style="display:none">
    <h1>GAME OVER</h1>
    <div id="finalScore">0</div>
    <div id="finalSpeed">0</div>
    <div id="finalMaxSpeed">0</div>
    <button id="restartBtn">RESTART</button>
  </div>
  <div id="startScreen">
    <h1>CANYON RACER</h1>
    <p class="subtitle">High-Speed Procedural Arcade</p>
    <div class="controls-info">
      <p>W/S — Pitch Up/Down</p>
      <p>A/D — Roll Left/Right</p>
      <p>SPACE — Boost</p>
      <p class="tip">Stay close to walls for near-miss multipliers!</p>
      <p class="tip">Collect orbs to keep energy!</p>
    </div>
    <button id="startBtn">LAUNCH</button>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./gameState":"src/gameState.ts","./input":"src/input.ts","./player":"src/player.ts","./world":"src/world.ts","./audio":"src/audio.ts","./particles":"src/particles.ts","./collision":"src/collision.ts","./hud":"src/hud.ts","./renderer":"src/renderer.ts"},"src/player.ts":{"./gameState":"src/gameState.ts","./input":"src/input.ts"},"src/world.ts":{"./gameState":"src/gameState.ts"},"src/particles.ts":{"./gameState":"src/gameState.ts"},"src/collision.ts":{"./gameState":"src/gameState.ts","./player":"src/player.ts","./world":"src/world.ts","./audio":"src/audio.ts"},"src/hud.ts":{"./gameState":"src/gameState.ts"},"src/renderer.ts":{"./gameState":"src/gameState.ts","./player":"src/player.ts","./world":"src/world.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 gameState_1 = require("./gameState");
const input_1 = require("./input");
const player_1 = require("./player");
const world_1 = require("./world");
const audio_1 = require("./audio");
const particles_1 = require("./particles");
const collision_1 = require("./collision");
const hud_1 = require("./hud");
const renderer_1 = require("./renderer");
// Game state
let state = (0, gameState_1.createInitialState)();
let input = (0, input_1.createInputState)();
let player = (0, player_1.createPlayerState)();
let chunks = new Map();
let audio = new audio_1.AudioEngine();
let particles = null;
let renderer = null;
// Timing
let lastTime = 0;
let deltaTime = 0;
let gameStartTime = 0;
let spawnTimer = 0;
let debrisTimer = 0;
let trailCounter = 0;
const canvas = document.getElementById('gameCanvas');
function init() {
    // Setup input
    (0, input_1.setupInput)(input);
    // Create renderer
    renderer = new renderer_1.GameRenderer(canvas);
    // Create particles
    particles = new particles_1.ParticleSystem(renderer.getScene());
    // Generate initial chunks
    for (let i = -2; i < gameState_1.CONFIG.chunksAhead; i++) {
        const z = i * gameState_1.CONFIG.chunkLength;
        const chunk = (0, world_1.generateChunk)(i, 1, z);
        chunks.set(i, chunk);
        renderer.getScene().add(chunk.group);
    }
    // Start screen
    (0, hud_1.showStartScreen)(true);
    // Handle resize
    window.addEventListener('resize', () => {
        if (renderer)
            renderer.resize(window.innerWidth, window.innerHeight);
    });
    renderer.resize(window.innerWidth, window.innerHeight);
    // Start button
    const startBtn = document.getElementById('startBtn');
    startBtn.addEventListener('click', startGame);
    // Restart button
    const restartBtn = document.getElementById('restartBtn');
    restartBtn.addEventListener('click', restartGame);
    // Start render loop
    lastTime = performance.now();
    requestAnimationFrame(gameLoop);
}
function startGame() {
    (0, hud_1.showStartScreen)(false);
    audio.init();
    audio.resume();
    audio.startEngine();
    state.running = true;
    gameStartTime = performance.now() / 1000;
}
function restartGame() {
    // Clean up old chunks
    chunks.forEach(chunk => {
        renderer?.getScene().remove(chunk.group);
        (0, world_1.disposeChunk)(chunk);
    });
    chunks.clear();
    // Reset state
    state = (0, gameState_1.createInitialState)();
    player = (0, player_1.createPlayerState)();
    spawnTimer = 0;
    debrisTimer = 0;
    trailCounter = 0;
    // Re-generate chunks
    for (let i = -2; i < gameState_1.CONFIG.chunksAhead; i++) {
        const z = i * gameState_1.CONFIG.chunkLength;
        const chunk = (0, world_1.generateChunk)(i, 1, z);
        chunks.set(i, chunk);
        renderer.getScene().add(chunk.group);
    }
    (0, hud_1.hideGameOver)();
    audio.resume();
    audio.startEngine();
    state.running = true;
    gameStartTime = performance.now() / 1000;
}
function gameLoop(timestamp) {
    requestAnimationFrame(gameLoop);
    deltaTime = Math.min((timestamp - lastTime) / 1000, 0.05); // cap dt
    lastTime = timestamp;
    if (!renderer || !particles)
        return;
    const time = timestamp / 1000;
    if (state.running) {
        update(time, timestamp);
    }
    // Render every frame
    renderer.updateFov(state, time);
    if (state.running) {
        renderer.updateCamera(state, time);
        renderer.animateOrbs(chunks, time);
        renderer.updateSunPosition(state.distance);
    }
    renderer.render();
    (0, hud_1.updateHUD)(state);
    // Game over handling
    if (state.gameOver) {
        (0, hud_1.showGameOver)(state);
    }
}
function update(time, timestamp) {
    // === Difficulty scaling ===
    state.difficulty = Math.min(gameState_1.CONFIG.maxDifficulty, 1 + state.score * gameState_1.CONFIG.speedScale);
    // === Speed ===
    let targetSpeed = gameState_1.CONFIG.playerSpeed + (state.difficulty - 1) * 0.08;
    // === Turbo/Boost ===
    if (state.boosting && state.turbo > 0) {
        state.turbo -= gameState_1.CONFIG.turboDrain * deltaTime * 60;
        if (state.turbo <= 0) {
            state.turbo = 0;
            state.boosting = false;
        }
        targetSpeed += gameState_1.CONFIG.turboBoost;
    }
    else {
        // Auto-charge turbo
        state.turbo = Math.min(gameState_1.CONFIG.turboMax, state.turbo + gameState_1.CONFIG.turboChargeRate * deltaTime * 60);
        // Allow manual boost
        if (input.boost && state.turbo >= 20) {
            state.boosting = true;
            audio.playBoost();
        }
    }
    // Smooth speed transition
    state.speed += (targetSpeed - state.speed) * 0.05;
    state.maxSpeed = Math.max(state.maxSpeed, state.speed);
    // === Player movement ===
    (0, player_1.updatePlayer)(player, input, deltaTime, state.speed);
    // === Distance tracking ===
    state.distance += state.speed * deltaTime * 60;
    // === Score ===
    state.score += state.speed * deltaTime * 10 * state.multiplier;
    // === Energy decay ===
    state.energy -= gameState_1.CONFIG.energyDecay * deltaTime * 60;
    if (state.energy <= 0) {
        state.energy = 0;
        // Gradual slowdown
        targetSpeed *= 0.95;
    }
    // === Near-miss timer ===
    if (state.nearMissTimer > 0) {
        state.nearMissTimer -= deltaTime;
        if (state.nearMissTimer <= 0) {
            state.nearMissActive = false;
            // Decay multiplier
            state.multiplier = Math.max(1, state.multiplier - 1);
        }
    }
    // === Camera shake decay ===
    if (state.cameraShake > 0) {
        state.cameraShake *= 0.95;
        if (state.cameraShake < 0.001)
            state.cameraShake = 0;
    }
    // === Update player visual ===
    renderer.updatePlayerVisuals(player, state.boosting, time);
    // === Particles ===
    if (particles) {
        trailCounter++;
        if (trailCounter % 2 === 0) {
            particles.addTrailPoint(0, player.x - 0.5, player.y, state.distance + 2);
            particles.addTrailPoint(1, player.x + 0.5, player.y, state.distance + 2);
        }
        // Speed lines / debris
        debrisTimer += deltaTime;
        if (debrisTimer > 0.02 / state.speed) {
            debrisTimer = 0;
            particles.spawnDebris(player.x + (Math.random() - 0.5) * 40, player.y + (Math.random() - 0.5) * 30, state.distance - 10 - Math.random() * 20);
        }
        particles.update(deltaTime, state.speed, state.boosting);
    }
    // === Chunk management ===
    const currentChunkIdx = Math.floor(state.distance / gameState_1.CONFIG.chunkLength);
    // Generate new chunks ahead
    let maxChunkIdx = chunks.size > 0 ? Math.max(...Array.from(chunks.keys())) : -1;
    while (maxChunkIdx < currentChunkIdx + gameState_1.CONFIG.chunksAhead) {
        maxChunkIdx++;
        const z = maxChunkIdx * gameState_1.CONFIG.chunkLength;
        const chunk = (0, world_1.generateChunk)(maxChunkIdx, state.difficulty, z);
        chunks.set(maxChunkIdx, chunk);
        renderer.getScene().add(chunk.group);
    }
    // Remove old chunks behind
    chunks.forEach((chunk, idx) => {
        if (idx < currentChunkIdx - gameState_1.CONFIG.chunksBehind) {
            renderer.getScene().remove(chunk.group);
            (0, world_1.disposeChunk)(chunk);
            chunks.delete(idx);
        }
    });
    // === Move chunks relative to player ===
    // Instead of moving the player forward, we keep player at z=0
    // and move all chunks backward
    // Actually, let's track player distance and position chunks absolutely
    // then move camera with player
    chunks.forEach(chunk => {
        const worldZ = state.distance - (chunk.chunkIndex * gameState_1.CONFIG.chunkLength);
        chunk.group.position.z = worldZ;
    });
    // Position camera
    const cam = renderer.getCamera();
    cam.position.z = state.distance + 15;
    // Update sun position to follow player
    renderer.updateSunPosition(state.distance);
    // Cycle dust particles
    const dustPos = renderer.getScene().children.find(c => c.userData && c.userData.isDust);
    if (dustPos) {
        dustPos.position.z = state.distance;
        // Slight drift
        const dg = dustPos.geometry;
        const d = dg.attributes.position.array;
        for (let i = 0; i < d.length; i += 3) {
            d[i + 2] += 0.3; // move toward player
            if (d[i + 2] > 20)
                d[i + 2] -= 420; // recycle
            d[i] += Math.sin(time + i) * 0.02; // gentle drift
        }
        dg.attributes.position.needsUpdate = true;
    }
    // === Collision detection ===
    (0, collision_1.checkCollisions)(state, player, chunks, state.distance, audio);
    // === Audio update ===
    audio.updateEngine(state.speed / gameState_1.CONFIG.boostSpeed, state.boosting);
}
// Start the game
init();
};

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

// ── module: src/gameState.ts ──
__mods["src/gameState.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONFIG = void 0;
exports.createInitialState = createInitialState;
// Game configuration and state
exports.CONFIG = {
    // Player
    playerSpeed: 0.25,
    playerMaxSpeed: 0.55,
    boostSpeed: 0.95,
    turnRate: 0.05,
    bankAmount: 0.7,
    pitchAmount: 0.035,
    energyDecay: 0.02,
    energyMax: 100,
    energyRecharge: 30,
    turboChargeRate: 0.08,
    turboMax: 100,
    turboDrain: 0.5,
    turboBoost: 0.4,
    // World
    chunkLength: 200,
    chunksAhead: 8,
    chunksBehind: 2,
    canyonWidth: 80,
    canyonHeight: 50,
    wallHeight: 35,
    // Collision
    playerRadius: 1.5,
    nearMissDistance: 8,
    nearMissDuration: 1.5,
    // Difficulty
    speedScale: 0.0001, // speed increase per score point
    maxDifficulty: 3,
    // Visual
    fogNear: 50,
    fogFar: 300,
    baseFov: 75,
    boostFov: 105,
};
function createInitialState() {
    return {
        running: false,
        score: 0,
        distance: 0,
        speed: exports.CONFIG.playerSpeed,
        maxSpeed: 0,
        multiplier: 1,
        energy: exports.CONFIG.energyMax,
        turbo: 0,
        boosting: false,
        nearMissTimer: 0,
        nearMissActive: false,
        lastNearMissTime: 0,
        difficulty: 1,
        gameOver: false,
        cameraShake: 0,
        trailTimer: 0,
    };
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInputState = createInputState;
exports.setupInput = setupInput;
function createInputState() {
    return { up: false, down: false, left: false, right: false, boost: false };
}
function setupInput(input) {
    const keyMap = {
        'w': 'up', 'W': 'up', 'ArrowUp': 'up',
        's': 'down', 'S': 'down', 'ArrowDown': 'down',
        'a': 'left', 'A': 'left', 'ArrowLeft': 'left',
        'd': 'right', 'D': 'right', 'ArrowRight': 'right',
        ' ': 'boost',
    };
    window.addEventListener('keydown', (e) => {
        if (keyMap[e.key] !== undefined) {
            input[keyMap[e.key]] = true;
            e.preventDefault();
        }
    });
    window.addEventListener('keyup', (e) => {
        if (keyMap[e.key] !== undefined) {
            input[keyMap[e.key]] = false;
            e.preventDefault();
        }
    });
}
};

// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayerState = createPlayerState;
exports.updatePlayer = updatePlayer;
const gameState_1 = require("./gameState");
function createPlayerState() {
    return { x: 0, y: 0, vx: 0, vy: 0, bankAngle: 0, pitchAngle: 0 };
}
function updatePlayer(player, input, dt, speed) {
    const turnRate = gameState_1.CONFIG.turnRate * dt * 60;
    const bankLerp = 0.08 * dt * 60;
    const pitchLerp = 0.06 * dt * 60;
    const damping = 0.92;
    const maxLateralSpeed = gameState_1.CONFIG.playerMaxSpeed;
    // Target angles based on input
    let targetBank = 0;
    let targetPitch = 0;
    if (input.left)
        targetBank = gameState_1.CONFIG.bankAmount;
    if (input.right)
        targetBank = -gameState_1.CONFIG.bankAmount;
    if (input.up)
        targetPitch = gameState_1.CONFIG.pitchAmount;
    if (input.down)
        targetPitch = -gameState_1.CONFIG.pitchAmount;
    // Smooth bank and pitch
    player.bankAngle += (targetBank - player.bankAngle) * bankLerp * 3;
    player.pitchAngle += (targetPitch - player.pitchAngle) * pitchLerp * 3;
    // Apply lateral forces with momentum
    if (input.left)
        player.vx += turnRate * speed * 20;
    if (input.right)
        player.vx -= turnRate * speed * 20;
    if (input.up)
        player.vy += turnRate * speed * 15;
    if (input.down)
        player.vy -= turnRate * speed * 15;
    // Damping (weighty feel)
    player.vx *= damping;
    player.vy *= damping;
    // Clamp
    player.vx = THREE.MathUtils.clamp(player.vx, -maxLateralSpeed, maxLateralSpeed);
    player.vy = THREE.MathUtils.clamp(player.vy, -maxLateralSpeed * 0.7, maxLateralSpeed * 0.7);
    // Update position
    player.x += player.vx * dt * 60;
    player.y += player.vy * dt * 60;
    // Bounds
    const halfWidth = gameState_1.CONFIG.canyonWidth * 0.4;
    const halfHeight = gameState_1.CONFIG.canyonHeight * 0.4;
    player.x = THREE.MathUtils.clamp(player.x, -halfWidth, halfWidth);
    player.y = THREE.MathUtils.clamp(player.y, -halfHeight, halfHeight);
}
};

// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateChunk = generateChunk;
exports.disposeChunk = disposeChunk;
const gameState_1 = require("./gameState");
function seededRandom(seed) {
    let s = Math.abs(seed) || 1;
    return () => {
        s = (s * 16807) % 2147483647;
        return (s - 1) / 2147483646;
    };
}
function generateChunk(chunkIndex, difficulty, globalZ) {
    const rand = seededRandom(chunkIndex * 7919 + 42);
    const group = new THREE.Group();
    group.position.z = globalZ;
    const chunkLen = gameState_1.CONFIG.chunkLength;
    const canyonW = gameState_1.CONFIG.canyonWidth;
    const canyonH = gameState_1.CONFIG.canyonHeight;
    const wallH = gameState_1.CONFIG.wallHeight;
    const narrowFactor = Math.max(0.3, 1 - (difficulty - 1) * 0.15);
    const effectiveWidth = canyonW * narrowFactor;
    // === Canyon walls - cyber sunset palette ===
    const sunsetColors = [
        new THREE.Color(0.65, 0.30, 0.12), // burnt orange
        new THREE.Color(0.50, 0.22, 0.10), // deep rust
        new THREE.Color(0.70, 0.35, 0.15), // golden orange
        new THREE.Color(0.45, 0.20, 0.12), // warm brown
        new THREE.Color(0.55, 0.25, 0.18), // terracotta
        new THREE.Color(0.40, 0.18, 0.20), // purple-brown (shadowed)
    ];
    const shadowColors = [
        new THREE.Color(0.25, 0.12, 0.10), // deep shadow
        new THREE.Color(0.20, 0.10, 0.15), // purple shadow
        new THREE.Color(0.22, 0.10, 0.08), // dark brown
    ];
    const wallSegs = 6;
    const segLen = chunkLen / wallSegs;
    for (let si = 0; si < wallSegs; si++) {
        const zOff = si * segLen - chunkLen / 2 + segLen / 2;
        const hVar = wallH * (0.8 + rand() * 0.5);
        const thick = 5 + rand() * 8;
        const xOffset = rand() * 3 - 1.5;
        const yVar = rand() * 4 - 2;
        const ci = Math.floor(rand() * sunsetColors.length);
        const si_idx = Math.floor(rand() * shadowColors.length);
        const matL = new THREE.MeshStandardMaterial({
            flatShading: true, roughness: 0.9, metalness: 0.1,
            color: sunsetColors[ci].clone(),
        });
        const lGeo = new THREE.BoxGeometry(thick, hVar, segLen + 0.5);
        const lMesh = new THREE.Mesh(lGeo, matL);
        lMesh.position.set(-effectiveWidth / 2 + 1 + xOffset, -wallH / 2 + hVar / 2 + yVar, zOff);
        group.add(lMesh);
        const matR = new THREE.MeshStandardMaterial({
            flatShading: true, roughness: 0.9, metalness: 0.1,
            color: shadowColors[si_idx].clone(),
        });
        const rGeo = new THREE.BoxGeometry(thick, hVar, segLen + 0.5);
        const rMesh = new THREE.Mesh(rGeo, matR);
        rMesh.position.set(effectiveWidth / 2 - 1 - xOffset, -wallH / 2 + hVar / 2 + yVar, zOff);
        group.add(rMesh);
    }
    const wallLeft = group.children[0];
    const wallRight = group.children[1];
    // === Obstacles ===
    const obstacles = [];
    // Safe zone: first 4 chunks have no obstacles (player starts there)
    if (chunkIndex >= 4) {
        const numObs = Math.floor(rand() * 3) + Math.floor(difficulty);
        const maxObs = Math.min(numObs, 8);
        for (let i = 0; i < maxObs; i++) {
            const zSpacing = chunkLen / (maxObs + 1);
            const adjustedZ = zSpacing * (i + 0.5) + (rand() - 0.5) * 15;
            if (rand() < 0.5) {
                // Floating monolith
                const monoW = 4 + rand() * 6;
                const monoH = 10 + rand() * 15;
                const x = (rand() - 0.5) * (effectiveWidth - monoW - 10);
                const y = (rand() - 0.5) * canyonH * 0.6;
                const geo = new THREE.BoxGeometry(monoW, monoH, 3);
                const mat = new THREE.MeshStandardMaterial({
                    flatShading: true,
                    color: new THREE.Color(0.15, 0.1, 0.2 + rand() * 0.15),
                    roughness: 0.7,
                    metalness: 0.3,
                });
                const mesh = new THREE.Mesh(geo, mat);
                mesh.position.set(x, y, adjustedZ);
                group.add(mesh);
                obstacles.push({
                    mesh, type: 'monolith',
                    x, y, z: adjustedZ,
                    width: monoW, height: monoH, depth: 3,
                    active: true,
                });
            }
            else {
                // Squeeze wall
                const gapW = Math.max(6, 18 - difficulty * 3);
                const gapH = Math.max(5, 12 - difficulty * 1.5);
                const gapX = (rand() - 0.5) * (effectiveWidth - gapW - 10);
                const gapY = (rand() - 0.5) * canyonH * 0.5;
                const barrierD = 2;
                const mat = new THREE.MeshStandardMaterial({
                    flatShading: true,
                    color: new THREE.Color(0.2, 0.12, 0.25),
                    roughness: 0.8,
                    metalness: 0.2,
                });
                const leftW = (effectiveWidth + gapW) / 2 - gapX;
                if (leftW > 1) {
                    const g = new THREE.BoxGeometry(leftW, canyonH * 0.8, barrierD);
                    const m = new THREE.Mesh(g, mat);
                    m.position.set(gapX - gapW / 2 - leftW / 2, gapY + canyonH * 0.4, adjustedZ);
                    group.add(m);
                }
                const rightW = (effectiveWidth + gapW) / 2 + gapX;
                if (rightW > 1) {
                    const g = new THREE.BoxGeometry(rightW, canyonH * 0.8, barrierD);
                    const m = new THREE.Mesh(g, mat);
                    m.position.set(gapX + gapW / 2 + rightW / 2, gapY + canyonH * 0.4, adjustedZ);
                    group.add(m);
                }
                const aboveH = (canyonH * 0.8 - gapH) / 2;
                if (aboveH > 0.5) {
                    const g = new THREE.BoxGeometry(gapW, aboveH, barrierD);
                    const m = new THREE.Mesh(g, mat);
                    m.position.set(gapX, gapY + canyonH * 0.4 + gapH / 2 + aboveH / 2, adjustedZ);
                    group.add(m);
                }
                if (aboveH > 0.5) {
                    const g = new THREE.BoxGeometry(gapW, aboveH, barrierD);
                    const m = new THREE.Mesh(g, mat);
                    m.position.set(gapX, gapY + canyonH * 0.4 - gapH / 2 - aboveH / 2, adjustedZ);
                    group.add(m);
                }
                const dummyGeo = new THREE.BoxGeometry(effectiveWidth, canyonH * 0.8, barrierD);
                const dummyMat = new THREE.MeshBasicMaterial({ visible: false });
                const dummyMesh = new THREE.Mesh(dummyGeo, dummyMat);
                dummyMesh.position.set(gapX, gapY + canyonH * 0.4, adjustedZ);
                obstacles.push({
                    mesh: dummyMesh, type: 'squeeze',
                    x: gapX, y: gapY + canyonH * 0.4, z: adjustedZ,
                    width: effectiveWidth, height: canyonH * 0.8, depth: barrierD,
                    active: true,
                    gapInfo: { gapX, gapY: gapY + canyonH * 0.4, gapW, gapH },
                });
            }
        }
    }
    // === Energy orbs ===
    const orbs = [];
    const numOrbs = Math.floor(rand() * 3) + 2;
    for (let i = 0; i < numOrbs; i++) {
        const z = rand() * chunkLen;
        let x = (rand() - 0.5) * effectiveWidth * 0.7;
        let y = (rand() - 0.5) * canyonH * 0.5;
        if (obstacles.length > 0 && rand() < 0.4) {
            const obs = obstacles[Math.floor(rand() * obstacles.length)];
            x = obs.x + (rand() - 0.5) * obs.width * 0.6;
            y = obs.y + (rand() - 0.5) * obs.height * 0.6;
        }
        const geo = new THREE.IcosahedronGeometry(1.2, 0);
        const mat = new THREE.MeshPhongMaterial({
            color: new THREE.Color(1.0, 0.6, 0.1),
            emissive: new THREE.Color(1.0, 0.4, 0.0),
            emissiveIntensity: 2,
            flatShading: true,
        });
        const mesh = new THREE.Mesh(geo, mat);
        mesh.position.set(x, y, z);
        group.add(mesh);
        orbs.push({ mesh, x, y, z, collected: false, phase: rand() * Math.PI * 2 });
    }
    return {
        chunkIndex,
        zPosition: globalZ,
        group,
        wallLeft,
        wallRight,
        obstacles,
        orbs,
        removed: false,
    };
}
function disposeChunk(chunk) {
    if (chunk.removed)
        return;
    chunk.removed = true;
    chunk.group.children.forEach(child => {
        if (child instanceof THREE.Mesh) {
            child.geometry.dispose();
            if (Array.isArray(child.material)) {
                child.material.forEach(m => m.dispose());
            }
            else {
                child.material.dispose();
            }
        }
    });
    chunk.group = new THREE.Group();
}
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioEngine = void 0;
// Procedural sound synthesis using Web Audio API
class AudioEngine {
    constructor() {
        this.ctx = null;
        this.masterGain = null;
        this.engineOsc = null;
        this.engineGain = null;
        this.engineFilter = null;
    }
    init() {
        try {
            this.ctx = new AudioContext();
            this.masterGain = this.ctx.createGain();
            this.masterGain.gain.value = 0.3;
            this.masterGain.connect(this.ctx.destination);
        }
        catch {
            console.warn('Web Audio not available');
        }
    }
    resume() {
        if (this.ctx && this.ctx.state === 'suspended') {
            this.ctx.resume();
        }
    }
    startEngine() {
        if (!this.ctx || !this.masterGain)
            return;
        // Low engine hum - layered oscillators
        this.engineOsc = this.ctx.createOscillator();
        this.engineOsc.type = 'sawtooth';
        this.engineOsc.frequency.value = 55;
        this.engineFilter = this.ctx.createBiquadFilter();
        this.engineFilter.type = 'lowpass';
        this.engineFilter.frequency.value = 200;
        this.engineFilter.Q.value = 2;
        this.engineGain = this.ctx.createGain();
        this.engineGain.gain.value = 0.12;
        this.engineOsc.connect(this.engineFilter);
        this.engineFilter.connect(this.engineGain);
        this.engineGain.connect(this.masterGain);
        this.engineOsc.start();
        // Second harmonic
        const osc2 = this.ctx.createOscillator();
        osc2.type = 'sine';
        osc2.frequency.value = 110;
        const g2 = this.ctx.createGain();
        g2.gain.value = 0.05;
        osc2.connect(g2);
        g2.connect(this.masterGain);
        osc2.start();
        this.engineOsc = this.engineOsc; // primary ref kept
    }
    updateEngine(speed, boosting) {
        if (!this.ctx || !this.engineOsc || !this.engineFilter || !this.engineGain)
            return;
        const t = this.ctx.currentTime;
        const freq = 45 + speed * 200;
        const cutoff = boosting ? 800 : 150 + speed * 300;
        const vol = boosting ? 0.2 : 0.08 + speed * 0.06;
        this.engineOsc.frequency.setTargetAtTime(freq, t, 0.05);
        this.engineFilter.frequency.setTargetAtTime(cutoff, t, 0.05);
        this.engineGain.gain.setTargetAtTime(vol, t, 0.05);
    }
    stopEngine() {
        if (this.engineOsc) {
            try {
                this.engineOsc.stop();
            }
            catch { }
            this.engineOsc = null;
        }
    }
    playCollect() {
        if (!this.ctx || !this.masterGain)
            return;
        const t = this.ctx.currentTime;
        const osc = this.ctx.createOscillator();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(800, t);
        osc.frequency.exponentialRampToValueAtTime(1600, t + 0.1);
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.3, t);
        gain.gain.exponentialRampToValueAtTime(0.001, t + 0.2);
        osc.connect(gain);
        gain.connect(this.masterGain);
        osc.start(t);
        osc.stop(t + 0.2);
    }
    playNearMiss() {
        if (!this.ctx || !this.masterGain)
            return;
        const t = this.ctx.currentTime;
        // Whoosh sound
        const bufferSize = this.ctx.sampleRate * 0.3;
        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) * (1 - i / bufferSize);
        }
        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'bandpass';
        filter.frequency.setValueAtTime(2000, t);
        filter.frequency.exponentialRampToValueAtTime(400, t + 0.3);
        filter.Q.value = 3;
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.25, t);
        gain.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
        noise.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        noise.start(t);
        noise.stop(t + 0.3);
    }
    playCrash() {
        if (!this.ctx || !this.masterGain)
            return;
        const t = this.ctx.currentTime;
        // Noise burst
        const bufferSize = this.ctx.sampleRate * 0.5;
        const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) {
            data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2);
        }
        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'lowpass';
        filter.frequency.value = 1500;
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.5, t);
        gain.gain.exponentialRampToValueAtTime(0.001, t + 0.5);
        noise.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        noise.start(t);
        noise.stop(t + 0.5);
        // Low impact thud
        const osc = this.ctx.createOscillator();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(80, t);
        osc.frequency.exponentialRampToValueAtTime(30, t + 0.3);
        const oscGain = this.ctx.createGain();
        oscGain.gain.setValueAtTime(0.4, t);
        oscGain.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
        osc.connect(oscGain);
        oscGain.connect(this.masterGain);
        osc.start(t);
        osc.stop(t + 0.3);
    }
    playBoost() {
        if (!this.ctx || !this.masterGain)
            return;
        const t = this.ctx.currentTime;
        const osc = this.ctx.createOscillator();
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(300, t);
        osc.frequency.exponentialRampToValueAtTime(1200, t + 0.15);
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.15, t);
        gain.gain.exponentialRampToValueAtTime(0.001, t + 0.2);
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'highpass';
        filter.frequency.value = 500;
        osc.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        osc.start(t);
        osc.stop(t + 0.2);
    }
    destroy() {
        this.stopEngine();
        if (this.ctx) {
            this.ctx.close();
        }
    }
}
exports.AudioEngine = AudioEngine;
};

// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParticleSystem = void 0;
// Particle system for trails and debris
class ParticleSystem {
    constructor(scene) {
        this.trailMax = 1200;
        this.debrisMax = 400;
        // Trail particles
        this.trailPositions = new Float32Array(this.trailMax * 3);
        this.trailOpacities = new Float32Array(this.trailMax);
        this.trailHeads = [0, 0]; // two streams (left and right engine)
        this.trailGeo = new THREE.BufferGeometry();
        this.trailGeo.setAttribute('position', new THREE.BufferAttribute(this.trailPositions, 3));
        this.trailMat = new THREE.PointsMaterial({
            color: new THREE.Color(1, 0.5, 0.1),
            size: 0.8,
            transparent: true,
            opacity: 0.7,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            sizeAttenuation: true,
        });
        this.trailPoints = new THREE.Points(this.trailGeo, this.trailMat);
        scene.add(this.trailPoints);
        // Debris particles (speed lines past camera)
        this.debrisPositions = new Float32Array(this.debrisMax * 3);
        this.debrisVelocities = new Array(this.debrisMax * 3).fill(0);
        this.debrisLifetimes = new Array(this.debrisMax).fill(0);
        this.debrisGeo = new THREE.BufferGeometry();
        this.debrisGeo.setAttribute('position', new THREE.BufferAttribute(this.debrisPositions, 3));
        this.debrisMat = new THREE.PointsMaterial({
            color: new THREE.Color(0.8, 0.7, 0.5),
            size: 0.3,
            transparent: true,
            opacity: 0.5,
            depthWrite: false,
            sizeAttenuation: true,
        });
        this.debrisPoints = new THREE.Points(this.debrisGeo, this.debrisMat);
        scene.add(this.debrisPoints);
        // Init debris positions offscreen
        for (let i = 0; i < this.debrisMax; i++) {
            this.debrisPositions[i * 3] = 0;
            this.debrisPositions[i * 3 + 1] = 0;
            this.debrisPositions[i * 3 + 2] = -9999;
            this.debrisLifetimes[i] = 0;
        }
    }
    addTrailPoint(stream, x, y, z) {
        const head = this.trailHeads[stream];
        const idx = head;
        this.trailPositions[idx * 3] = x;
        this.trailPositions[idx * 3 + 1] = y;
        this.trailPositions[idx * 3 + 2] = z;
        this.trailHeads[stream] = (head + 1) % this.trailMax;
    }
    spawnDebris(x, y, z) {
        for (let i = 0; i < this.debrisMax; i++) {
            if (this.debrisLifetimes[i] <= 0) {
                this.debrisPositions[i * 3] = x + (Math.random() - 0.5) * 5;
                this.debrisPositions[i * 3 + 1] = y + (Math.random() - 0.5) * 5;
                this.debrisPositions[i * 3 + 2] = z;
                this.debrisVelocities[i * 3] = (Math.random() - 0.5) * 0.3;
                this.debrisVelocities[i * 3 + 1] = (Math.random() - 0.5) * 0.3;
                this.debrisVelocities[i * 3 + 2] = -Math.random() * 2 - 1;
                this.debrisLifetimes[i] = 1.0;
                return;
            }
        }
    }
    update(dt, speed, boosting) {
        // Decay debris
        for (let i = 0; i < this.debrisMax; i++) {
            if (this.debrisLifetimes[i] > 0) {
                this.debrisLifetimes[i] -= dt * 2;
                this.debrisPositions[i * 3] += this.debrisVelocities[i * 3];
                this.debrisPositions[i * 3 + 1] += this.debrisVelocities[i * 3 + 1];
                this.debrisPositions[i * 3 + 2] += this.debrisVelocities[i * 3 + 2];
                if (this.debrisLifetimes[i] <= 0) {
                    this.debrisPositions[i * 3 + 2] = -9999;
                }
            }
        }
        this.trailGeo.attributes.position.needsUpdate = true;
        this.debrisGeo.attributes.position.needsUpdate = true;
        // Update trail color based on boost
        if (boosting) {
            this.trailMat.color.setRGB(1, 0.2, 0.1);
            this.trailMat.size = 1.2;
            this.trailMat.opacity = 0.9;
        }
        else {
            this.trailMat.color.setRGB(1, 0.5, 0.1);
            this.trailMat.size = 0.8;
            this.trailMat.opacity = 0.7;
        }
    }
    destroy() {
        this.trailPoints.geometry.dispose();
        this.trailMat.dispose();
        this.debrisPoints.geometry.dispose();
        this.debrisMat.dispose();
    }
}
exports.ParticleSystem = ParticleSystem;
};

// ── module: src/collision.ts ──
__mods["src/collision.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkCollisions = checkCollisions;
const gameState_1 = require("./gameState");
function checkCollisions(state, player, chunks, playerZ, audio) {
    const pRadius = gameState_1.CONFIG.playerRadius;
    const nearMissDist = gameState_1.CONFIG.nearMissDistance;
    // Check nearby chunks
    const currentChunkIdx = Math.floor(playerZ / gameState_1.CONFIG.chunkLength);
    for (let ci = currentChunkIdx - 1; ci <= currentChunkIdx + 2; ci++) {
        const chunk = chunks.get(ci);
        if (!chunk || chunk.removed)
            continue;
        const chunkZ = chunk.zPosition;
        // Check obstacles
        for (const obs of chunk.obstacles) {
            if (!obs.active)
                continue;
            const obsWorldZ = chunkZ + obs.z;
            const dz = Math.abs(playerZ - obsWorldZ);
            // Only check if player is near the obstacle in Z
            const zReach = (obs.depth / 2) + pRadius;
            if (dz > zReach)
                continue;
            const dx = Math.abs(player.x - obs.x);
            const dy = Math.abs(player.y - obs.y);
            const hw = obs.width / 2 + pRadius;
            const hh = obs.height / 2 + pRadius;
            if (obs.type === 'squeeze' && obs.gapInfo) {
                // For squeeze obstacles, check if player is within the gap
                const gi = obs.gapInfo;
                const inGapX = Math.abs(player.x - gi.gapX) < gi.gapW / 2;
                const inGapY = Math.abs(player.y - gi.gapY) < gi.gapH / 2;
                if (inGapX && inGapY) {
                    // Player is through the gap - check near-miss on edges
                    const distToGapEdgeX = gi.gapW / 2 - Math.abs(player.x - gi.gapX);
                    const distToGapEdgeY = gi.gapH / 2 - Math.abs(player.y - gi.gapY);
                    const edgeDist = Math.min(distToGapEdgeX, distToGapEdgeY);
                    if (edgeDist > 0 && edgeDist < nearMissDist) {
                        triggerNearMiss(state, edgeDist, audio);
                    }
                    continue;
                }
                // Outside gap - collision
                if (dx < hw && dy < hh && dz < zReach) {
                    crash(state, audio);
                    return;
                }
                continue;
            }
            // Monolith collision
            if (dx < hw && dy < hh && dz < zReach) {
                crash(state, audio);
                return;
            }
            // Near-miss for monoliths
            const dist = Math.sqrt(Math.max(0, hw - dx) ** 2 + Math.max(0, hh - dy) ** 2 + Math.max(0, zReach - dz) ** 2);
            if (dist < nearMissDist && dx < hw + nearMissDist && dy < hh + nearMissDist && dz < zReach + nearMissDist) {
                triggerNearMiss(state, dist, audio);
            }
        }
        // Check orbs
        for (const orb of chunk.orbs) {
            if (orb.collected)
                continue;
            const orbWorldZ = chunkZ + orb.z;
            const dx = player.x - orb.x;
            const dy = player.y - orb.y;
            const dz = playerZ - orbWorldZ;
            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
            if (dist < pRadius + 2) {
                orb.collected = true;
                orb.mesh.visible = false;
                state.energy = Math.min(gameState_1.CONFIG.energyMax, state.energy + gameState_1.CONFIG.energyRecharge);
                state.turbo = Math.min(gameState_1.CONFIG.turboMax, state.turbo + 15);
                audio.playCollect();
                state.score += 50;
            }
        }
        // Check canyon wall proximity
        const narrowFactor = Math.max(0.3, 1 - (state.difficulty - 1) * 0.15);
        const effectiveWidth = gameState_1.CONFIG.canyonWidth * narrowFactor;
        const distToLeftWall = Math.abs(player.x) - (effectiveWidth / 2 - pRadius);
        const distToRightWall = Math.abs(player.x) - (effectiveWidth / 2 - pRadius);
        const wallDist = effectiveWidth / 2 - Math.abs(player.x) - pRadius;
        if (wallDist < nearMissDist && wallDist > 0) {
            // Only trigger if we're very close
            if (wallDist < 3) {
                triggerNearMiss(state, wallDist, audio);
            }
        }
    }
}
function triggerNearMiss(state, distance, audio) {
    const now = performance.now() / 1000;
    if (now - state.lastNearMissTime < gameState_1.CONFIG.nearMissDuration)
        return;
    state.lastNearMissTime = now;
    state.nearMissTimer = gameState_1.CONFIG.nearMissDuration;
    state.nearMissActive = true;
    const bonus = Math.floor((1 / (distance + 0.5)) * 100);
    state.score += bonus;
    // Build multiplier based on closeness
    if (distance < 3) {
        state.multiplier = Math.min(10, state.multiplier + 3);
    }
    else if (distance < 5) {
        state.multiplier = Math.min(8, state.multiplier + 2);
    }
    else {
        state.multiplier = Math.min(5, state.multiplier + 1);
    }
    state.turbo = Math.min(gameState_1.CONFIG.turboMax, state.turbo + 5);
    state.cameraShake = 0.3;
    audio.playNearMiss();
}
function crash(state, audio) {
    state.gameOver = true;
    state.running = false;
    state.cameraShake = 1.0;
    audio.playCrash();
}
};

// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateHUD = updateHUD;
exports.showGameOver = showGameOver;
exports.hideGameOver = hideGameOver;
exports.showStartScreen = showStartScreen;
const gameState_1 = require("./gameState");
const elMultiplier = document.getElementById('multiplier');
const elEnergyBar = document.getElementById('energyBar');
const elSpeedBar = document.getElementById('speedBar');
const elTurboBar = document.getElementById('turboBar');
const elGameOver = document.getElementById('gameOver');
const elFinalScore = document.getElementById('finalScore');
const elFinalSpeed = document.getElementById('finalSpeed');
const elFinalMaxSpeed = document.getElementById('finalMaxSpeed');
const elStartScreen = document.getElementById('startScreen');
function updateHUD(state) {
    // Energy bar
    const energyPct = (state.energy / gameState_1.CONFIG.energyMax) * 100;
    elEnergyBar.style.width = energyPct + '%';
    if (energyPct < 20) {
        elEnergyBar.style.background = 'linear-gradient(90deg, #ff0000, #ff4444)';
        elEnergyBar.style.boxShadow = '0 0 15px rgba(255, 0, 0, 0.8)';
    }
    else if (energyPct < 50) {
        elEnergyBar.style.background = 'linear-gradient(90deg, #ff4444, #ff8c00)';
        elEnergyBar.style.boxShadow = '0 0 10px rgba(255, 100, 0, 0.5)';
    }
    else {
        elEnergyBar.style.background = 'linear-gradient(90deg, #ff4444, #ff8c00, #ffcc00)';
        elEnergyBar.style.boxShadow = '0 0 10px rgba(255, 140, 0, 0.5)';
    }
    // Speed bar
    const maxPossibleSpeed = gameState_1.CONFIG.boostSpeed;
    const speedPct = (state.speed / maxPossibleSpeed) * 100;
    elSpeedBar.style.height = Math.min(100, speedPct * 1.5) + '%';
    // Turbo bar
    elTurboBar.style.height = (state.turbo / gameState_1.CONFIG.turboMax) * 100 + '%';
    // Multiplier
    elMultiplier.textContent = 'x' + state.multiplier;
    if (state.nearMissActive) {
        const scale = 1 + Math.sin(performance.now() / 50) * 0.15;
        elMultiplier.style.transform = `translateX(-50%) scale(${scale})`;
        elMultiplier.style.color = '#ffcc00';
        elMultiplier.style.textShadow = '0 0 30px rgba(255, 200, 0, 0.8), 0 0 60px rgba(255, 140, 0, 0.5)';
    }
    else {
        elMultiplier.style.transform = 'translateX(-50%) scale(1)';
        elMultiplier.style.color = '#ff8c00';
        elMultiplier.style.textShadow = '0 0 20px rgba(255, 140, 0, 0.6), 0 0 40px rgba(255, 140, 0, 0.3)';
    }
    // Score display in multiplier when no near miss
    if (state.running) {
        elMultiplier.textContent = Math.floor(state.score).toLocaleString();
    }
}
function showGameOver(state) {
    elGameOver.style.display = 'flex';
    elFinalScore.textContent = 'SCORE: ' + Math.floor(state.score).toLocaleString();
    elFinalSpeed.textContent = 'FINAL SPEED: ' + (state.speed * 1000).toFixed(0) + ' km/h';
    elFinalMaxSpeed.textContent = 'MAX SPEED: ' + (state.maxSpeed * 1000).toFixed(0) + ' km/h';
}
function hideGameOver() {
    elGameOver.style.display = 'none';
}
function showStartScreen(show) {
    elStartScreen.style.display = show ? 'flex' : 'none';
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GameRenderer = void 0;
const gameState_1 = require("./gameState");
class GameRenderer {
    constructor(canvas) {
        // Main renderer
        this.renderer = new THREE.WebGLRenderer({
            canvas,
            antialias: true,
            alpha: false,
        });
        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        this.renderer.shadowMap.enabled = true;
        this.renderer.toneMapping = 2; // ACESFilmic
        this.renderer.toneMappingExposure = 1.2;
        // Scene
        this.scene = new THREE.Scene();
        // Fog - cyber sunset gradient
        this.scene.fog = new THREE.Fog(0x1a0a2e, gameState_1.CONFIG.fogNear * 0.7, gameState_1.CONFIG.fogFar * 1.2);
        this.scene.background = new THREE.Color(0x1a0a2e);
        // Sky dome with gradient colors
        const skyGeo = new THREE.SphereGeometry(400, 16, 8);
        const skyMat = new THREE.ShaderMaterial({
            side: THREE.BackSide,
            uniforms: {
                topColor: { value: new THREE.Color(0.15, 0.05, 0.25) },
                bottomColor: { value: new THREE.Color(0.6, 0.25, 0.1) },
                offset: { value: 20 },
                exponent: { value: 0.4 },
            },
            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;
        uniform float offset;
        uniform float exponent;
        varying vec3 vWorldPosition;
        void main() {
          float h = normalize(vWorldPosition + offset).y;
          gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
        }
      `,
            depthWrite: false,
        });
        const sky = new THREE.Mesh(skyGeo, skyMat);
        sky.userData.isSky = true;
        this.scene.add(sky);
        // Camera
        const aspect = window.innerWidth / window.innerHeight;
        this.camera = new THREE.PerspectiveCamera(gameState_1.CONFIG.baseFov, aspect, 0.1, 500);
        this.camera.position.set(0, 5, 15);
        // Lighting - dramatic golden hour
        const ambient = new THREE.AmbientLight(0x2a1540, 0.5);
        this.scene.add(ambient);
        // Sun (directional) - low angle, warm
        const sun = new THREE.DirectionalLight(0xff8844, 2.0);
        sun.position.set(30, 20, -50);
        sun.castShadow = true;
        sun.shadow.mapSize.width = 1024;
        sun.shadow.mapSize.height = 1024;
        sun.shadow.camera.near = 1;
        sun.shadow.camera.far = 200;
        sun.shadow.camera.left = -60;
        sun.shadow.camera.right = 60;
        sun.shadow.camera.top = 40;
        sun.shadow.camera.bottom = -40;
        this.scene.add(sun);
        // Rim light (cool purple from behind)
        const rim = new THREE.DirectionalLight(0x8844ff, 0.5);
        rim.position.set(0, 10, 20);
        this.scene.add(rim);
        // Warm point light from below (ground bounce)
        const bounceLight = new THREE.PointLight(0xff6633, 0.8, 100);
        bounceLight.position.set(0, -gameState_1.CONFIG.canyonHeight / 2, -20);
        bounceLight.userData.isBounce = true;
        this.scene.add(bounceLight);
        // Sun disc - glowing quad in the distance
        const sunGeo = new THREE.PlaneGeometry(30, 30);
        const sunMat = new THREE.MeshBasicMaterial({
            color: new THREE.Color(1.0, 0.5, 0.1),
            transparent: true,
            opacity: 0.6,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            side: THREE.DoubleSide,
        });
        const sunDisc = new THREE.Mesh(sunGeo, sunMat);
        sunDisc.position.set(0, 8, -300);
        sunDisc.userData.isSunDisc = true;
        this.scene.add(sunDisc);
        // Sun glow (larger, more transparent)
        const glowGeo = new THREE.PlaneGeometry(80, 80);
        const glowMat = new THREE.MeshBasicMaterial({
            color: new THREE.Color(1.0, 0.3, 0.0),
            transparent: true,
            opacity: 0.15,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            side: THREE.DoubleSide,
        });
        const sunGlow = new THREE.Mesh(glowGeo, glowMat);
        sunGlow.position.set(0, 8, -300);
        sunGlow.userData.isSunGlow = true;
        this.scene.add(sunGlow);
        // Floating dust particles for atmosphere
        const dustCount = 300;
        const dustGeo = new THREE.BufferGeometry();
        const dustPos = new Float32Array(dustCount * 3);
        for (let i = 0; i < dustCount; i++) {
            dustPos[i * 3] = (Math.random() - 0.5) * 100;
            dustPos[i * 3 + 1] = (Math.random() - 0.5) * 50;
            dustPos[i * 3 + 2] = -Math.random() * 400;
        }
        dustGeo.setAttribute('position', new THREE.BufferAttribute(dustPos, 3));
        const dustMat = new THREE.PointsMaterial({
            color: new THREE.Color(0.8, 0.6, 0.3),
            size: 0.2,
            transparent: true,
            opacity: 0.3,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            sizeAttenuation: true,
        });
        const dust = new THREE.Points(dustGeo, dustMat);
        dust.userData.isDust = true;
        this.scene.add(dust);
        // Ground plane (dark canyon floor)
        const groundGeo = new THREE.PlaneGeometry(500, 2000);
        const groundMat = new THREE.MeshStandardMaterial({
            color: new THREE.Color(0.08, 0.04, 0.12),
            roughness: 0.95,
            metalness: 0.05,
        });
        const ground = new THREE.Mesh(groundGeo, groundMat);
        ground.rotation.x = -Math.PI / 2;
        ground.position.y = -gameState_1.CONFIG.canyonHeight / 2 - 5;
        ground.position.z = -500;
        ground.userData.isGround = true;
        this.scene.add(ground);
        // Ceiling plane (dark, creating enclosed canyon feel)
        const ceilGeo = new THREE.PlaneGeometry(200, 2000);
        const ceilMat = new THREE.MeshStandardMaterial({
            color: new THREE.Color(0.05, 0.02, 0.1),
            roughness: 1,
            metalness: 0,
        });
        const ceiling = new THREE.Mesh(ceilGeo, ceilMat);
        ceiling.rotation.x = Math.PI / 2;
        ceiling.position.y = gameState_1.CONFIG.canyonHeight / 2 + 10;
        ceiling.position.z = -500;
        ceiling.userData.isCeiling = true;
        this.scene.add(ceiling);
        // === Player Craft ===
        this.playerGroup = this.createPlayerCraft();
        this.scene.add(this.playerGroup);
    }
    createPlayerCraft() {
        const group = new THREE.Group();
        // Main body - sleek arrow shape
        const bodyGeo = new THREE.ConeGeometry(0.6, 3, 6);
        const bodyMat = new THREE.MeshStandardMaterial({
            color: new THREE.Color(0.9, 0.85, 0.8),
            flatShading: true,
            metalness: 0.8,
            roughness: 0.2,
        });
        const body = new THREE.Mesh(bodyGeo, bodyMat);
        body.rotation.x = Math.PI / 2;
        group.add(body);
        // Nose cone
        const noseGeo = new THREE.ConeGeometry(0.3, 1.5, 4);
        const noseMat = new THREE.MeshStandardMaterial({
            color: new THREE.Color(1, 0.4, 0.1),
            flatShading: true,
            emissive: new THREE.Color(1, 0.2, 0.0),
            emissiveIntensity: 0.5,
            metalness: 0.5,
            roughness: 0.3,
        });
        const nose = new THREE.Mesh(noseGeo, noseMat);
        nose.rotation.x = Math.PI / 2;
        nose.position.z = -2.0;
        group.add(nose);
        // Wings
        const wingGeo = new THREE.BoxGeometry(4, 0.1, 1.5);
        const wingMat = new THREE.MeshStandardMaterial({
            color: new THREE.Color(0.15, 0.1, 0.25),
            flatShading: true,
            metalness: 0.7,
            roughness: 0.3,
        });
        const wings = new THREE.Mesh(wingGeo, wingMat);
        wings.position.z = 0.2;
        group.add(wings);
        // Wing tips (glowing)
        const tipGeo = new THREE.BoxGeometry(0.3, 0.15, 0.5);
        const tipMat = new THREE.MeshPhongMaterial({
            color: new THREE.Color(1, 0.3, 0.0),
            emissive: new THREE.Color(1, 0.3, 0.0),
            emissiveIntensity: 2,
        });
        const tipL = new THREE.Mesh(tipGeo, tipMat);
        tipL.position.set(-2.1, 0, 0.2);
        group.add(tipL);
        const tipR = new THREE.Mesh(tipGeo, tipMat);
        tipR.position.set(2.1, 0, 0.2);
        group.add(tipR);
        // Engine glow (two small cones at back)
        const engineGeo = new THREE.ConeGeometry(0.3, 1.2, 4);
        const engineMat = new THREE.MeshPhongMaterial({
            color: new THREE.Color(1, 0.4, 0.0),
            emissive: new THREE.Color(1, 0.4, 0.0),
            emissiveIntensity: 3,
            transparent: true,
            opacity: 0.8,
        });
        const engineL = new THREE.Mesh(engineGeo, engineMat);
        engineL.rotation.x = -Math.PI / 2;
        engineL.position.set(-0.5, 0, 1.5);
        group.add(engineL);
        const engineR = new THREE.Mesh(engineGeo, engineMat);
        engineR.rotation.x = -Math.PI / 2;
        engineR.position.set(0.5, 0, 1.5);
        group.add(engineR);
        // Engine glow sphere
        const glowGeo = new THREE.IcosahedronGeometry(0.5, 1);
        const glowMat = new THREE.MeshBasicMaterial({
            color: new THREE.Color(1, 0.5, 0.1),
            transparent: true,
            opacity: 0.3,
            blending: THREE.AdditiveBlending,
        });
        const glowL = new THREE.Mesh(glowGeo, glowMat);
        glowL.position.set(-0.5, 0, 1.8);
        group.add(glowL);
        const glowR = new THREE.Mesh(glowGeo, glowMat);
        glowR.position.set(0.5, 0, 1.8);
        group.add(glowR);
        group.position.set(0, 0, 0);
        return group;
    }
    getPlayerMesh() {
        return this.playerGroup;
    }
    updatePlayerVisuals(player, boosting, time) {
        this.playerGroup.position.x = player.x;
        this.playerGroup.position.y = player.y;
        // Banking (roll)
        this.playerGroup.rotation.z = player.bankAngle;
        // Pitch (nose up/down)
        this.playerGroup.rotation.x = -player.pitchAngle * 2 - 0.15;
        // Engine glow intensity based on boost
        const intensity = boosting ? 5 : 2 + Math.sin(time * 10) * 0.5;
        const engineColor = boosting ? new THREE.Color(1, 0.2, 0.0) : new THREE.Color(1, 0.5, 0.1);
        this.playerGroup.children.forEach(child => {
            if (child instanceof THREE.Mesh && child.material instanceof THREE.MeshPhongMaterial) {
                child.material.emissiveIntensity = intensity;
                child.material.emissive = engineColor.clone();
            }
        });
    }
    updateCamera(state, time) {
        // Camera follows player with smooth interpolation
        const targetX = this.playerGroup.position.x * 0.5;
        const targetY = this.playerGroup.position.y * 0.4 + 5;
        this.camera.position.x += (targetX - this.camera.position.x) * 0.05;
        this.camera.position.y += (targetY - this.camera.position.y) * 0.05;
        // Move sky to follow camera
        this.scene.children.forEach(child => {
            if (child.userData && child.userData.isSky) {
                child.position.copy(this.camera.position);
            }
            // Keep ground following camera in X and Z
            if (child.userData && child.userData.isGround) {
                child.position.x = this.camera.position.x * 0.5;
                child.position.z = this.camera.position.z - 500;
            }
            if (child.userData && child.userData.isCeiling) {
                child.position.x = this.camera.position.x * 0.3;
                child.position.z = this.camera.position.z - 500;
            }
            if (child.userData && child.userData.isSunDisc) {
                child.position.z = this.camera.position.z - 300;
            }
            if (child.userData && child.userData.isSunGlow) {
                child.position.z = this.camera.position.z - 300;
            }
            if (child.userData && child.userData.isBounce) {
                child.position.z = this.camera.position.z - 20;
            }
        });
        // Camera shake
        if (state.cameraShake > 0.01) {
            const shake = state.cameraShake;
            this.camera.position.x += Math.sin(time * 43) * shake;
            this.camera.position.y += Math.cos(time * 37) * shake;
            this.camera.position.z += Math.sin(time * 51) * shake * 0.5;
        }
        // Look ahead
        const lookTarget = new THREE.Vector3(this.playerGroup.position.x * 0.3, this.playerGroup.position.y * 0.3, -30);
        this.camera.lookAt(lookTarget);
    }
    updateFov(state, _time) {
        let targetFov = gameState_1.CONFIG.baseFov;
        // Speed-based FOV
        const speedRatio = state.speed / gameState_1.CONFIG.boostSpeed;
        targetFov += speedRatio * 10;
        // Boost FOV
        if (state.boosting) {
            targetFov = gameState_1.CONFIG.boostFov;
        }
        // Near-miss FOV kick
        if (state.nearMissActive) {
            targetFov += 5;
        }
        this.camera.fov += (targetFov - this.camera.fov) * 0.05;
        this.camera.updateProjectionMatrix();
    }
    animateOrbs(chunks, time) {
        chunks.forEach(chunk => {
            if (chunk.removed)
                return;
            chunk.orbs.forEach(orb => {
                if (orb.collected)
                    return;
                orb.mesh.rotation.x = time * 2 + orb.phase;
                orb.mesh.rotation.y = time * 1.5 + orb.phase;
                const pulse = 1 + Math.sin(time * 4 + orb.phase) * 0.2;
                orb.mesh.scale.set(pulse, pulse, pulse);
            });
        });
    }
    updateSunPosition(playerZ) {
        this.scene.children.forEach(child => {
            if (child instanceof THREE.DirectionalLight) {
                child.position.z = playerZ - 50;
            }
        });
    }
    render() {
        this.renderer.render(this.scene, this.camera);
    }
    resize(w, h) {
        this.camera.aspect = w / h;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(w, h);
    }
    getScene() {
        return this.scene;
    }
    getCamera() {
        return this.camera;
    }
}
exports.GameRenderer = GameRenderer;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"8031a2b2-2731-4292-b7d4-725c9a680579","tokensIn":4007694,"tokensOut":58503,"tokensTotal":4066197,"cost":0,"turns":82,"toolCalls":91,"failedToolCalls":10,"timestamp":"2026-08-02T05:12:05.928Z"} -->