Showcase

Time only moves when you move, built by Flint

NVFP4via DaBenchllama.cpp
Running live in your browserNew tab
Speed
113tok/s
First token included
Written
17Ktokens
Time to build
2m 31s
Flint's builds and scores
Show the code the model wrote
HTML27,820 characters
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SUPERHOT-STYLE TIME FPS</title>
<style>
  html,body{margin:0;height:100%;overflow:hidden;background:#07070a;font-family:monospace;color:#eee}
  #game{position:fixed;inset:0}
  #game canvas{display:block}
  #crosshair{position:fixed;left:50%;top:50%;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;pointer-events:none;z-index:10}
  #crosshair::after{content:'';position:absolute;left:-1px;top:-1px;width:2px;height:14px;background:#fff}
  #hud{position:fixed;inset:0;pointer-events:none;z-index:9}
  #hearts{position:absolute;top:20px;left:24px;font-size:28px;letter-spacing:4px;text-shadow:0 2px 8px #000}
  #enemiesLeft{position:absolute;top:20px;right:24px;font-size:28px;letter-spacing:4px;text-shadow:0 2px 8px #000}
  #timeDial{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);font-size:20px;letter-spacing:6px;color:#9fd8ff;text-shadow:0 0 12px #0099ff}
  #vignette{position:fixed;inset:0;background:radial-gradient(ellipse at center,transparent 55%,rgba(0,0,0,0.55) 100%);pointer-events:none;z-index:8}
  #grain{position:fixed;inset:0;pointer-events:none;z-index:8;opacity:0.06;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");mix-blend-mode:overlay}
  .overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.65);z-index:20;backdrop-filter:blur(4px)}
  .card{background:#0d0d12;border:1px solid #2a2a35;padding:40px 60px;text-align:center;box-shadow:0 0 60px rgba(0,150,255,0.15)}
  .card h1{font-size:42px;margin:0 0 12px;letter-spacing:8px;color:#fff}
  .card p{color:#9a9ab0;margin-bottom:24px}
  .btn{background:#0a84ff;color:#fff;border:none;padding:14px 34px;font-family:inherit;font-size:18px;letter-spacing:4px;cursor:pointer}
  #deathOverlay h1{color:#ff3b3b}
  #levelClearOverlay h1{color:#39ff8a}
  #demoToggle{position:fixed;bottom:80px;right:24px;z-index:30;background:rgba(10,10,16,0.7;border:1px solid #2a2a35;color:#9fd8ff;padding:10px 16px;font-family:inherit;letter-spacing:2px;cursor:pointer}
  @media (max-width:600px){#timeDial{font-size:14px}#hearts,#enemiesLeft{font-size:20px}}
</style>
</head>
<body>
<div id="game"></div>
<div id="crosshair"></div>
<div id="hud">
  <div id="hearts"></div>
  <div id="enemiesLeft"></div>
  <div id="timeDial">100%</div>
</div>
<div id="vignette"></div>
<div id="grain"></div>
<div id="startOverlay">
  <div class="card">
    <h1>SUPERHOT</h1>
    <p>TIME ONLY MOVES WHEN YOU MOVE</p>
    <button class="btn" id="startBtn">CLICK TO DEPLOY</button>
    <p style="margin-top:18px;font-size:12px;color:#777">WASD MOVE • MOUSE LOOK • CLICK SHOOT • B DEMO MODE</p>
  </div>
</div>
<div id="deathOverlay" class="overlay" style="display:none">
  <div class="card">
    <h1 id="deathText">YOU DIED</h1>
    <p>CLICK TO RESTART</p>
    <button class="btn" id="deathBtn">RETRY</button>
  </div>
</div>
<div id="levelClearOverlay" class="overlay" style="display:none">
  <div class="card">
    <h1 id="levelClearText">LEVEL CLEAR</h1>
    <p>CLICK TO CONTINUE</p>
    <button class="btn" id="levelClearBtn">NEXT</button>
  </div>
</div>
<div id="demoToggle">DEMO: OFF</div>
<script type="importmap">
{"imports":{"three":"https://unpkg.com/three@0.160.0/build/three.module.js","three/addons/":"https://unpkg.com/three@0.160.0/examples/jsm/"}}
</script>
<script type="module">
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';

const canvas = document.getElementById('game');
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio(Math.min(window.devicePixelRatio,1.5));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x0a0810));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;
renderer.outputColorSpace = THREE.SRGBColorSpace;
canvas.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x12101c, 0.028);
scene.background = new THREE.Color(0x0c0a12);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 120);
camera.position.set(0,1.6,6);

const clock = new THREE.Clock();
const raycaster = new THREE.Raycaster();
const rayDir = new THREE.Vector3();

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloom = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.85, 0.45, 0.82);
composer.addPass(bloom);
const gradeMat = new THREE.ShaderMaterial({
  uniforms: { tDiffuse: { value: null }, uTimeScale: { value: 1 } },
  vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); }`,
  fragmentShader: `uniform sampler2D tDiffuse; uniform float uTimeScale; varying vec2 vUv; void main(){ vec4 color = texture2D(tDiffuse, vUv); float desat = 1.0 - uTimeScale; float sat = clamp(desat,0.0,1.0); vec3 grey = vec3(dot(color.rgb, vec3(0.299,0.587,0.114))); color.rgb = mix(grey, color.rgb, sat); color.rgb *= uTimeScale; gl_FragColor = vec4(color.rgb,1.0); }`
});
const gradePass = new ShaderPass(gradeMat);
composer.addPass(gradePass);
composer.addPass(new OutputPass());

const keyState = { forward:false, back:false, left:false, right:false };
const player = { pos:new THREE.Vector3(0,1.6,6), yaw:0, pitch:0, health:3, bob:0 };
let timeScale = 0.05;
let targetTimeScale = 0.05;
let state = 'playing';
let demoMode = false;
let botActive = false;
let enemies = [];
let enemyGroups = [];
let projectiles = [];
let particles = [];
let tracers = [];
let flashLight = null;
let flashLife = 0;
let muzzleWorldPos = new THREE.Vector3();
let damageDir = new THREE.Vector3();
let shakeTime = 0;
let shakeOffset = new THREE.Vector3();

// ---------- textures ----------
function makeTex(size, draw){
  const c = document.createElement('canvas'); c.width=c.height=size; const x=c.getContext('2d'); draw(x);
  const t = new THREE.CanvasTexture(c); t.wrapS=t.wrapT=THREE.RepeatWrapping; t.repeat.set(12,12); t.anisotropy=8; return t;
}
const concrete = makeTex(256,(x)=>{ x.fillStyle='#2a2a2e'; x.fillRect(0,0,256,256); for(let i=0;i<8000;i++){ x.fillStyle=`rgba(${Math.random()*30},${Math.random()*30},${Math.random()*35},${Math.random()*0.5})`; x.fillRect(Math.random()*256,Math.random()*256,2,2); } });
const brick = makeTex(256,(x)=>{ x.fillStyle='#3a2420'; x.fillRect(0,0,256,256); x.fillStyle='#552a24'; for(let i=0;i<10;i++){ x.fillRect(0,i*25.6,256,12); } });
const metal = makeTex(256,(x)=>{ x.fillStyle='#33363c'; x.fillRect(0,0,256,256); x.strokeStyle='#555a63'; x.lineWidth=3; x.strokeRect(0,0,256,256); });
const asphalt = makeTex(256,(x)=>{ x.fillStyle='#17181c'; x.fillRect(0,0,256,256); for(let i=0;i<2000;i++){ x.fillStyle=`rgba(${Math.random()*20},${Math.random()*20},${Math.random()*25},${Math.random()*0.6})`; x.fillRect(Math.random()*256,Math.random()*256,2,2); } });

const matConcrete = new THREE.MeshStandardMaterial({map:concrete, roughness:0.92, metalness:0.05});
const matBrick = new THREE.MeshStandardMaterial({map:brick, roughness:0.9, metalness:0.02});
const matMetal = new THREE.MeshStandardMaterial({map:metal, roughness:0.4, metalness:0.8});
const matAsphalt = new THREE.MeshStandardMaterial({map:asphalt, roughness:0.95, metalness:0.0});
const matDark = new THREE.MeshStandardMaterial({color:0x15151c, roughness:0.85, metalness:0.2});
const matDark2 = new THREE.MeshStandardMaterial({color:0x1c1c26, roughness:0.8, metalness:0.25});
const matEmissive = new THREE.MeshStandardMaterial({color:0x111111, emissive:0x00ffcc, emissiveIntensity:2});

function box(w,h,d,color,mat){ const m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),mat); m.castShadow=true; m.receiveShadow=true; return m; }
function cyl(r,h,color,mat){ const m=new THREE.Mesh(new THREE.CylinderGeometry(r,r,h,16),mat); m.castShadow=true; m.receiveShadow=true; return m; }

// ---------- arena ----------
const ground = new THREE.Mesh(new THREE.PlaneGeometry(80,80), matAsphalt);
ground.rotation.x=-Math.PI/2; ground.position.y=0; ground.receiveShadow=true; scene.add(ground);
const wallMat = new THREE.MeshStandardMaterial({map:matBrick, roughness:0.95, metalness:0.02});
const walls = [
  box(80,8,2,0x222027,matConcrete), box(80,8,2,0x222027,matConcrete), // back
  box(2,8,80,0x222027,matConcrete), box(2,8,80,0x222027,matConcrete), // left
  box(2,8,80,0x222027,matConcrete), box(2,8,80,0x222027,matConcrete), // right
  box(60,10,2,0x1a191f,matConcrete), // front low wall
];
walls.forEach(w=>scene.add(w));
for(let i=0;i<6;i++){ const c=new THREE.Mesh(new THREE.CylinderGeometry(0.6,0.6,6,10),matMetal); c.position.set(-20+(i%3)*14,3,-20+(Math.floor(i/3))*14); c.castShadow=true; scene.add(c); }
const crateMat = new THREE.MeshStandardMaterial({map:matConcrete, roughness:0.9});
const crate = new THREE.Mesh(new THREE.BoxGeometry(6,3,6),crateMat); crate.position.set(12,-1.5,10); crate.rotation.y=0.3; crate.castShadow=true; scene.add(crate);
const crate2 = new THREE.Mesh(new THREE.BoxGeometry(4,2,4),crateMat); crate2.position.set(-14,-1,8); crate2.rotation.y=-0.7; crate2.castShadow=true; scene.add(crate2);

// ---------- lights ----------
const keyLight = new THREE.DirectionalLight(0xffc9a0, 2.4);
keyLight.position.set(6,10,4);
keyLight.castShadow = true;
keyLight.shadow.mapSize.set(2048,2048);
keyLight.shadow.camera.near=0.5; keyLight.shadow.camera.far=60;
keyLight.shadow.camera.left=-20; keyLight.shadow.camera.right=20; keyLight.shadow.camera.top=20; keyLight.shadow.camera.bottom=-20;
scene.add(keyLight);
const fillLight = new THREE.DirectionalLight(0x4455aa, 0.7); fillLight.position.set(-8,5,-6); scene.add(fillLight);
const hemi = new THREE.HemisphereLight(0x8a7ca8, 0x2a2118, 0.55); scene.add(hemi);
const signLight = new THREE.PointLight(0x00ffcc, 20, 12, 2); signLight.position.set(-12,4,6); scene.add(signLight);
const stripLight = new THREE.PointLight(0xff3366, 15, 10, 2); stripLight.position.set(10,3,-8); scene.add(stripLight);

// ---------- enemies ----------
class Enemy {
  constructor(id, pos){
    this.id = id;
    this.group = new THREE.Group();
    const skin = new THREE.MeshStandardMaterial({color:0x22222a, roughness:0.9});
    const dark = new THREE.MeshStandardMaterial({color:0x14141a, roughness:0.85});
    const accent = new THREE.MeshStandardMaterial({color:0x22222a, roughness:0.8, emissive:0x00ffcc, emissiveIntensity:0.6});
    const pelvis = new THREE.Mesh(new THREE.BoxGeometry(0.5,0.8,0.3),dark); pelvis.position.y=1.0; this.group.add(pelvis);
    const torso = new THREE.Mesh(new THREE.BoxGeometry(0.65,0.9,0.4),skin); torso.position.y=1.7; torso.castShadow=true; this.group.add(torso);
    const head = new THREE.Mesh(new THREE.BoxGeometry(0.35,0.5,0.35),skin); head.position.y=2.45; head.castShadow=true; this.group.add(head);
    const eye = new THREE.Mesh(new THREE.BoxGeometry(0.1,0.05,0.1),accent); eye.position.set(0,2.5,0.19); this.group.add(eye);
    const armL = new THREE.Mesh(new THREE.BoxGeometry(0.18,0.7,0.18),dark); armL.position.set(-0.45,1.75,0); armL.rotation.z=0.2; this.group.add(armL);
    const armR = new THREE.Mesh(new THREE.BoxGeometry(0.18,0.7,0.18),dark); armR.position.set(0.45,1.75,0); armR.rotation.z=-0.2; this.group.add(armR);
    const legL = new THREE.Mesh(new THREE.BoxGeometry(0.25,0.8,0.25),dark); legL.position.set(-0.15,0.4,0); legL.castShadow=true; this.group.add(legL);
    const legR = new THREE.Mesh(new THREE.BoxGeometry(0.25,0.8,0.25),dark); legR.position.set(0.15,0.4,0); legR.castShadow=true; this.group.add(legR);
    this.group.position.copy(pos);
    scene.add(this.group);
    this.state='patrol';
    this.hp=3;
    this.speed=2.2;
    this.radius=0.6;
    this.waypointIndex=0;
    this.windup=0;
    this.cooldown=0;
    this.attackTimer=0;
    this.eyes=new THREE.Vector3();
    this.fireDir=new THREE.Vector3();
  }
  update(dt){
    this.attackTimer -= dt;
    if(this.cooldown>0) this.cooldown -= dt;
    if(this.windup>0) this.windup -= dt;
    if(this.state==='patrol'){
      const wp = this.waypoints[this.waypointIndex];
      const dir = new THREE.Vector3().subVectors(wp, this.group.position); dir.y=0; const d=dir.length();
      if(d<0.5){ this.waypointIndex=(this.waypointIndex+1)%this.waypoints.length; }
      else this.moveDir(dir.normalize(), this.speed*dt);
      this.group.rotation.y = Math.atan2(-dir.x, -dir.z); // rough
    } else if(this.state==='chase'){
      const dir = new THREE.Vector3().subVectors(player.pos, this.group.position); dir.y=0; const d=dir.length();
      if(d<1.5) this.state='attack';
      else if(this.attackTimer<=0 && this.cooldown<=0){
        this.state='attack'; this.windup=0.55; this.cooldown=0.8;
      }
      if(this.state==='chase'){
        this.moveDir(dir.normalize(), this.speed*dt);
        this.group.rotation.y = Math.atan2(-dir.x, -dir.z);
      }
    } else if(this.state==='attack'){
      this.attackTimer -= dt;
      const dir = new THREE.Vector3().subVectors(player.pos, this.group.position); dir.y=0;
      if(this.windup>0){
        // telegraph
      } else {
        if(this.attackTimer<=0){
          this.fireDir = dir.normalize();
          const muzzle = this.group.position.clone().add(new THREE.Vector3(0,2.6,0.2));
          const proj = new Projectile(muzzle, this.fireDir, 6);
          projectiles.push(proj);
          scene.add(proj.mesh); scene.add(proj.light);
          this.state='chase';
          this.cooldown=1.2;
        }
      }
    }
    // simple animation
    const t = clock.elapsedTime*2;
    this.group.position.y = Math.sin(t)*0.02;
    this.group.rotation.x = Math.sin(t)*0.05;
    const legSwing = Math.sin(t)*0.6;
    this.group.children[5].rotation.x = legSwing; // legL rough index
    this.group.children[6].rotation.x = -legSwing;
  }
  moveDir(dir, speed){ this.group.position.addScaledVector(dir, speed); }
  takeDamage(dir){
    this.hp-=1;
    damageDir.copy(dir).normalize();
    shakeTime=0.25; shakeOffset.add(dir.clone().multiplyScalar(0.15));
    if(this.hp<=0){
      this.state='dead';
      this.kill();
    }
  }
  kill(){
    const embers = new THREE.Vector3();
    const count = 14;
    const posArr = new Float32Array(count*3);
    const colArr = new Float32Array(count*3);
    for(let i=0;i<count;i++){ posArr[i*3]=this.group.position.x+Math.random()*0.6; posArr[i*3+1]=this.group.position.y+Math.random()*0.6; posArr[i*3+2]=this.group.position.z+Math.random()*0.6; colArr[i*3]=1; colArr[i*3+1]=0.2; colArr[i*3+2]=0.05; }
    const pg = new THREE.Points(new THREE.BufferGeometry(), new THREE.PointsMaterial({size:0.15, vertexColors:true, blending:THREE.AdditiveBlending, transparent:true, depthWrite:false, map:new THREE.CanvasTexture((()=>{const c=document.createElement('canvas');c.width=c.height=64;c.getContext('2d').fillStyle='#fff';c.getContext('2d').fillRect(0,0,64,64);return c;}())),}));
    pg.setAttribute('position', new THREE.BufferAttribute(posArr,3));
    pg.setAttribute('color', new THREE.BufferAttribute(colArr,3));
    scene.add(pg); particles.push({mesh:pg, life:1, maxLife:1});
    this.group.visible=false;
    setTimeout(()=>scene.remove(this.group), 300);
  }
}
class Projectile {
  constructor(pos, dir, speed){
    this.pos = pos.clone();
    this.dir = dir.clone().normalize();
    this.speed = speed;
    this.life = 2;
    this.light = new THREE.PointLight(0x00ffcc, 30, 8, 2);
    this.light.position.copy(pos); this.light.color.setHex(0x00ffcc);
    const mesh = new THREE.Mesh(new THREE.SphereGeometry(0.12,8,8), matEmissive);
    mesh.position.copy(pos);
    this.mesh = mesh;
    this.trail = new THREE.Line(new THREE.BufferGeometry(), new THREE.LineBasicMaterial({color:0x00ffcc, transparent:true, opacity:0.8}));
    this.trail.visible = false;
    scene.add(this.mesh); scene.add(this.light, this.trail);
  }
  update(dt){
    this.pos.addScaledVector(this.dir, this.speed*dt);
    this.life -= dt;
    this.mesh.position.copy(this.pos);
    this.light.position.copy(this.pos);
    if(this.life<=0){ this.remove(); }
  }
  remove(){
    scene.remove(this.mesh); scene.remove(this.light); scene.remove(this.trail);
  }
}

// ---------- particles ----------
function spawnParticles(pos, color, count, speed){
  const posArr = new Float32Array(count*3); const colArr = new Float32Array(count*3); const velArr = new Float32Array(count*3);
  for(let i=0;i<count;i++){ posArr[i*3]=pos.x+Math.random()*0.2; posArr[i*3+1]=pos.y+Math.random()*0.2; posArr[i*3+2]=pos.z+Math.random()*0.2; colArr[i*3]=color[0]; colArr[i*3+1]=color[1]; colArr[i*3+2]=color[2]; velArr[i*3]=(Math.random()-0.5)*speed; velArr[i*3+1]=Math.random()*speed*0.5; velArr[i*3+2]=(Math.random()-0.5)*speed; }
  const pg = new THREE.Points(new THREE.BufferGeometry(), new THREE.PointsMaterial({size:0.08, vertexColors:true, blending:THREE.AdditiveBlending, transparent:true, depthWrite:false}));
  pg.setAttribute('position', new THREE.BufferAttribute(posArr,3)); pg.setAttribute('color', new THREE.BufferAttribute(colArr,3));
  scene.add(pg); particles.push({mesh:pg, life:1, maxLife:1, vel:velArr});
}
function updateParticles(dt){
  for(let i=particles.length-1;i>=0;i--){
    const p=particles[i]; p.life-=dt; if(p.life<=0){ scene.remove(p.mesh); particles.splice(i,1); continue; }
    const pos = p.mesh.geometry.attributes.position.array; const vel = p.vel;
    for(let j=0;j<pos.length/3;j++){ pos[j*3]+=vel[j*3]*dt; pos[j*3+1]+=vel[j*3+1]*dt; pos[j*3+2]+=vel[j*3+2]*dt; }
    p.mesh.geometry.attributes.position.needsUpdate=true;
  }
}

// ---------- tracers ----------
function addTracer(from, to){
  const geo = new THREE.BufferGeometry();
  const pos = new Float32Array([from.x,from.y,from.z, to.x,to.y,to.z]);
  geo.setAttribute('position', new THREE.BufferAttribute(pos,3));
  const line = new THREE.Line(geo, new THREE.LineBasicMaterial({color:0xffaa33, transparent:true, opacity:0.9}));
  line.frustumCulled=false; scene.add(line);
  tracers.push({mesh:line, life:0.25});
}
function updateTracers(dt){
  for(let i=tracers.length-1;i>=0;i--){
    const t=tracers[i]; t.life-=dt; if(t.life<=0){ scene.remove(t.mesh); tracers.splice(i,1); continue; }
    const arr = t.mesh.geometry.attributes.position.array; const to = new THREE.Vector3(arr[3],arr[4],arr[5]); const from = new THREE.Vector3(arr[0],arr[1],arr[2]);
    to.y += dt*2; // slight rise
    const arr2 = t.mesh.geometry.attributes.position.array; arr2[3]=to.x; arr2[4]=to.y; arr2[5]=to.z;
    t.mesh.geometry.attributes.position.needsUpdate=true;
  }
}

// ---------- audio ----------
let audioCtx = null;
let noiseBuffer = null;
function initAudio(){
  if(!audioCtx){ audioCtx = new (window.AudioContext||window.webkitAudioContext)(); }
  const len = audioCtx.sampleRate*2; const buf = audioCtx.createBuffer(1,len,audioCtx.sampleRate);
  const data = buf.getChannelData(0); for(let i=0;i<len;i++) data[i]=Math.random()*2-1;
  noiseBuffer = buf;
}
function noiseHit(timeScale){
  if(!audioCtx) return;
  const src = audioCtx.createBufferSource(); src.buffer=noiseBuffer; src.playbackRate.value=timeScale*1.8;
  const g = audioCtx.createGain(); g.gain.setValueAtTime(0.8,timeScale?audioCtx.currentTime:audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.2);
  src.connect(g).connect(audioCtx.destination); src.start();
}
function shootAudio(timeScale){
  if(!audioCtx) return;
  const o = audioCtx.createOscillator(); o.type='sawtooth'; o.frequency.setValueAtTime(900*timeScale,audioCtx.currentTime); o.frequency.exponentialRampToValueAtTime(100,timeScale?audioCtx.currentTime+0.1:audioCtx.currentTime+0.1);
  const g = audioCtx.createGain(); g.gain.setValueAtTime(0.4,audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.12);
  o.connect(g).connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime+0.12);
}
function ambientAudio(timeScale){
  if(!audioCtx){ initAudio(); }
  const src = audioCtx.createBufferSource(); src.buffer=noiseBuffer; src.loop=true; src.playbackRate.value=timeScale*0.5;
  const g = audioCtx.createGain(); g.gain.value=0.05;
  src.connect(g).connect(audioCtx.destination); src.start();
}

// ---------- input ----------
window.addEventListener('resize',()=>{ renderer.setSize(window.innerWidth,window.innerHeight); camera.aspect=window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); composer.setSize(window.innerWidth,window.innerHeight); });
window.addEventListener('keydown',e=>{ if(e.code==='KeyW') keyState.forward=true; if(e.code==='KeyS') keyState.back=true; if(e.code==='KeyA') keyState.left=true; if(e.code==='KeyD') keyState.right=true; if(e.code==='KeyB'){ demoMode=!demoMode; botActive=demoMode; document.getElementById('demoToggle').textContent='DEMO: '+(demoMode?'ON':'OFF'); } });
window.addEventListener('keyup',e=>{ if(e.code==='KeyW') keyState.forward=false; if(e.code==='KeyS') keyState.back=false; if(e.code==='KeyA') keyState.left=false; if(e.code==='KeyD') keyState.right=false; });
window.addEventListener('mousemove',e=>{ if(document.pointerLockElement===renderer.domElement){ player.yaw-=e.movementX*0.002; player.pitch-=e.movementY*0.002; player.pitch=Math.max(-1.5,Math.min(1.5,player.pitch)); } });
window.addEventListener('contextmenu',e=>e.preventDefault());
document.addEventListener('pointerlockchange',()=>{ if(!document.pointerLockElement){ document.getElementById('startOverlay').style.display='flex'; document.getElementById('deathOverlay').style.display='none'; document.getElementById('levelClearOverlay').style.display='none'; } });

document.getElementById('startBtn').onclick=()=>{ initAudio(); document.getElementById('startOverlay').style.display='none'; document.getElementById('deathOverlay').style.display='none'; document.getElementById('levelClearOverlay').style.display='none'; renderer.domElement.requestPointerLock(); };
document.getElementById('deathBtn').onclick=()=>{ location.reload(); };
document.getElementById('levelClearBtn').onclick=()=>{ location.reload(); };

// ---------- spawn enemies ----------
function spawnEnemies(){
  const positions = [
    [-12,0,2],[12,0,2],[-8,-1,14],[8,-1,14],[0,-1,22],[4,-1,30]
  ];
  positions.forEach((p,i)=>{
    const e = new Enemy(i, new THREE.Vector3(p[0],p[1],p[2]));
    enemies.push(e);
    enemyGroups.push(e.group);
  });
}
spawnEnemies();

// ---------- player update ----------
function updatePlayer(dt){
  const moveVec = new THREE.Vector3();
  if(keyState.forward) moveVec.z-=1;
  if(keyState.back) moveVec.z+=1;
  if(keyState.left) moveVec.x-=1;
  if(keyState.right) moveVec.x+=1;
  const len = Math.sqrt(moveVec.x*moveVec.x+moveVec.z*moveVec.z);
  if(len>0){
    moveVec.normalize();
    if(botActive){
      // simple bot: move toward nearest enemy
      let best=null,bestD=Infinity;
      for(const e of enemies){ if(e.state==='dead') continue; const d=e.group.position.distanceTo(player.pos); if(d<bestD){bestD=d;best=e;} }
      if(best){ const dir=new THREE.Vector3().subVectors(best.group.position, player.pos); dir.y=0; moveVec.copy(dir).normalize(); }
    }
    const forward = new THREE.Vector3(-Math.sin(player.yaw),0,-Math.cos(player.yaw));
    const right = new THREE.Vector3(Math.cos(player.yaw),0,-Math.sin(player.yaw));
    const worldMove = moveVec.x*right + moveVec.z*forward;
    player.pos.addScaledVector(worldMove, 5.5*dt);
  }
  player.pos.y = Math.max(1.0, player.pos.y);
  player.bob += dt*10;
  camera.position.copy(player.pos);
  camera.rotation.order='YXZ';
  camera.rotation.y=player.yaw;
  camera.rotation.x=player.pitch;
}

function shoot(){
  if(state!=='playing') return;
  const camPos = camera.position;
  rayDir.copy(0,0,-1).applyQuaternion(camera.quaternion);
  raycaster.set(camPos, rayDir);
  raycaster.far=100;
  const hits = raycaster.intersectObjects(enemyGroups, true);
  if(hits.length>0){
    const hit = hits[0];
    const enemy = hit.object.parent;
    if(enemy instanceof Enemy){
      const dir = new THREE.Vector3().subVectors(hit.point, camPos);
      enemy.takeDamage(dir);
      addTracer(muzzleWorldPos, hit.point);
      spawnParticles(hit.point, [1,0.4,0.05], 10, 3);
      noiseHit(timeScale);
      if(enemy.state==='dead'){
        // maybe level clear
      }
    }
  }
  // muzzle flash
  if(!flashLight){
    flashLight = new THREE.PointLight(0xffd0a0, 30, 10, 2);
    scene.add(flashLight);
  }
  flashLight.position.copy(camera.position).addScaledVector(rayDir,0.5);
  flashLight.intensity=30;
  flashLight.color.setHex(0xffd0a0);
  flashLife=0.03;
}

// ---------- HUD ----------
function updateHUD(){
  const heartsEl = document.getElementById('hearts');
  heartsEl.textContent = '♥'.repeat(Math.max(0,player.health)) + '♡'.repeat(Math.max(0,3-player.health));
  document.getElementById('enemiesLeft').textContent = 'HOSTILES: '+(enemies.filter(e=>e.state!=='dead').length);
  document.getElementById('timeDial').textContent = Math.round(timeScale*100)+'%';
}

// ---------- main loop ----------
function animate(){
  requestAnimationFrame(animate);
  const realDelta = Math.min(clock.getDelta(), 0.05);
  // time scale
  const anyInput = keyState.forward||keyState.back||keyState.left||keyState.right;
  if(anyInput || document.pointerLockElement===renderer.domElement){ targetTimeScale = 1; } else { targetTimeScale = 0.05; }
  timeScale += (targetTimeScale - timeScale) * Math.min(1, realDelta*8);
  timeScale = Math.max(0.05, Math.min(1.0, timeScale));
  const dt = realDelta * timeScale;

  if(state==='playing'){
    updatePlayer(realDelta);
    // bot AI
    if(botActive){
      // simple bot movement already in updatePlayer; bot shooting
      const botDir = new THREE.Vector3();
      let best=null,bestD=Infinity;
      for(const e of enemies){ if(e.state==='dead') continue; const d=e.group.position.distanceTo(player.pos); if(d<bestD){bestD=d;best=e;} }
      if(best){
        botDir.subVectors(best.group.position, player.pos); botDir.y=0; if(botDir.length()>0) botDir.normalize();
        rayDir.copy(0,0,-1).applyQuaternion(camera.quaternion);
        raycaster.set(camera.position, rayDir); raycaster.far=100;
        const hits = raycaster.intersectObjects([best.group], true);
        if(hits.length>0 && Math.random()<0.02){
          shoot();
        }
      }
    }
  }

  for(const e of enemies){ if(e.state!=='dead') e.update(dt); }
  for(let i=projectiles.length-1;i>=0;i--){ projectiles[i].update(dt); if(projectiles[i].life<=0) projectiles[i].remove(), projectiles.splice(i,1); }
  updateParticles(dt);
  updateTracers(dt);
  updateHUD();

  if(flashLife>0){ flashLife -= realDelta; if(flashLife<=0){ scene.remove(flashLight); flashLight=null; } }

  // render
  composer.render();
  gradePass.uniforms.uTimeScale.value = timeScale;
  renderer.render(scene, camera); // maybe not needed? composer includes render
}
// Actually use composer.render only. We'll remove duplicate.

requestAnimationFrame(animate);
clock.start();

window.addEventListener('resize',()=>{ composer.setSize(window.innerWidth,window.innerHeight); });

</script>
</body>
</html>

Same prompt, other models

6 more
Compare side by side

One email when a new model lands on the board or in the showcase. Unsubscribe anytime.