Models 44

Claude Haiku 5.5

by AnthropicReasoning5 console errors
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
1 min 37 s
Cost
$0.012
Output tokens
23,128
Spent thinking
13,253 tokens
Speed
243.1 tok/s
Console errors
5
First token after
1.9 s
First visible text after
1 min 8 s
Prompt tokens
649
Code size
18.1 KB
Finish reason
stop
Served by
Claude Platform on AWS
Model id
anthropic/claude-haiku-5.5
Reasoning sent
{"effort":"medium"}
Max output
32,000 tokens
Generated
Nov 10, 2026

Console errors

Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.

THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values. CylinderGeometry

THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values. CylinderGeometry

THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values. CylinderGeometry

THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values. CylinderGeometry

THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values. CylinderGeometry

What it was asked

The prompt
“a 3D aquarium with Three.js where the fish swim away when I move the mouse near them (Load Three.js only through this import map: <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script>)”
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 587 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Reef Aquarium 3D</title>
<style>
  html, body {
    margin: 0;
    height: 100%;
    overflow: hidden;
    background: #04304a;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  }
  canvas {
    display: block;
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
  }
  #title {
    position: fixed;
    left: 22px;
    top: 18px;
    color: #e8fbff;
    pointer-events: none;
    text-shadow: 0 2px 12px rgba(0,0,0,.55);
  }
  #title h1 {
    margin: 0;
    font-size: 22px;
    font-weight: 600;
    letter-spacing: .4px;
  }
  #title p {
    margin: 4px 0 0;
    font-size: 13px;
    opacity: .75;
  }
  #mode {
    position: fixed;
    right: 22px;
    top: 20px;
    font-size: 12px;
    color: #bfeeff;
    padding: 6px 12px;
    border-radius: 999px;
    background: rgba(0, 40, 60, .55);
    border: 1px solid rgba(150, 230, 255, .25);
    pointer-events: none;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    white-space: nowrap;
  }
  #start {
    position: fixed;
    left: 50%;
    bottom: 26px;
    transform: translateX(-50%);
    padding: 10px 20px;
    border-radius: 999px;
    background: rgba(0, 30, 50, .6);
    border: 1px solid rgba(150, 230, 255, .4);
    color: #d8f6ff;
    font-size: 14px;
    pointer-events: none;
    white-space: nowrap;
    transition: opacity .8s ease;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
  }
  #start.hide { opacity: 0; }
  @media (max-width: 480px) {
    #title h1 { font-size: 17px; }
    #title p { font-size: 11px; }
    #mode { font-size: 10px; right: 12px; top: 14px; }
    #title { left: 14px; top: 14px; }
    #start { font-size: 12px; bottom: 16px; }
  }
</style>
<script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script>
</head>
<body>
<div id="title">
  <h1>Reef Aquarium</h1>
  <p>Three.js · the fish swim away from your cursor</p>
</div>
<div id="mode">Demo · autopilot cursor</div>
<div id="start">Click or press a key to start</div>

<script type="module">
import * as THREE from 'three';

// ---------- Renderer / scene / camera ----------
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
document.body.prepend(renderer.domElement);

const scene = new THREE.Scene();
const WATER = 0x04304a;
scene.background = new THREE.Color(WATER);
scene.fog = new THREE.FogExp2(WATER, 0.035);

const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 200);
const tanHalf = Math.tan(THREE.MathUtils.degToRad(25));

function layout() {
  const w = innerWidth, h = innerHeight;
  const aspect = w / h;
  renderer.setSize(w, h);
  camera.aspect = aspect;
  // Keep the whole tank visible even on narrow phones
  const dist = Math.max(14, 8.4 / (tanHalf * aspect));
  camera.position.set(0, 0.8, dist);
  camera.lookAt(0, -0.3, 0);
  camera.updateProjectionMatrix();
  camera.updateMatrixWorld();
}
layout();
window.addEventListener('resize', layout);

// ---------- Lights ----------
scene.add(new THREE.HemisphereLight(0xa6eeff, 0x1b3b4a, 1.25));
scene.add(new THREE.AmbientLight(0x7fd6ee, 0.25));
const sun = new THREE.DirectionalLight(0xcff8ff, 1.5);
sun.position.set(4, 10, 6);
scene.add(sun);

// ---------- Tank ----------
const BX = 7.2, BY = 3.7, BZ = 2.8; // half extents of the water volume
const boxGeo = new THREE.BoxGeometry(BX * 2, BY * 2, BZ * 2);
scene.add(new THREE.LineSegments(
  new THREE.EdgesGeometry(boxGeo),
  new THREE.LineBasicMaterial({ color: 0x8ff0ff, transparent: true, opacity: 0.45 })
));
scene.add(new THREE.Mesh(boxGeo, new THREE.MeshBasicMaterial({
  color: 0x5ad8ff, transparent: true, opacity: 0.05, depthWrite: false, side: THREE.BackSide
})));
scene.add(new THREE.Mesh(boxGeo, new THREE.MeshBasicMaterial({
  color: 0x9ff4ff, transparent: true, opacity: 0.035, depthWrite: false, side: THREE.FrontSide
})));

// Tank base
const base = new THREE.Mesh(
  new THREE.BoxGeometry(BX * 2 + 0.6, 0.6, BZ * 2 + 0.6),
  new THREE.MeshStandardMaterial({ color: 0x0b1c26, roughness: 0.8 })
);
base.position.y = -BY - 0.3;
scene.add(base);

// Sand floor with gentle undulation
const sandGeo = new THREE.PlaneGeometry(BX * 2, BZ * 2, 60, 24);
sandGeo.rotateX(-Math.PI / 2);
{
  const p = sandGeo.attributes.position;
  for (let i = 0; i < p.count; i++) {
    const x = p.getX(i), z = p.getZ(i);
    const y = Math.sin(x * 0.8 + z * 0.5) * 0.07 + Math.cos(z * 1.7) * 0.05 + Math.sin(x * 2.3) * 0.02;
    p.setY(i, y);
  }
  sandGeo.computeVertexNormals();
}
const sand = new THREE.Mesh(sandGeo, new THREE.MeshStandardMaterial({ color: 0xd9bb82, roughness: 1 }));
sand.position.y = -BY + 0.02;
scene.add(sand);

// Rocks
function addRock(x, z, r) {
  const geo = new THREE.DodecahedronGeometry(1, 1);
  const m = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({
    color: 0x5b6b74, roughness: 0.9, flatShading: true
  }));
  m.scale.set(r * (1.1 + Math.random() * 0.4), r * (0.6 + Math.random() * 0.3), r * (0.9 + Math.random() * 0.3));
  m.position.set(x, -BY + m.scale.y * 0.35, z);
  m.rotation.set(Math.random() * 3, Math.random() * 3, Math.random() * 3);
  scene.add(m);
}
addRock(-5.4, -1.8, 0.9);
addRock(-4.2, -1.9, 0.5);
addRock(5.2, -1.6, 1.0);
addRock(3.9, -2.1, 0.55);
addRock(0.4, -2.3, 0.7);

// Coral clusters
const coralColors = [0xff6f91, 0xff9ac1, 0xb86bff, 0xffa36b];
function addCoral(x, z) {
  const group = new THREE.Group();
  for (let i = 0; i < 6; i++) {
    const s = 0.25 + Math.random() * 0.3;
    const m = new THREE.Mesh(
      new THREE.IcosahedronGeometry(s, 1),
      new THREE.MeshStandardMaterial({
        color: coralColors[(Math.random() * coralColors.length) | 0],
        roughness: 0.6, emissive: 0x220011, emissiveIntensity: 0.4
      })
    );
    m.position.set((Math.random() - 0.5) * 0.7, s * 0.5 + Math.random() * 0.2, (Math.random() - 0.5) * 0.5);
    m.scale.y = 0.8 + Math.random() * 0.8;
    group.add(m);
  }
  group.position.set(x, -BY, z);
  scene.add(group);
}
addCoral(-2.6, -1.4);
addCoral(1.8, -1.2);
addCoral(6.2, -0.4);

// Seaweed (vertex-animated)
const seaweeds = [];
const weedMat = new THREE.MeshStandardMaterial({ color: 0x2f9e5e, roughness: 0.7, side: THREE.DoubleSide });
for (let i = 0; i < 11; i++) {
  const h = 2.4 + Math.random() * 2.2;
  const geo = new THREE.CylinderGeometry(0.08, 0.16, h, 6, 14, true);
  geo.translate(0, h / 2, 0);
  const base0 = geo.attributes.position.array.slice();
  const mesh = new THREE.Mesh(geo, weedMat);
  const x = -6.6 + (i / 10) * 13.2 + (Math.random() - 0.5) * 0.6;
  const z = -2.3 + Math.random() * 1.6;
  mesh.position.set(x, -BY + 0.02, z);
  scene.add(mesh);
  seaweeds.push({ geo, base0, h, phase: Math.random() * 10, mesh });
}

// Light shafts from the surface
const shafts = [];
for (let i = 0; i < 5; i++) {
  const m = new THREE.Mesh(
    new THREE.PlaneGeometry(1.3, 14),
    new THREE.MeshBasicMaterial({
      color: 0xbff4ff, transparent: true, opacity: 0.05,
      blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide
    })
  );
  m.position.set(-5 + i * 2.5, 0, -1.6 - (i % 2) * 0.6);
  m.rotation.z = -0.22 + (i % 2) * 0.1;
  m.userData.phase = i * 1.7;
  scene.add(m);
  shafts.push(m);
}

// Bubbles
const BUBBLES = 90;
const bubbleMesh = new THREE.InstancedMesh(
  new THREE.SphereGeometry(0.06, 8, 6),
  new THREE.MeshBasicMaterial({ color: 0xcff8ff, transparent: true, opacity: 0.5, depthWrite: false }),
  BUBBLES
);
scene.add(bubbleMesh);
const bubbles = [];
for (let i = 0; i < BUBBLES; i++) {
  bubbles.push({
    x: (Math.random() - 0.5) * BX * 2,
    y: -BY + Math.random() * BY * 2,
    z: -BZ + Math.random() * BZ * 2,
    s: 0.6 + Math.random() * 0.9,
    ph: Math.random() * 10,
    sc: 0.5 + Math.random() * 1.2
  });
}

// ---------- Fish ----------
const PALETTES = [
  { body: 0xff7a2e, accent: 0xffffff }, // orange
  { body: 0x2f7fff, accent: 0x9fd8ff }, // blue
  { body: 0xffd23f, accent: 0xfff3b0 }, // yellow
  { body: 0xc9d6e0, accent: 0x7b8fa1 }, // silver
  { body: 0xff4d6d, accent: 0xffc2cf }, // red
  { body: 0x7be0a0, accent: 0xd9ffe6 }  // mint
];

function makeFish(pal) {
  const g = new THREE.Group();
  const bodyMat = new THREE.MeshStandardMaterial({ color: pal.body, roughness: 0.35, metalness: 0.05 });
  const finMat = new THREE.MeshStandardMaterial({
    color: pal.body, roughness: 0.5, transparent: true, opacity: 0.75, side: THREE.DoubleSide
  });
  const accentMat = new THREE.MeshStandardMaterial({ color: pal.accent, roughness: 0.4 });

  const body = new THREE.Mesh(new THREE.SphereGeometry(0.5, 24, 16), bodyMat);
  body.scale.set(1.3, 0.85, 0.4);
  g.add(body);

  // Accent band
  const band = new THREE.Mesh(new THREE.TorusGeometry(0.42, 0.035, 6, 24), accentMat);
  band.rotation.y = Math.PI / 2;
  band.scale.set(0.5, 1, 1);
  band.position.x = 0.1;
  g.add(band);

  // Tail (animated)
  const tail = new THREE.Group();
  tail.position.set(-0.92, 0, 0);
  const tailMesh = new THREE.Mesh(new THREE.ConeGeometry(0.42, 0.7, 6), finMat);
  tailMesh.rotation.z = -Math.PI / 2;
  tailMesh.scale.set(1, 1, 0.25);
  tail.add(tailMesh);
  g.add(tail);

  // Dorsal fin
  const dorsal = new THREE.Mesh(new THREE.ConeGeometry(0.22, 0.42, 4), finMat);
  dorsal.position.set(0.1, 0.45, 0);
  dorsal.scale.set(1, 1, 0.2);
  g.add(dorsal);

  // Eyes
  const eyeWhite = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.2 });
  const pupilMat = new THREE.MeshStandardMaterial({ color: 0x050505, roughness: 0.2 });
  for (const s of [-1, 1]) {
    const e = new THREE.Mesh(new THREE.SphereGeometry(0.09, 10, 8), eyeWhite);
    e.position.set(0.5, 0.12, 0.2 * s);
    g.add(e);
    const p = new THREE.Mesh(new THREE.SphereGeometry(0.045, 8, 6), pupilMat);
    p.position.set(0.56, 0.12, 0.27 * s);
    g.add(p);
  }

  g.userData.tail = tail;
  return g;
}

const N = 46;
const fishes = [];
const FLEE_R = 4.2;

for (let i = 0; i < N; i++) {
  const pal = PALETTES[i % PALETTES.length];
  const size = 0.7 + Math.random() * 0.5;
  const g = makeFish(pal);
  g.scale.setScalar(size);
  g.rotation.order = 'YZX';
  scene.add(g);
  fishes.push({
    g,
    tail: g.userData.tail,
    size,
    pos: new THREE.Vector3(
      (Math.random() - 0.5) * BX * 1.6,
      (Math.random() - 0.5) * BY * 1.3,
      (Math.random() - 0.5) * BZ * 1.6
    ),
    vel: new THREE.Vector3(Math.random() - 0.5, (Math.random() - 0.5) * 0.4, Math.random() - 0.5).multiplyScalar(0.9),
    acc: new THREE.Vector3(),
    phase: Math.random() * 100,
    speed: 0.8 + Math.random() * 0.4,
    fear: 0,
    yaw: 0,
    pitch: 0
  });
}

// ---------- Cursor indicator ----------
const ring = new THREE.Mesh(
  new THREE.RingGeometry(0.22, 0.3, 40),
  new THREE.MeshBasicMaterial({ color: 0xaef6ff, transparent: true, opacity: 0.6, side: THREE.DoubleSide, depthWrite: false })
);
const dot = new THREE.Mesh(
  new THREE.CircleGeometry(0.07, 16),
  new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.8, depthWrite: false })
);
const cursorGroup = new THREE.Group();
cursorGroup.add(ring, dot);
scene.add(cursorGroup);

// ---------- Input ----------
const ndc = new THREE.Vector2(0, 0);
const raycaster = new THREE.Raycaster();
let lastMove = -1e9;
let started = false;
const startEl = document.getElementById('start');
const modeEl = document.getElementById('mode');

window.addEventListener('pointermove', (e) => {
  ndc.set((e.clientX / innerWidth) * 2 - 1, -(e.clientY / innerHeight) * 2 + 1);
  lastMove = performance.now();
});
function startDemo() {
  started = true;
  startEl.classList.add('hide');
}
window.addEventListener('pointerdown', (e) => {
  ndc.set((e.clientX / innerWidth) * 2 - 1, -(e.clientY / innerHeight) * 2 + 1);
  lastMove = performance.now();
  startDemo();
});
window.addEventListener('keydown', startDemo);

// ---------- Simulation ----------
const tmpA = new THREE.Vector3();
const tmpB = new THREE.Vector3();
const closest = new THREE.Vector3();
const cursorPoint = new THREE.Vector3();
const autoTarget = new THREE.Vector3();
const UP = new THREE.Vector3(0, 1, 0);
let lastModeLabel = '';

function wrapAngle(a) {
  while (a > Math.PI) a -= Math.PI * 2;
  while (a < -Math.PI) a += Math.PI * 2;
  return a;
}

function updateFish(dt, t) {
  // Determine the pointer ray: live mouse if recently moved, else autopilot
  const live = (performance.now() - lastMove) < 2500;
  let o, d;

  if (live) {
    raycaster.setFromCamera(ndc, camera);
    o = raycaster.ray.origin;
    d = raycaster.ray.direction;
    // Point on the ray that sits on the z=0 plane (for the indicator)
    const tt = Math.abs(d.z) > 1e-4 ? -o.z / d.z : 10;
    cursorPoint.copy(o).addScaledVector(d, Math.max(1, tt));
  } else {
    autoTarget.set(
      Math.sin(t * 0.33) * 5.2 + Math.sin(t * 0.91) * 0.8,
      Math.sin(t * 0.47 + 1) * 2.4,
      Math.cos(t * 0.29) * 1.6
    );
    cursorPoint.copy(autoTarget);
    o = camera.position;
    d = tmpB.copy(autoTarget).sub(camera.position).normalize();
  }

  const label = live ? 'Live · move your mouse near the fish' : 'Demo · autopilot cursor';
  if (label !== lastModeLabel) {
    modeEl.textContent = label;
    lastModeLabel = label;
  }

  cursorGroup.position.copy(cursorPoint);
  cursorGroup.lookAt(camera.position);
  const pulse = 1 + Math.sin(t * 6) * 0.08;
  cursorGroup.scale.setScalar(live ? pulse : pulse * 0.8);
  ring.material.opacity = live ? 0.7 : 0.35;

  // Boids
  const NEIGH = 3.2, SEP = 1.2;
  for (let i = 0; i < N; i++) {
    const f = fishes[i];
    f.acc.set(0, 0, 0);
    let count = 0;
    const ali = tmpA.set(0, 0, 0);
    const coh = new THREE.Vector3();
    const sep = new THREE.Vector3();

    for (let j = 0; j < N; j++) {
      if (i === j) continue;
      const h = fishes[j];
      const dx = f.pos.x - h.pos.x, dy = f.pos.y - h.pos.y, dz = f.pos.z - h.pos.z;
      const d2 = dx * dx + dy * dy + dz * dz;
      if (d2 < NEIGH * NEIGH) {
        ali.add(h.vel);
        coh.add(h.pos);
        count++;
        if (d2 < SEP * SEP && d2 > 1e-6) {
          sep.x += dx / d2; sep.y += dy / d2; sep.z += dz / d2;
        }
      }
    }
    if (count > 0) {
      ali.divideScalar(count).sub(f.vel).multiplyScalar(0.6);
      coh.divideScalar(count).sub(f.pos).multiplyScalar(0.25);
      f.acc.add(ali).add(coh);
    }
    f.acc.addScaledVector(sep, 2.2);

    // Gentle wander
    f.acc.x += Math.sin(t * 0.7 + f.phase) * 0.3;
    f.acc.y += Math.cos(t * 0.5 + f.phase * 1.3) * 0.2;
    f.acc.z += Math.sin(t * 0.4 + f.phase * 0.7) * 0.15;

    // Soft tank walls
    const m = 0.9 * f.size;
    const lim = { x: BX - m, y: BY - m * 0.8, z: BZ - m };
    if (f.pos.x > lim.x) f.acc.x -= (f.pos.x - lim.x) * 7;
    if (f.pos.x < -lim.x) f.acc.x += (-lim.x - f.pos.x) * 7;
    if (f.pos.y > lim.y) f.acc.y -= (f.pos.y - lim.y) * 7;
    if (f.pos.y < -lim.y) f.acc.y += (-lim.y - f.pos.y) * 7;
    if (f.pos.z > lim.z) f.acc.z -= (f.pos.z - lim.z) * 7;
    if (f.pos.z < -lim.z) f.acc.z += (-lim.z - f.pos.z) * 7;

    // Flee from the cursor ray (closest point on ray in 3D)
    tmpA.copy(f.pos).sub(o);
    const tt = Math.max(0, tmpA.dot(d));
    closest.copy(o).addScaledVector(d, tt);
    const diff = tmpB.copy(f.pos).sub(closest);
    const dist = diff.length();
    if (dist < FLEE_R) {
      const k = 1 - dist / FLEE_R;
      if (dist < 1e-4) diff.set(Math.random() - 0.5, 1, Math.random() - 0.5);
      diff.normalize();
      f.acc.addScaledVector(diff, k * k * 34 + 2);
      f.fear = Math.max(f.fear, k);
    }
    f.fear = Math.max(0, f.fear - dt * 0.6);

    // Integrate
    f.vel.addScaledVector(f.acc, dt);
    const maxS = (1.1 + f.fear * 4.5) * f.speed;
    const minS = 0.55;
    const sp = f.vel.length();
    if (sp > maxS) f.vel.multiplyScalar(maxS / sp);
    else if (sp < minS) f.vel.multiplyScalar(minS / Math.max(sp, 1e-4));
    f.pos.addScaledVector(f.vel, dt);

    // Hard clamp (safety)
    f.pos.x = THREE.MathUtils.clamp(f.pos.x, -BX + 0.3, BX - 0.3);
    f.pos.y = THREE.MathUtils.clamp(f.pos.y, -BY + 0.4, BY - 0.3);
    f.pos.z = THREE.MathUtils.clamp(f.pos.z, -BZ + 0.3, BZ - 0.3);

    // Orientation
    const vx = f.vel.x, vy = f.vel.y, vz = f.vel.z;
    const horiz = Math.hypot(vx, vz);
    const tYaw = Math.atan2(-vz, vx);
    const tPitch = Math.atan2(vy, horiz);
    f.yaw += wrapAngle(tYaw - f.yaw) * 0.15;
    f.pitch += (tPitch - f.pitch) * 0.15;
    f.g.position.copy(f.pos);
    f.g.rotation.y = f.yaw;
    f.g.rotation.z = f.pitch;

    // Tail wag, faster when scared
    f.tail.rotation.y = Math.sin(t * (9 + f.fear * 14) * f.speed + f.phase) * (0.3 + f.fear * 0.2);
  }
}

function updateWeed(t) {
  for (const w of seaweeds) {
    const p = w.geo.attributes.position;
    const arr = w.base0;
    for (let i = 0; i < p.count; i++) {
      const bx = arr[i * 3], by = arr[i * 3 + 1], bz = arr[i * 3 + 2];
      const k = Math.pow(by / w.h, 1.6);
      const off = Math.sin(t * 1.3 + w.phase + by * 0.9) * 0.22 * k;
      const off2 = Math.cos(t * 0.9 + w.phase + by * 0.6) * 0.1 * k;
      p.setXYZ(i, bx + off, by, bz + off2);
    }
    p.needsUpdate = true;
    w.geo.computeVertexNormals();
  }
}

const dummy = new THREE.Object3D();
function updateBubbles(dt, t) {
  for (let i = 0; i < BUBBLES; i++) {
    const b = bubbles[i];
    b.y += b.s * dt * 0.9;
    b.x += Math.sin(t * 2 + b.ph) * 0.25 * dt;
    if (b.y > BY - 0.1) {
      b.y = -BY + 0.1;
      b.x = (Math.random() - 0.5) * BX * 2;
      b.z = -BZ + Math.random() * BZ * 2;
    }
    dummy.position.set(b.x, b.y, b.z);
    dummy.scale.setScalar(b.sc);
    dummy.updateMatrix();
    bubbleMesh.setMatrixAt(i, dummy.matrix);
  }
  bubbleMesh.instanceMatrix.needsUpdate = true;
}

function updateShafts(t) {
  for (const s of shafts) {
    s.material.opacity = 0.035 + Math.sin(t * 0.6 + s.userData.phase) * 0.02 + 0.02;
  }
}

// ---------- Loop ----------
const clock = new THREE.Clock();
let elapsed = 0;

function frame() {
  const dt = Math.min(clock.getDelta(), 0.05);
  elapsed += dt;
  updateFish(dt, elapsed);
  updateWeed(elapsed);
  updateBubbles(dt, elapsed);
  updateShafts(elapsed);
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(frame);
</script>
</body>
</html>
Reasoning trace (13,253 tokens)
I'm planning a single HTML file that uses Three.js through an import map, with a 3D aquarium where boid-style fish swim and steer away from the mouse via a 3D projection. The fish should be moving on first paint, with a self-playing demo behind a "Click or press a key to start" overlay that hides once the user interacts.

I'm settling on the autopilot details now: when the pointer has been idle for about two seconds, a virtual cursor traces a Lissajous path and drives the flee point, with a faint ripple showing where it is.

Still working out the fish orientation math: I'll set each fish's heading from its velocity, using a yaw from the horizontal components and a pitch from the vertical component, with the Euler order chosen so the head points along the direction of travel.

Still working out the Euler setup: with order 'YZX', the fish's pitch and yaw combine so the head follows the direction of travel. Next I'm sizing the tank bounds and choosing a low-opacity glass material for the walls.

I'm laying out the rest of the scene now: sand floor, rocks, swaying seaweed, and rising bubbles, with fog and a mix of ambient, directional, and hemisphere lights for depth. For the flee behavior, I'm casting a ray from the camera onto the z=0 plane and measuring each fish's 3D distance to that point.

Now I'm wiring up the flee behavior: for each fish I find the closest point on the camera ray and repel it from there, which keeps the math fully 3D. Then I'm adding an autopilot ray that follows a moving target whenever the pointer has been idle, plus a small glowing ring to mark that target. The last thing I'm sketching is the HUD title in the top-left corner.

Now I'm finishing the HUD: a small pill with the "Click or press a key to start" prompt that fades once the user interacts, plus a hint line and the title, while the demo keeps running regardless. Then I'm setting up resize and pixel ratio handling, and wiring pointer input so touch works the same way as the mouse. Next comes writing the fish constructor, starting with the body and the tail group.

Still writing the fish geometry, shaping the tail cone and fins and picking the colors. Next I'm working out the per-fish boid update: separation, alignment, cohesion, wandering, wall avoidance, and fleeing from the closest point on the camera ray.

Now I'm setting the flee force to fall off with distance from the cursor ray, and adding a fear value that boosts speed and triggers bursts. Velocity is capped by a base speed plus fear, with a minimum and some damping. Next I'm fixing the tank bounds with the floor just above the sand, and placing the camera so the 15-unit-wide tank fits the view, with narrower phone screens still needing a check.

Sizing the camera distance against the aspect ratio so narrow phone screens still see the full tank width, with a minimum of 14 units.

Still on the seaweed: I'm building each plant as a tall cylinder with many height segments, then shifting its vertices sideways over time so it sways. Next come the rocks, dodecahedrons jittered and set on the floor, then the sand plane with a warm base color and the glass walls drawn as low-opacity edges.

Still writing the HTML page, adding the light rays and water background, and setting up the fog.

Still writing the CSS for the title and the start pill, which fades out once the user interacts.

Still writing the page's CSS, adding the mode indicator on the right and a smaller layout for narrow phone screens.

Still writing the scene setup: I'm creating the renderer and the camera, and laying out the camera so the tank stays fully in view on narrow screens too.

Still writing the scene lighting and tank layout: a hemisphere light with a directional light from above, and walls bounded at fixed half-extents with the floor set just below the fish.

Still writing the sand floor: a plane sized to the tank with gentle wave displacement and a warm sandy material.

Still writing the scene objects: rocks as jittered dodecahedrons resting on the floor, then the seaweed as tall cylinders with their bases anchored at the sand.

Still writing the seaweed sway, and the coral clusters and light rays go in after that.

Still writing the light rays and bubbles, then setting up the fish palettes with their body and stripe colors.

Still writing the fish body: a sphere scaled into a flattened, elongated shape with its head pointing along the x axis.

Still writing the fish tail: a cone-shaped fin that I'm orienting so it sits behind the body.

Still working on the fish tail: I'm settling on a flattened cone for the fin, rotating it so the point faces the body, and deciding whether to set the rotation on the mesh or in the geometry.

Still working on the fish tail and dorsal fin: I'm flattening both along z so they read as thin fins, and weighing whether a stripe band should be a thin sphere shell or a cylinder wrapped around the body.

Still working on the fish stripes: I'm weighing a thin torus ring around the body, centered toward the head, and matching its radius to the ellipsoid's cross-section at that point.

Still working on the fish's tail: I'm figuring out how to orient and flatten the fin, and whether a stripe ring around the body should be sized to the body's cross-section at that spot.

Still working on the fish's eyes: I'm checking whether the two white spheres sit on the body's surface at that point along the x axis, given how the ellipsoid narrows toward the head.

Still working on the fish's eyes and pupils: I'm placing the whites just outside the body surface near the head, with the black pupils set slightly further out. Next I'm wrapping the fish parts into one group that takes a per-fish scale, then defining each fish's position, velocity, size, fear, and phase.

Still working on the autopilot's virtual cursor: I'm setting its target point to drift through the tank on sine and cosine curves, then building a ray from the camera toward it, so the fish respond to it the same way they do to the real mouse.

Still working on the virtual cursor: I'm finding where the autopilot's ray meets the z=0 plane by computing the intersection along it, so the cursor ring sits inside the tank.

Next I'm making the fear-driven repulsion from the cursor ray: the closest point on the ray to each fish sets the push direction, its strength grows as the fish gets nearer, and fear builds up and decays slowly.

Still working on the boid flocking step: I'm summing neighbor velocities and positions for alignment and cohesion, adding a separation push from nearby fish, and layering in a slow sinusoidal wander.

Still writing the boid update: I'm clamping the wall margin, letting fear decay over time, and capping each fish's speed by its fear level before orienting it along its velocity.

Still working on the fish's heading: I'm computing a target yaw and pitch from the velocity, wrapping the angle difference so the fish turns the short way, and easing toward it gently.

Still writing the fish details: I'm setting the count to 46 with varied sizes, adding a small bubble update that rises and resets at the bottom, and building the cursor ring with a faint autopilot opacity.

Now I'm wiring up the pointer handling: each move updates the normalized mouse position and stamps the time, so the scene switches to live mode for a few seconds after the last movement. I'm also planning the start overlay to dismiss on any click, touch, or key press.

Next I'm wiring the start overlay to hide on the first interaction, and the animation loop and resize handling come after that. The pointer-driven ray will be handled the same way as the autopilot's cursor ray, with the camera as its origin.

Still writing the main code: setting up the Three.js renderer, scene, and camera for the tank.

Still working on the camera setup: I'm computing the distance from the field of view and aspect ratio so the tank fills the frame, then positioning and aiming the camera at it. Fog is next, with its density chosen so the far end of the tank fades out around 14 units away.

Still writing the tank setup: I'm placing the glass box and its edge lines, with the fish sized and positioned to fit inside it.

Still writing the tank: I'm adding a faint front glass layer on top of the back faces, and I'm about to add a base box under the tank.

Still working on the tank's base and floor: I'm adding a dark base box under the tank and laying out the sand as a slightly wavy plane sitting just above the bottom of the glass.

Still writing the tank details: I'm making sure the sand surface sits just above the bottom of the glass and the fish stay clear of it. Next I'm sketching the rocks as dodecahedrons with jittered vertices so they look irregular.

Still writing the tank decorations: I'm dropping rocks in as dodecahedrons and making each one irregular by stretching it along different axes. Next up are coral clusters made of a few spheres in slightly different colors.

Still writing the fish body: I'm placing the seaweed, bubbles, and a population of 46 fish with varied sizes, and building each one with a position, velocity, speed, and phase.

Still writing the fish's loop: I'm setting up the update step that handles the cursor ray and the boid behavior together.

Still working on the fish's heading and the cursor: I'm computing where the ray meets the tank and switching between the live pointer and the autopilot's drifting target, so the ring and the fear response stay consistent.

Still writing the fish repulsion: I'm finding the point on the cursor ray closest to each fish, and if the fish is within the flee radius, I'm scaling the push by how close it is.

Still writing the fish repulsion: each fish gets pushed away from the cursor ray with a strength that rises as it gets closer, and its fear level climbs with it, so the scared fish also speed up.

Still working on the fish's turning: I'm picking the Euler order so the yaw and pitch compose correctly, and I'm checking how Three.js multiplies the rotations for that order.

Still working out the fish's heading: I'm confirming that the rotation order composes yaw and pitch the way I intended, so the fish's x-axis gets turned toward its velocity.

Still writing the rest of the scene: I'm adding light rays as a faint additive plane that fades in and out, then laying out the HTML overlays for the title, the mode indicator, and the start prompt. Pointer events on the window will drive the cursor, and the first click or key press will dismiss the start overlay and set the scene live.

Now I'm settling the start overlay and the mode indicator. The mode text can stay as it is, since if the user only clicks without moving, the scene just keeps running in demo mode.