Canyon Run
nex agi Nex N2.5 mini GGUF · typescript

Browser check: Artifact runtime error. 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 39,393 bytes · SHA-256 e1466132e4ed
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canyon Rush — High-Speed Procedural Racer</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<style>
/* ── css: base ── */
html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; background: #120618; font-family: "Avenir Next", "Segoe UI", sans-serif; }
#app { position: relative; width: 100%; height: 100%; }
canvas.game-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#hud { position: absolute; inset: 0; pointer-events: none; color: rgba(255, 244, 226, .94); text-shadow: 0 1px 18px rgba(255, 116, 56, .25); }
.hud-top { position: absolute; top: 22px; left: 26px; right: 26px; display: flex; justify-content: space-between; align-items: flex-start; }
.label { font-size: 10px; letter-spacing: .28em; opacity: .62; text-transform: uppercase; }
#score { font-size: clamp(28px, 4vw, 52px); font-weight: 200; line-height: 1; margin-top: 4px; }
#multiplier { font-size: clamp(30px, 4vw, 56px); font-weight: 700; line-height: .9; color: #ffb866; }
#multiplier.pulse { animation: multiplierPulse .45s ease-out; }
#energy-wrap { width: 160px; margin-top: 7px; }
.bar { height: 3px; background: rgba(255,255,255,.13); position: relative; overflow: hidden; }
#energy-fill { height: 100%; width: 100%; background: linear-gradient(90deg, #ff7244, #ffd166); box-shadow: 0 0 16px #ff7b4f; }
#boost-wrap { width: 160px; margin-top: 7px; }
#boost-fill { height: 100%; width: 0%; background: linear-gradient(90deg,#c76bff,#ff5ca8); box-shadow: 0 0 16px #b75cff; }
#speed-wrap { position: absolute; left: 26px; bottom: 28px; width: min(270px, 34vw); }
#speed-line { position: relative; height: 2px; background: rgba(255,255,255,.14); }
#speed-fill { position: absolute; left: 0; top: 0; height: 100%; width: 0%; background: linear-gradient(90deg,#ffb866,#ff5ca8); box-shadow: 0 0 12px #ff7a54; transition: width .12s linear; }
#speed-value { margin-top: 9px; font-size: 12px; letter-spacing: .16em; opacity: .8; }
#boost-indicator { position: absolute; right: 26px; bottom: 28px; width: 170px; text-align: right; }
#toast { position: absolute; left: 50%; top: 31%; transform: translate(-50%, -50%); color: #ffe0ad; letter-spacing: .2em; opacity: 0; font-size: 12px; text-transform: uppercase; }
#toast.show { animation: toastFade 1.2s ease forwards; }
#start-screen, #game-over-screen { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; background: radial-gradient(circle at 50% 44%, rgba(103,42,92,.04), rgba(10,3,17,.78) 62%); backdrop-filter: blur(3px); transition: opacity .45s ease, visibility .45s; z-index: 4; }
.hidden { opacity: 0 !important; visibility: hidden !important; }
.eyebrow { font-size: 10px; letter-spacing: .38em; color: #ffb866; text-transform: uppercase; }
h1 { font-size: clamp(46px, 8vw, 108px); font-weight: 100; line-height: .9; margin: 15px 0 12px; letter-spacing: -.06em; }
.subtitle { max-width: 540px; text-align: center; line-height: 1.6; font-weight: 200; color: rgba(255,240,224,.75); }
.btn { pointer-events: auto; margin-top: 32px; padding: 13px 30px; border: 1px solid rgba(255, 202, 144, .55); color: #ffd8aa; background: rgba(255, 112, 66, .12); text-transform: uppercase; letter-spacing: .25em; font-size: 12px; cursor: pointer; transition: transform .2s ease, background .2s ease, box-shadow .2s ease; }
.btn:hover { transform: translateY(-2px); background: rgba(255, 112, 66, .24); box-shadow: 0 0 32px rgba(255, 115, 66, .3); }
.stats { display: flex; gap: 34px; margin-top: 28px; }
.stat { min-width: 90px; }
.stat strong { display: block; font-size: 30px; font-weight: 200; color: #fff1d7; }
.stat span { font-size: 10px; letter-spacing: .24em; text-transform: uppercase; opacity: .6; }
#hint { position: absolute; bottom: 26px; left: 50%; transform: translateX(-50%); opacity: .52; font-size: 10px; letter-spacing: .2em; text-transform: uppercase; }
@media (max-width: 680px) { .hud-top { left: 16px; right: 16px; top: 16px; } #energy-wrap, #boost-wrap { width: 118px; } #speed-wrap { left: 16px; bottom: 16px; width: 48vw; } #boost-indicator { right: 16px; bottom: 16px; width: 42vw; } }
</style>
</head>
<body>
<div id="app"></div>
<div id="hud" class="hidden">
<div class="hud-top">
<div><div class="label">score</div><div id="score">000000</div></div>
<div style="text-align:right"><div class="label">proximity</div><div id="multiplier">×1</div><div id="energy-wrap"><div class="label">ion core</div><div class="bar"><div id="energy-fill"></div></div></div><div id="boost-wrap"><div class="label">turbo</div><div class="bar"><div id="boost-fill"></div></div></div></div>
</div>
<div id="speed-wrap"><div class="label">velocity</div><div id="speed-line"><div id="speed-fill"></div></div><div id="speed-value">0 km/h</div></div>
<div id="boost-indicator"><div id="boost-state" class="label">turbo offline</div></div>
<div id="toast"></div>
<div id="hint">mouse / touch to steer · pitch with vertical motion · boost with Space</div>
</div>
<div id="start-screen">
<div class="eyebrow">high-speed canyon protocol</div>
<h1>CANYON RUSH</h1>
<div class="subtitle">Thread the last light through an endless procedural canyon. Stay near the rock to multiply your score, but touch the stone and the run is over. Gather ion shards to survive.</div>
<button class="btn" id="start-button">Launch craft</button>
<div class="stats"><div class="stat"><strong id="best-score">000000</strong><span>best score</span></div><div class="stat"><strong id="best-speed">000</strong><span>best velocity</span></div></div>
</div>
<div id="game-over-screen" class="hidden">
<div class="eyebrow">signal lost</div>
<h1>CRASHED</h1>
<div class="stats"><div class="stat"><strong id="final-score">0</strong><span>final score</span></div><div class="stat"><strong id="final-speed">0</strong><span>max speed km/h</span></div><div class="stat"><strong id="final-distance">0</strong><span>distance km</span></div></div>
<button class="btn" id="restart-button">Run it again</button>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {};
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 T = window.THREE;
const app = document.getElementById("app");
const startScreen = document.getElementById("start-screen");
const gameOverScreen = document.getElementById("game-over-screen");
const hud = document.getElementById("hud");
const scoreEl = document.getElementById("score");
const multiplierEl = document.getElementById("multiplier");
const energyFillEl = document.getElementById("energy-fill");
const boostFillEl = document.getElementById("boost-fill");
const boostStateEl = document.getElementById("boost-state");
const speedFillEl = document.getElementById("speed-fill");
const speedValueEl = document.getElementById("speed-value");
const toastEl = document.getElementById("toast");
const finalScoreEl = document.getElementById("final-score");
const finalSpeedEl = document.getElementById("final-speed");
const finalDistanceEl = document.getElementById("final-distance");
const bestScoreEl = document.getElementById("best-score");
const bestSpeedEl = document.getElementById("best-speed");
let scene;
let camera;
let renderer;
let composer = null;
let bloomPass = null;
let sun;
let rim;
let ground;
let dustGroup;
let trail = null;
let chunkGroup;
let player;
let engineGlow;
let orbitRing;
let shardGroup;
let obstaclePool = [];
let wallPool = [];
let chunkIds = [];
let activeChunks = [];
let phase = "menu";
let menuChunkStart = 0;
let score = 0;
let nearMisses = 0;
let multiplier = 1;
let energy = 100;
let boost = 0;
let speed = 0;
let maxSpeed = 0;
let distance = 0;
let baseSpeed = 52;
let playerX = 0;
let playerY = 0;
let nearMissCooldown = 0;
let playerZ = 0;
let pitch = 0;
let roll = 0;
let rollTarget = 0;
let velocityX = 0;
let velocityY = 0;
let pointerX = 0;
let pointerY = 0;
let mouseX = 0;
let mouseY = 0;
let keys = new Set();
let lastTime = performance.now();
let elapsed = 0;
let frameCount = 0;
let shake = 0;
let toastTimer = 0;
let audio = { started: false, ctx: undefined, osc: undefined, engineGain: undefined, filter: undefined, master: undefined };
let ambientAudio;
let bestScore = Number(localStorage.getItem("canyon-rush-best-score") || 0);
let bestSpeed = Number(localStorage.getItem("canyon-rush-best-speed") || 0);
function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); }
function lerp(a, b, t) { return a + (b - a) * t; }
function rand(min, max) { return min + Math.random() * (max - min); }
function hash2(x, z) { const n = Math.sin(x * 127.1 + z * 311.7) * 43758.5453; return n - Math.floor(n); }
let menuGroup = null;
function startMenuWorld() {
menuGroup = new T.Group();
const material = makeMaterial(0x3a1d46, 0x1a071e, 0.24);
const accent = makeMaterial(0x6f376b, 0x2a0b2d, 0.38);
for (let i = 0; i < 18; i++) {
const mesh = new T.Mesh(new T.ConeGeometry(rand(3.2, 7.5), rand(12, 28), 6), i % 3 === 0 ? accent : material);
mesh.position.set(rand(-70, 70), rand(-12, 12), -i * 28 + Math.sin(i * 2.1) * 8);
mesh.rotation.set(rand(-0.18, 0.18), rand(0, 6), rand(-0.12, 0.12));
mesh.castShadow = true;
mesh.receiveShadow = true;
menuGroup.add(mesh);
}
scene = menuGroup;
const floor = new T.Mesh(new T.PlaneGeometry(220, 800), makeMaterial(0x1b0b23, 0x110512, 0.18));
floor.rotation.x = -Math.PI / 2;
floor.position.y = -15;
scene.add(floor);
}
function makeMaterial(color, emissive, emissiveIntensity = 0.7) {
const mat = new T.MeshPhongMaterial({ color, emissive: emissive === undefined ? 0x000000 : emissive, emissiveIntensity });
mat.shininess = 18;
return mat;
}
function makeBasicMaterial(color) { return new T.MeshBasicMaterial({ color }); }
function createScene() {
startMenuWorld();
camera = new T.PerspectiveCamera(72, app.clientWidth / app.clientHeight, 0.1, 900);
camera.position.set(0, 12, -18);
renderer = new T.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.6));
renderer.setSize(app.clientWidth, app.clientHeight);
renderer.domElement.className = "game-canvas";
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = T.PCFSoftShadowMap;
app.appendChild(renderer.domElement);
sun = new T.DirectionalLight(0xffad66, 2.1);
sun.position.set(-52, 70, -30);
sun.castShadow = true;
sun.shadow.mapSize.set(1024, 1024);
scene.add(sun);
scene.add(new T.AmbientLight(0x7f56b8, 0.9));
rim = new T.DirectionalLight(0xff5c8c, 0.7);
rim.position.set(50, 18, 35);
scene.add(rim);
ground = new T.Mesh(new T.PlaneGeometry(700, 1200, 1, 1), makeMaterial(0x25102c, 0x1b071f, 0.18));
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
chunkGroup = new T.Group();
scene.add(chunkGroup);
shardGroup = new T.Group();
scene.add(shardGroup);
dustGroup = new T.Group();
scene.add(dustGroup);
player = new T.Group();
scene.add(player);
engineGlow = new T.Mesh(new T.SphereGeometry(0.22, 12, 8), makeBasicMaterial(0xff834f));
engineGlow.position.set(0, 0.18, 1.22);
player.add(engineGlow);
orbitRing = new T.Mesh(new T.TorusGeometry(0.86, 0.018, 6, 42), makeBasicMaterial(0xff8b68));
orbitRing.rotation.x = Math.PI / 2;
player.add(orbitRing);
makePlayerMesh();
if (T.EffectComposer && T.UnrealBloomPass && T.RenderPass) {
composer = new T.EffectComposer(renderer);
composer.addPass(new T.RenderPass(scene, camera));
bloomPass = new T.UnrealBloomPass({ x: 1, y: 1 }, 1.15, 0.26, 0.36);
composer.addPass(bloomPass);
}
else {
bloomPass = null;
}
if (!composer) {
scene = new T.Scene();
scene.background = new T.Color(0x1a0920);
scene.fog = new T.Fog(0x3c163a, 120, 520);
scene.add(ground);
scene.add(sun);
scene.add(new T.AmbientLight(0x7f56b8, 0.9));
scene.add(rim);
}
window.addEventListener("resize", handleResize);
}
function makePlayerMesh() {
const hullMat = makeMaterial(0x9b7bd8, 0x30103b, 0.35);
const accentMat = makeMaterial(0xff9f62, 0xff5a3f, 0.65);
const darkMat = makeMaterial(0x28182f, 0x0b0712, 0.1);
const hull = new T.Mesh(new T.ConeGeometry(0.42, 1.55, 8), hullMat);
hull.rotation.x = Math.PI / 2;
hull.position.set(0, 0.16, -0.25);
player.add(hull);
const nose = new T.Mesh(new T.ConeGeometry(0.13, 0.7, 8), accentMat);
nose.rotation.x = Math.PI / 2;
nose.position.set(0, 0.25, -1.1);
player.add(nose);
const fin = new T.Mesh(new T.ConeGeometry(0.34, 1.15, 4), darkMat);
fin.rotation.z = 0;
fin.rotation.x = Math.PI / 2;
fin.scale.set(0.48, 1, 1);
fin.position.set(0, 0.45, 0.65);
player.add(fin);
const wingMat = makeMaterial(0x472450, 0x1c092b, 0.25);
const wingL = new T.Mesh(new T.BoxGeometry(1.15, 0.045, 0.64), wingMat);
wingL.position.set(-0.78, 0.03, 0.28);
wingL.rotation.z = 0.06;
player.add(wingL);
const wingR = wingL.clone();
wingR.position.x = 0.78;
wingR.rotation.z = -0.06;
player.add(wingR);
const stripe = new T.Mesh(new T.BoxGeometry(0.08, 0.055, 0.74), accentMat);
stripe.position.set(0, 0.43, -0.16);
player.add(stripe);
const lightL = new T.Mesh(new T.SphereGeometry(0.045, 8, 6), makeBasicMaterial(0xffc373));
lightL.position.set(-0.66, 0.27, 0.42);
player.add(lightL);
const lightR = lightL.clone();
lightR.position.x = 0.66;
player.add(lightR);
}
function createObstacleGeometry(kind) {
if (kind === "monolith") {
const g = new T.ShapeGeometry(new T.Shape().moveTo(-1, -2).lineTo(1, -2).lineTo(0.55, 2).lineTo(-0.55, 2));
g.rotateY(Math.PI / 2);
return g;
}
if (kind === "arch") {
const g = new T.BoxGeometry(3.0, 0.18, 0.58);
g.translate(0, 2.8, 0);
return g;
}
const g = new T.ConeGeometry(0.42, 1.1, 6);
g.rotateZ(Math.PI / 2);
return g;
}
function spawnChunk(id) {
const group = new T.Group();
group.position.z = -(id * 18);
const obstacles = [];
const shards = [];
const walls = [];
const rocks = [];
const count = 10 + Math.floor(hash2(id, 4.8) * 6);
for (let i = 0; i < count; i++) {
const z = -(i * 18);
const r = hash2(id * 1.7 + i, 8.2);
if (i % 4 === 0) {
const x = rand(-25, 25);
const y = rand(-13, -3);
const wall = new T.Mesh(new T.BoxGeometry(rand(14, 28), rand(15, 34), rand(7, 18)), makeMaterial(r > 0.72 ? 0x6f376b : 0x3a2344, 0x17081d, 0.16));
wall.position.set(x, y, z);
wall.rotation.z = rand(-0.12, 0.12);
wall.castShadow = true;
wall.receiveShadow = true;
group.add(wall);
walls.push(wall);
}
else if (i % 3 === 0) {
const x = rand(-7, 7);
const y = rand(-2.4, 2.6);
const mesh = new T.Mesh(createObstacleGeometry("monolith"), makeMaterial(hash2(id, i) > 0.45 ? 0xa66c9f : 0x6e416f, 0x270c29, 0.38));
mesh.scale.setScalar(rand(0.85, 1.55));
mesh.position.set(x, y, z);
mesh.castShadow = true;
mesh.receiveShadow = true;
group.add(mesh);
obstacles.push({ mesh, kind: "monolith", radius: 1.05 * mesh.scale.x, halfY: 1.25 * mesh.scale.y, x, z, passed: false });
}
else if (i % 5 === 2) {
const x = rand(-8, 8);
const y = rand(-1.6, 1.7);
const mesh = new T.Mesh(new T.SphereGeometry(rand(0.12, 0.18), 8, 6), makeBasicMaterial(0xffb96a));
mesh.position.set(x, y, z);
mesh.userData.collectible = true;
group.add(mesh);
shards.push(mesh);
if (i % 10 === 2) {
const beacon = new T.Mesh(new T.ConeGeometry(0.18, 1.15, 5), makeBasicMaterial(0xff6aa5));
beacon.rotation.x = Math.PI / 2;
beacon.position.set(x, y, z - 1.25);
beacon.userData.collectible = true;
group.add(beacon);
shards.push(beacon);
}
}
else if (r > 0.82) {
const x = rand(-13, 13);
const y = rand(-5, 5);
const mesh = new T.Mesh(new T.IcosahedronGeometry(rand(0.25, 0.55), 0), makeMaterial(0xc6748f, 0x4b173b, 0.5));
mesh.position.set(x, y, z);
mesh.rotation.set(rand(0, 3), rand(0, 3), rand(0, 3));
group.add(mesh);
obstacles.push({ mesh, kind: "debris", radius: 0.48 * mesh.scale.x, halfY: 0.38 * mesh.scale.y, x, z, passed: false });
rocks.push(mesh);
}
}
const floorGlow = new T.Mesh(new T.PlaneGeometry(28, 15), new T.MeshBasicMaterial({ color: 0x5b2858, transparent: true, opacity: 0.32 }));
floorGlow.rotation.x = -Math.PI / 2;
floorGlow.position.z = -7;
group.add(floorGlow);
chunkGroup.add(group);
const chunk = { id, group, obstacles, shards, walls, rocks };
activeChunks.push(chunk);
chunkIds.push(id);
return chunk;
}
function removeChunk(chunk) {
chunkGroup.remove(chunk.group);
chunk.group.traverse((node) => {
if (node.isMesh && node.geometry && node.geometry !== ground.geometry)
node.geometry.dispose();
if (node.material && node.material !== ground.material && node.material.dispose)
node.material.dispose();
});
activeChunks = activeChunks.filter((item) => item !== chunk);
}
function updateChunkSpacing() {
for (const chunk of activeChunks) {
const targetZ = -(chunk.id * 18);
chunk.group.position.z = lerp(chunk.group.position.z, targetZ, 0.06);
}
}
function resetRun() {
score = 0;
nearMisses = 0;
multiplier = 1;
energy = 100;
boost = 0;
speed = 52;
maxSpeed = 0;
distance = 0;
baseSpeed = 52;
playerX = 0;
playerY = 0;
playerZ = 0;
pitch = 0;
roll = 0;
rollTarget = 0;
velocityX = 0;
velocityY = 0;
pointerX = 0;
pointerY = 0;
mouseX = 0;
mouseY = 0;
shake = 0;
nearMissCooldown = 0;
elapsed = 0;
frameCount = 0;
for (let i = activeChunks.length - 1; i >= 0; i--)
removeChunk(activeChunks[i]);
obstaclePool = [];
wallPool = [];
chunkIds = [];
shardGroup.clear();
dustGroup.clear();
for (let id = 0; id < 42; id++)
spawnChunk(id);
updateCameraBase();
}
function updateCameraBase() {
camera.position.set(playerX * 0.36, 8.6 + playerY * 0.46, playerZ - 18);
camera.lookAt(playerX * 0.52, playerY * 0.72, playerZ - 42);
camera.fov = 72 + clamp(speed / 120, 0, 1) * 28;
camera.updateProjectionMatrix();
}
function updatePlayer(dt) {
const desiredX = lerp(playerX, pointerX, 1 - Math.pow(0.001, dt));
const desiredY = clamp(pointerY + playerY * 0.12, -5.6, 5.6);
velocityX = lerp(velocityX, desiredX - playerX, 1 - Math.pow(0.002, dt));
velocityY = lerp(velocityY, desiredY - playerY, 1 - Math.pow(0.004, dt));
playerX += velocityX * dt;
playerY += velocityY * dt;
playerX = clamp(playerX, -9.8, 9.8);
playerY = clamp(playerY, -5.5, 5.5);
rollTarget = clamp(-velocityX * 0.42, -0.72, 0.72);
pitch = lerp(pitch, clamp(velocityY * 0.22, -0.36, 0.36), 1 - Math.pow(0.004, dt));
roll = lerp(roll, rollTarget, 1 - Math.pow(0.005, dt));
player.position.set(playerX, playerY, playerZ);
player.rotation.set(pitch, 0, roll);
engineGlow.scale.setScalar(1 + boost * 1.6 + Math.sin(elapsed * 24) * 0.12);
engineGlow.material.color.setHex(0xff834f).lerp(new T.Color(0xffd46a), boost * 0.65);
orbitRing.rotation.z = elapsed * 1.7;
orbitRing.scale.setScalar(1 + boost * 0.18);
}
function updateSpeed(dt) {
const difficulty = clamp(distance / 4500, 0, 1);
baseSpeed = 54 + difficulty * 68 + multiplier * 5;
speed = lerp(speed, baseSpeed + boost * 88, 1 - Math.pow(0.006, dt));
maxSpeed = Math.max(maxSpeed, speed);
distance += speed * dt;
playerZ -= speed * dt;
ground.position.z = playerZ;
dustGroup.position.z = playerZ;
if (boost > 0)
boost = Math.max(0, boost - dt * 0.58);
const targetEnergy = 100 - (22 + difficulty * 14) * dt;
energy = clamp(energy + targetEnergy, 0, 100);
if (energy <= 0 && phase === "running")
endRun(false);
if (frameCount % 4 === 0)
spawnDust();
}
function updateObstacles(dt) {
nearMissCooldown = Math.max(0, nearMissCooldown - dt);
for (const chunk of activeChunks) {
for (const obs of chunk.obstacles) {
if (!obs.passed && obs.z > playerZ + 3) {
obs.passed = true;
const horizontalDistance = Math.abs(obs.x - playerX);
const verticalDistance = Math.abs(obs.halfY * 0.5 - playerY);
const clearance = Math.max(0, (horizontalDistance + verticalDistance) - 2.25);
if (clearance < 1.75 && clearance > 0.18 && nearMissCooldown <= 0) {
nearMisses++;
multiplier = Math.min(10, multiplier + (clearance < 0.7 ? 1 : 0.45));
showToast(clearance < 0.7 ? "GRAZING THE ROCK ×" + multiplier.toFixed(1) : "PROXIMITY BONUS");
sfxNearMiss(clearance < 0.7);
boost = Math.min(100, boost + 9);
shake = Math.min(1, shake + 0.18);
nearMissCooldown = 0.48;
pulseMultiplier();
}
}
obs.mesh.position.x = obs.mesh.position.x + Math.sin(elapsed * 1.3 + obs.z) * 0.002;
}
for (const shard of chunk.shards) {
shard.rotation.y += dt * 3.2;
shard.rotation.x += dt * 1.7;
const d = Math.sqrt((shard.position.x - playerX) ** 2 + (shard.position.y - playerY) ** 2 + (shard.position.z - playerZ) ** 2);
if (d < 0.72 && phase === "running") {
shardGroup.attach(shard);
shardGroup.add(shard);
chunk.shards.splice(chunk.shards.indexOf(shard), 1);
score += 120 * multiplier;
energy = clamp(energy + 13, 0, 100);
boost = Math.min(100, boost + 16);
sfxCollect();
showToast("ION SHARD +");
const ring = new T.Mesh(new T.TorusGeometry(0.42, 0.025, 6, 20), new T.MeshBasicMaterial({ color: 0xffb96a, transparent: true, opacity: 0.9 }));
ring.position.copy(shard.position);
shardGroup.add(ring);
ring.life = 0.35;
}
}
if (chunk.id * 18 < playerZ - 90)
removeChunk(chunk);
}
updateChunkSpacing();
}
function updateParticles(dt) {
for (const node of Array.from(dustGroup.children)) {
const mesh = node;
mesh.position.z -= speed * dt * 1.8;
mesh.position.x += Math.sin(elapsed * 4 + mesh.position.z) * 0.004;
mesh.material.opacity = Math.max(0, mesh.material.opacity - dt * 1.2);
if (mesh.position.z < playerZ - 240 || mesh.material.opacity <= 0) {
dustGroup.remove(mesh);
if (mesh.geometry)
mesh.geometry.dispose();
if (mesh.material)
mesh.material.dispose();
}
}
if (trail && trail.material)
trail.material.opacity = clamp(trail.material.opacity - dt * 0.018, 0, 0.72);
for (const node of Array.from(shardGroup.children)) {
const mesh = node;
if (mesh.life !== undefined) {
mesh.life -= dt;
mesh.scale.multiplyScalar(1 + dt * 4);
mesh.material.opacity = Math.max(0, mesh.life / 0.35);
if (mesh.life <= 0) {
shardGroup.remove(mesh);
if (mesh.geometry)
mesh.geometry.dispose();
if (mesh.material)
mesh.material.dispose();
}
}
}
}
function spawnDust() {
const p = new T.Mesh(new T.SphereGeometry(rand(0.008, 0.032), 5, 4), new T.MeshBasicMaterial({ color: Math.random() > 0.5 ? 0xffb877 : 0xa977d8, transparent: true, opacity: rand(0.16, 0.42), depthWrite: false }));
p.position.set(playerX + rand(-17, 17), playerY + rand(-8, 8), playerZ + rand(10, 54));
dustGroup.add(p);
}
function handleInputMove(clientX, clientY) {
const rect = app.getBoundingClientRect();
const nx = clamp((clientX - rect.left) / rect.width, 0, 1);
const ny = clamp((clientY - rect.top) / rect.height, 0, 1);
mouseX = (nx - 0.5) * 2;
mouseY = (ny - 0.5) * 2;
pointerX = clamp(mouseX * 8.4, -9.8, 9.8);
pointerY = clamp(-mouseY * 4.3, -5.5, 5.5);
}
function handleInput() {
if (phase !== "running")
return;
if (keys.has("ArrowLeft") || keys.has("a") || keys.has("A"))
pointerX = clamp(pointerX - 9.5 * 0.016, -9.8, 9.8);
if (keys.has("ArrowRight") || keys.has("d") || keys.has("D"))
pointerX = clamp(pointerX + 9.5 * 0.016, -9.8, 9.8);
if (keys.has("ArrowUp") || keys.has("w") || keys.has("W"))
pointerY = clamp(pointerY + 5.2 * 0.016, -5.5, 5.5);
if (keys.has("ArrowDown") || keys.has("s") || keys.has("S"))
pointerY = clamp(pointerY - 5.2 * 0.016, -5.5, 5.5);
if (keys.has(" ")) {
if (boost <= 0) {
boost = 100;
showToast("TURBO IGNITION");
sfxBoost();
shake = Math.min(1, shake + 0.28);
}
keys.delete(" ");
}
if (keys.has("Escape")) {
keys.delete("Escape");
endRun(true);
}
}
function updateHUD() {
scoreEl.textContent = String(Math.floor(score)).padStart(6, "0");
multiplierEl.textContent = "×" + multiplier.toFixed(1);
energyFillEl.style.width = energy.toFixed(1) + "%";
boostFillEl.style.width = boost.toFixed(1) + "%";
speedFillEl.style.width = (clamp(speed / 150, 0, 1) * 100).toFixed(1) + "%";
speedValueEl.textContent = Math.round(speed * 2.35) + " km/h";
boostStateEl.textContent = boost > 20 ? "TURBO ACTIVE · " + Math.round(boost) + "%" : boost > 0 ? "TURBO ARMING" : "TURBO OFFLINE";
boostStateEl.style.color = boost > 20 ? "#ffcf83" : "#9c78c8";
if (toastTimer > 0) {
toastTimer -= 1 / 60;
toastEl.style.opacity = clamp(toastTimer / 0.45, 0, 1).toFixed(2);
}
}
function showToast(message) {
toastEl.textContent = message;
toastEl.classList.remove("show");
void toastEl.offsetWidth;
toastEl.classList.add("show");
toastEl.style.opacity = "1";
toastTimer = 0.9;
}
function pulseMultiplier() {
multiplierEl.classList.remove("pulse");
void multiplierEl.offsetWidth;
multiplierEl.classList.add("pulse");
}
function endRun(byEscape = false) {
if (phase !== "running")
return;
phase = "dead";
keys.clear();
hud.classList.add("hidden");
gameOverScreen.classList.remove("hidden");
finalScoreEl.textContent = String(Math.floor(score));
finalSpeedEl.textContent = String(Math.round(maxSpeed * 2.35));
finalDistanceEl.textContent = (distance / 1000).toFixed(2);
const isRecord = Math.floor(score) > bestScore;
if (isRecord) {
bestScore = Math.floor(score);
localStorage.setItem("canyon-rush-best-score", String(bestScore));
}
bestSpeed = Math.max(bestSpeed, Math.round(maxSpeed * 2.35));
localStorage.setItem("canyon-rush-best-speed", String(bestSpeed));
bestScoreEl.textContent = String(bestScore).padStart(6, "0");
bestSpeedEl.textContent = String(bestSpeed).padStart(3, "0");
if (!byEscape) {
sfxCrash();
shake = 1;
}
}
function startGame() {
startScreen.classList.add("hidden");
gameOverScreen.classList.add("hidden");
hud.classList.remove("hidden");
if (scene !== composer && scene.add) {
const activeScene = scene;
scene = new T.Scene();
scene.background = new T.Color(0x1a0920);
scene.fog = new T.Fog(0x3c163a, 120, 520);
scene.add(ground);
scene.add(sun);
scene.add(new T.AmbientLight(0x7f56b8, 0.9));
scene.add(rim);
if (composer && T.RenderPass)
composer.addPass(new T.RenderPass(scene, camera));
activeScene && activeScene.traverse && activeScene.traverse((node) => {
if (node.isMesh && node.geometry)
node.geometry.dispose();
if (node.material && node.material.dispose)
node.material.dispose();
});
}
else {
scene = new T.Scene();
scene.background = new T.Color(0x1a0920);
scene.fog = new T.Fog(0x3c163a, 120, 520);
scene.add(ground);
scene.add(sun);
scene.add(new T.AmbientLight(0x7f56b8, 0.9));
scene.add(rim);
}
resetRun();
phase = "running";
ensureAudio();
updateHUD();
showToast("CANYON OPEN");
sfxLaunch();
}
function ensureAudio() {
if (audio.started)
return;
if (audio.ctx && audio.osc) {
try {
audio.osc.stop();
}
catch (_) { }
}
const AudioCtx = window.AudioContext || window.webkitAudioContext;
if (!AudioCtx)
return;
const ctx = new AudioCtx();
const master = ctx.createGain();
master.gain.value = 0.12;
master.connect(ctx.destination);
const osc = ctx.createOscillator();
const engineGain = ctx.createGain();
osc.type = "sawtooth";
osc.frequency.value = 42;
engineGain.gain.value = 0.035;
const filter = ctx.createBiquadFilter();
filter.type = "lowpass";
filter.frequency.value = 260;
osc.connect(filter);
filter.connect(engineGain);
engineGain.connect(master);
osc.start();
osc._engine = { ctx, osc, engineGain, filter, master };
audio = { started: true, ctx, osc, engineGain, filter, master };
}
function sfxLaunch() {
ensureAudio();
if (!audio.ctx)
return;
const ctx = audio.ctx;
const o = ctx.createOscillator();
const g = ctx.createGain();
o.type = "triangle";
o.frequency.setValueAtTime(110, ctx.currentTime);
o.frequency.exponentialRampToValueAtTime(520, ctx.currentTime + 0.45);
g.gain.setValueAtTime(0.0001, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.22, ctx.currentTime + 0.04);
g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.7);
o.connect(g);
g.connect(audio.ctx.destination);
o.start();
o.stop(ctx.currentTime + 0.72);
}
function sfxCollect() {
ensureAudio();
if (!audio.ctx)
return;
const ctx = audio.ctx;
const o = ctx.createOscillator();
const g = ctx.createGain();
o.type = "sine";
o.frequency.setValueAtTime(660, ctx.currentTime);
o.frequency.exponentialRampToValueAtTime(1320, ctx.currentTime + 0.12);
g.gain.setValueAtTime(0.12, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.22);
o.connect(g);
g.connect(ctx.destination);
o.start();
o.stop(ctx.currentTime + 0.24);
}
function sfxNearMiss(proximity) {
ensureAudio();
if (!audio.ctx)
return;
const ctx = audio.ctx;
const o = ctx.createOscillator();
const g = ctx.createGain();
const f = ctx.createBiquadFilter();
o.type = "sawtooth";
f.type = "bandpass";
f.frequency.value = proximity ? 1800 : 900;
f.Q.value = 4;
o.frequency.setValueAtTime(proximity ? 220 : 130, ctx.currentTime);
g.gain.setValueAtTime(0.0001, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(proximity ? 0.26 : 0.12, ctx.currentTime + 0.035);
g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.36);
o.connect(f);
f.connect(g);
g.connect(ctx.destination);
o.start();
o.stop(ctx.currentTime + 0.38);
}
function sfxBoost() {
ensureAudio();
if (!audio.ctx)
return;
const ctx = audio.ctx;
const o = ctx.createOscillator();
const g = ctx.createGain();
const f = ctx.createBiquadFilter();
o.type = "sawtooth";
f.type = "lowpass";
f.frequency.setValueAtTime(320, ctx.currentTime);
f.frequency.exponentialRampToValueAtTime(1800, ctx.currentTime + 0.3);
g.gain.setValueAtTime(0.0001, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.2, ctx.currentTime + 0.06);
g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.6);
o.connect(f);
f.connect(g);
g.connect(ctx.destination);
o.start();
o.stop(ctx.currentTime + 0.62);
}
function sfxCrash() {
ensureAudio();
if (!audio.ctx)
return;
const ctx = audio.ctx;
const o = ctx.createOscillator();
const g = ctx.createGain();
const f = ctx.createBiquadFilter();
o.type = "square";
o.frequency.setValueAtTime(170, ctx.currentTime);
o.frequency.exponentialRampToValueAtTime(36, ctx.currentTime + 0.55);
f.type = "lowpass";
f.frequency.value = 600;
g.gain.setValueAtTime(0.25, ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.58);
o.connect(f);
f.connect(g);
g.connect(ctx.destination);
o.start();
o.stop(ctx.currentTime + 0.6);
}
function collideWithWorld() {
if (phase !== "running")
return;
const bodyR = 0.66;
for (const chunk of activeChunks) {
for (const obs of chunk.obstacles) {
const dz = Math.abs(obs.z - playerZ);
if (dz > 0.58)
continue;
const dx = obs.x - playerX;
if (obs.kind === "monolith") {
const rx = Math.abs(dx) / obs.radius;
const ry = Math.abs(0 - playerY) / obs.halfY;
if (rx * rx + ry * ry < 1) {
endRun(false);
return;
}
}
else if (obs.kind === "debris") {
const d = Math.sqrt(dx * dx + (playerY - obs.mesh.position.y) ** 2) ** 2 + dz * dz;
if (d < bodyR * bodyR) {
endRun(false);
return;
}
}
}
if (chunk.id * 18 < playerZ - 4) {
const wallX = chunk.walls[0] ? chunk.walls[0].position.x : 0;
const wallHalf = Math.min(3.2, Math.abs(wallX) * 0.55);
const inside = Math.abs(playerX) < wallHalf;
const wallNearMiss = inside && energy > 0 && Math.random() < 0.004;
if (inside) {
if (wallNearMiss) {
nearMisses++;
multiplier = Math.min(10, multiplier + 0.8);
showToast("EDGE RUN ×" + multiplier.toFixed(1));
sfxNearMiss(true);
shake = Math.min(1, shake + 0.35);
boost = Math.min(100, boost + 12);
nearMissCooldown = 0.45;
}
endRun(false);
return;
}
}
}
}
function handleResize() {
camera.aspect = app.clientWidth / app.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(app.clientWidth, app.clientHeight);
if (composer)
composer.setSize(app.clientWidth, app.clientHeight);
}
function animate(now) {
const dt = Math.min(0.04, Math.max(0.001, (now - lastTime) / 1000));
lastTime = now;
elapsed += dt;
if (phase === "menu") {
player.position.set(Math.sin(elapsed * 0.7) * 4, Math.sin(elapsed * 1.4) * 2.4, -10 + Math.sin(elapsed * 0.4) * 4);
player.rotation.set(Math.sin(elapsed * 0.5) * 0.12, Math.sin(elapsed * 0.8) * 0.2, Math.sin(elapsed * 0.7) * 0.35);
updateCameraBase();
updateParticles(dt);
const menuOffset = -(menuChunkStart * 28);
renderer.render(scene, camera);
if (composer)
composer.render(dt);
}
else if (phase === "running") {
handleInput();
updatePlayer(dt);
updateSpeed(dt);
updateObstacles(dt);
updateParticles(dt);
collideWithWorld();
updateHUD();
updateCameraBase();
shake = Math.max(0, shake - dt * 0.9);
const sx = (Math.sin(elapsed * 39) + Math.sin(elapsed * 17.3)) * 0.018 * shake;
const sy = (Math.cos(elapsed * 31) + Math.sin(elapsed * 23)) * 0.014 * shake;
camera.position.x += sx;
camera.position.y += sy;
if (bloomPass)
bloomPass.strength = 0.8 + boost * 0.012;
if (trail)
trail.material.color.setHex(0xff6e55);
renderer.render(scene, camera);
if (composer)
composer.render(dt);
}
else {
updateParticles(dt);
updateCameraBase();
player.rotation.z += dt * 1.3;
player.position.y -= dt * 2.2;
renderer.render(scene, camera);
if (composer)
composer.render(dt);
}
frameCount++;
requestAnimationFrame(animate);
}
document.getElementById("start-button").addEventListener("click", startGame);
document.getElementById("restart-button").addEventListener("click", startGame);
window.addEventListener("pointermove", (event) => handleInputMove(event.clientX, event.clientY));
window.addEventListener("touchmove", (event) => {
if (event.touches.length)
handleInputMove(event.touches[0].clientX, event.touches[0].clientY);
}, { passive: true });
window.addEventListener("keydown", (event) => {
keys.add(event.key);
if (event.key === " " && phase === "running")
event.preventDefault();
if ((event.key === "Enter" || event.key === " ") && (phase === "menu" || phase === "dead"))
startGame();
if (event.key === "Escape" && phase === "running")
event.preventDefault();
});
window.addEventListener("keyup", (event) => keys.delete(event.key));
window.addEventListener("blur", () => keys.clear());
createScene();
bestScoreEl.textContent = String(bestScore).padStart(6, "0");
bestSpeedEl.textContent = String(bestSpeed).padStart(3, "0");
requestAnimationFrame(animate);
};
// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"bartowski/nex-agi_Nex-N2.5-mini-GGUF","provider":"brigid","persona":"typescript","sessionId":"03324f62-b4e8-43b6-a0f8-b167bd87cd77","tokensIn":9450725,"tokensOut":45253,"tokensTotal":9495978,"cost":0,"turns":162,"toolCalls":162,"failedToolCalls":50,"timestamp":"2026-09-14T17:11:30.190Z"} -->