Models 44

Claude Sonnet 5.5

by Anthropicreasoning defaultClean
Open in new window

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

How it went

Total time
2 min 22 s
Cost
$0.198
Output tokens
19,699
Spent thinking
8,695 tokens
Speed
141.3 tok/s
Console errors
0
First token after
2.4 s
First visible text after
1 min 23 s
Prompt tokens
649
Code size
18.4 KB
Finish reason
stop
Served by
Claude Platform on AWS
Model id
anthropic/claude-sonnet-5.5
Reasoning sent
model default
Max output
128,000 tokens
Generated
Nov 12, 2026

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 · 350 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>3D Aquarium</title>
<style>
  html,body{margin:0;height:100%;overflow:hidden;background:#04101c;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
  body{background:radial-gradient(ellipse at 50% 40%,#0d2b45 0%,#050d18 70%)}
  canvas{display:block;width:100%;height:100%;touch-action:none}
  .ui{position:fixed;pointer-events:none;color:#cfefff;text-shadow:0 1px 6px rgba(0,0,0,.6)}
  #title{top:16px;left:20px}
  #title h1{margin:0;font-size:20px;font-weight:600;letter-spacing:.04em}
  #title p{margin:4px 0 0;font-size:13px;opacity:.7}
  #hint{bottom:22px;left:50%;transform:translateX(-50%);padding:9px 18px;border-radius:999px;
        background:rgba(10,40,66,.55);border:1px solid rgba(160,225,255,.25);backdrop-filter:blur(6px);
        font-size:14px;white-space:nowrap;transition:opacity .4s}
  @media(max-width:520px){#hint{white-space:normal;text-align:center;width:80%;font-size:13px}#title h1{font-size:17px}}
</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 class="ui" id="title"><h1>Reef Tank</h1><p>Click to scare the fish</p></div>
<div class="ui" id="hint">Move your mouse near the fish — they swim away</div>
<script type="module">
import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({antialias:true, alpha:true});
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x0a4a73, 24, 60);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 200);

// ---------- helpers ----------
const rand = (a,b)=>a+Math.random()*(b-a);
function gradTex(stops, w=4, h=256){
  const c=document.createElement('canvas'); c.width=w; c.height=h;
  const x=c.getContext('2d'); const g=x.createLinearGradient(0,0,0,h);
  stops.forEach(s=>g.addColorStop(s[0],s[1])); x.fillStyle=g; x.fillRect(0,0,w,h);
  const t=new THREE.CanvasTexture(c); t.colorSpace=THREE.SRGBColorSpace; return t;
}

// ---------- tank dims ----------
const TW=20, TH=11, TD=10;

// water volume (gradient interior)
const waterTex = gradTex([[0,'#38b6e0'],[0.35,'#1580b5'],[1,'#052a47']]);
const water = new THREE.Mesh(new THREE.BoxGeometry(TW,TH,TD),
  new THREE.MeshBasicMaterial({map:waterTex, side:THREE.BackSide, fog:false}));
scene.add(water);

// glass
const glass = new THREE.Mesh(new THREE.BoxGeometry(TW+0.1,TH+0.1,TD+0.1),
  new THREE.MeshPhysicalMaterial({color:0xbfeaff, transparent:true, opacity:0.07, roughness:0, metalness:0, depthWrite:false, clearcoat:1}));
scene.add(glass);
const edges = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(TW+0.1,TH+0.1,TD+0.1)),
  new THREE.LineBasicMaterial({color:0xb6ecff, transparent:true, opacity:0.55, fog:false}));
scene.add(edges);

// table / base
const base = new THREE.Mesh(new THREE.BoxGeometry(TW+1.2,0.6,TD+1.2), new THREE.MeshStandardMaterial({color:0x0a0f18, roughness:0.5}));
base.position.y = -TH/2-0.35; scene.add(base);

// lights
scene.add(new THREE.HemisphereLight(0xb8ecff, 0x1b4a5e, 1.4));
const sun = new THREE.DirectionalLight(0xffffff, 2.2); sun.position.set(-4,12,8); scene.add(sun);
const fill = new THREE.PointLight(0x66ccff, 120, 40); fill.position.set(6,2,8); scene.add(fill);

// ---------- sand ----------
{
  const g = new THREE.PlaneGeometry(TW,TD,80,40); g.rotateX(-Math.PI/2);
  const p=g.attributes.position; const col=new Float32Array(p.count*3); const c=new THREE.Color();
  for(let i=0;i<p.count;i++){
    const x=p.getX(i), z=p.getZ(i);
    const h = Math.sin(x*0.5+1)*0.18+Math.sin(z*0.8+x*0.3)*0.14+Math.sin(x*2.1+z*1.7)*0.04 + (z<0?0.35:0)+Math.max(0,Math.sin(x*0.25-1)*0.3);
    p.setY(i,h);
    const v=0.85+Math.sin(x*3.1)*Math.sin(z*2.7)*0.08+Math.random()*0.06;
    c.setRGB(0.85*v,0.74*v,0.52*v); col[i*3]=c.r; col[i*3+1]=c.g; col[i*3+2]=c.b;
  }
  g.setAttribute('color',new THREE.BufferAttribute(col,3)); g.computeVertexNormals();
  const sand=new THREE.Mesh(g,new THREE.MeshStandardMaterial({vertexColors:true,roughness:1}));
  sand.position.y=-TH/2+0.4; scene.add(sand);
}
const FLOOR_Y = -TH/2+0.8;

// ---------- rocks & coral ----------
function rock(x,z,s,colorHex){
  const g=new THREE.DodecahedronGeometry(1,1); const p=g.attributes.position;
  for(let i=0;i<p.count;i++){ const k=1+Math.sin(p.getX(i)*3.1+p.getY(i)*2.3)*0.18+Math.cos(p.getZ(i)*4.1)*0.12; p.setXYZ(i,p.getX(i)*k,p.getY(i)*k*0.7,p.getZ(i)*k); }
  g.computeVertexNormals();
  const m=new THREE.Mesh(g,new THREE.MeshStandardMaterial({color:colorHex,roughness:0.9,flatShading:true}));
  m.scale.set(s*rand(1,1.4),s*rand(0.8,1.2),s*rand(0.9,1.3)); m.position.set(x,FLOOR_Y+s*0.3,z); m.rotation.y=rand(0,6); scene.add(m); return m;
}
rock(-7.2,-2.5,1.6,0x6b6a72); rock(-8.3,0.5,1.0,0x5a5a63); rock(-5.8,-3.6,1.1,0x7a7270);
rock(7.0,-2.8,1.8,0x66666e); rock(8.2,0.2,1.0,0x777078); rock(5.5,-3.8,1.0,0x5b5a60);
rock(0.5,-3.8,1.3,0x6d6b73); rock(2.8,3.4,0.6,0x7a7a80); rock(-3,3.6,0.55,0x6a6870);
const coralMats=[0xff5d8f,0xff8a3d,0xb44fe0].map(c=>new THREE.MeshStandardMaterial({color:c,roughness:0.6,flatShading:true}));
function coral(x,z,s,mi){
  const grp=new THREE.Group();
  for(let i=0;i<7;i++){
    const m=new THREE.Mesh(new THREE.IcosahedronGeometry(1,1),coralMats[mi]);
    const a=i/7*Math.PI*2; const r=i===0?0:0.6;
    m.position.set(Math.cos(a)*r,(i===0?0.6:0.2+Math.random()*0.5),Math.sin(a)*r);
    m.scale.setScalar(i===0?0.6:rand(0.3,0.45)); grp.add(m);
  }
  grp.position.set(x,FLOOR_Y,z); grp.scale.setScalar(s); scene.add(grp);
}
coral(-6.3,-1.0,1.2,0); coral(6.4,-0.8,1.3,2); coral(-1.8,-3.2,0.9,1); coral(3.8,-3.0,1.0,0); coral(-9,-3,1,1);

// ---------- seaweed ----------
const weeds=[];
const greens=[0x1f9d4d,0x2fbf5f,0x138a5a,0x58c248].map(c=>new THREE.MeshStandardMaterial({color:c,roughness:0.7}));
function weed(x,z,segs,mat){
  const root=new THREE.Group(); root.position.set(x,FLOOR_Y-0.15,z); scene.add(root);
  let parent=root; const len=0.85; const list=[];
  for(let i=0;i<segs;i++){
    const seg=new THREE.Group(); seg.position.y=i===0?0:len; parent.add(seg);
    const r0=0.16*(1-i/(segs+1)), r1=0.16*(1-(i+1)/(segs+1));
    const m=new THREE.Mesh(new THREE.CylinderGeometry(r1,r0,len,6),mat); m.scale.z=0.45; m.position.y=len/2; seg.add(m);
    list.push(seg); parent=seg;
  }
  weeds.push({list,phase:rand(0,6.28),amp:rand(0.08,0.14)});
}
for(let i=0;i<26;i++){
  const side=Math.random();
  let x = side<0.35? rand(-9.3,-5.5) : side<0.7? rand(5.5,9.3) : rand(-4,4);
  const z = rand(-4.4,-1.2) + (Math.abs(x)>5.5? rand(0,2.5):0);
  weed(x,z,Math.floor(rand(4,8)),greens[i%greens.length]);
}

// ---------- light rays ----------
const rays=[];
{
  const c=document.createElement('canvas'); c.width=64; c.height=256; const x=c.getContext('2d');
  const gv=x.createLinearGradient(0,0,0,256); gv.addColorStop(0,'rgba(255,255,255,1)'); gv.addColorStop(1,'rgba(255,255,255,0)');
  x.fillStyle=gv; x.fillRect(0,0,64,256);
  x.globalCompositeOperation='destination-in';
  const gh=x.createLinearGradient(0,0,64,0); gh.addColorStop(0,'rgba(255,255,255,0)'); gh.addColorStop(0.5,'rgba(255,255,255,1)'); gh.addColorStop(1,'rgba(255,255,255,0)');
  x.fillStyle=gh; x.fillRect(0,0,64,256);
  const tex=new THREE.CanvasTexture(c);
  for(let i=0;i<8;i++){
    const m=new THREE.Mesh(new THREE.PlaneGeometry(rand(1.5,3),11),
      new THREE.MeshBasicMaterial({map:tex,color:0xbfeaff,transparent:true,opacity:0.12,blending:THREE.AdditiveBlending,depthWrite:false,fog:false}));
    const bx=-9+i*2.6+rand(-1,1); m.position.set(bx,0,rand(-3.5,-1)); m.rotation.z=-0.22;
    rays.push({m,bx,ph:rand(0,6.28),sp:rand(0.2,0.5)}); scene.add(m);
  }
}

// ---------- water surface ----------
const surfGeo=new THREE.PlaneGeometry(TW,TD,40,20); surfGeo.rotateX(Math.PI/2);
const surface=new THREE.Mesh(surfGeo,new THREE.MeshStandardMaterial({color:0x9be7ff,transparent:true,opacity:0.35,roughness:0.1,metalness:0.2,side:THREE.DoubleSide,depthWrite:false}));
surface.position.y=TH/2-0.15; scene.add(surface);

// ---------- bubbles ----------
const NB=110;
const bubbles=new THREE.InstancedMesh(new THREE.SphereGeometry(1,10,8),
  new THREE.MeshPhysicalMaterial({color:0xffffff,transparent:true,opacity:0.38,roughness:0,metalness:0,depthWrite:false}),NB);
scene.add(bubbles);
const sources=[[-8,-3.2],[7.6,-3.4],[0.3,-4.2]];
const bdata=[];
for(let i=0;i<NB;i++){ bdata.push({}); resetBubble(bdata[i],true); }
function resetBubble(b,init){
  const s=sources[Math.floor(Math.random()*sources.length)];
  b.x=s[0]+rand(-0.15,0.15); b.z=s[1]+rand(-0.15,0.15);
  b.y=init? rand(FLOOR_Y,TH/2-0.3): FLOOR_Y+0.2;
  b.sp=rand(1.2,2.6); b.s=rand(0.05,0.17); b.ph=rand(0,6.28); b.bx=b.x;
}
const dummy=new THREE.Object3D();

// ---------- fish ----------
const SPECIES=[
 {n:7, size:0.75, speed:2.2, fin:0xff7a1a, col:(x,y)=>{ if(Math.abs(x-0.4)<0.12||Math.abs(x+0.3)<0.1) return [1,1,1]; return [1,0.42,0.07]; }},
 {n:5, size:0.95, speed:2.6, fin:0xffd21f, col:(x,y)=>{ if(Math.abs(y-0.15+x*0.25)<0.13) return [0.04,0.08,0.3]; return [0.1,0.42,0.9]; }},
 {n:7, size:0.7,  speed:2.3, fin:0xffb800, col:(x,y)=>[1,0.83,0.0]},
 {n:18,size:0.38, speed:3.0, fin:0xff5050, col:(x,y)=>{ if(y<0&&x<0.15) return [1,0.15,0.15]; if(y>0.1) return [0.1,0.95,0.9]; return [0.82,0.9,0.95]; }},
 {n:6, size:0.8,  speed:2.0, fin:0x8a3fd0, col:(x,y)=>[0.82,0.28,0.82]},
];
const bellyCol=[[1,0.9,0.8],[0.5,0.7,1],[1,0.97,0.75],[0.95,0.95,1],[1,0.8,0.96]];
const bodyGeos=SPECIES.map((sp,si)=>{
  const g=new THREE.SphereGeometry(1,22,16); const p=g.attributes.position; const cols=new Float32Array(p.count*3); const c=new THREE.Color();
  for(let i=0;i<p.count;i++){
    const x=p.getX(i), y=p.getY(i);
    let rgb=sp.col(x,y);
    const t=Math.max(0,Math.min(1,(-y-0.1)/0.8))*0.55; const b=bellyCol[si];
    rgb=[rgb[0]*(1-t)+b[0]*t, rgb[1]*(1-t)+b[1]*t, rgb[2]*(1-t)+b[2]*t];
    c.setRGB(rgb[0],rgb[1],rgb[2],THREE.SRGBColorSpace); cols[i*3]=c.r; cols[i*3+1]=c.g; cols[i*3+2]=c.b;
  }
  g.setAttribute('color',new THREE.BufferAttribute(cols,3)); g.scale(1,0.5,0.28); return g;
});
function shapeGeo(pts){ const s=new THREE.Shape(); s.moveTo(pts[0][0],pts[0][1]); for(let i=1;i<pts.length;i++) s.lineTo(pts[i][0],pts[i][1]); s.closePath(); return new THREE.ShapeGeometry(s); }
const tailGeo=shapeGeo([[0.05,0],[-0.5,0.5],[-0.8,0.62],[-0.62,0],[-0.8,-0.62],[-0.5,-0.5]]);
const dorsalGeo=shapeGeo([[0.4,0.4],[0.15,0.78],[-0.2,0.8],[-0.5,0.4]]);
const pelvicGeo=shapeGeo([[0.2,-0.4],[-0.05,-0.7],[-0.3,-0.4]]);
const pectGeo=new THREE.SphereGeometry(1,8,6); pectGeo.scale(0.22,0.05,0.12);
const eyeGeo=new THREE.SphereGeometry(0.1,10,8), pupilGeo=new THREE.SphereGeometry(0.058,8,6);
const eyeMat=new THREE.MeshStandardMaterial({color:0xffffff,roughness:0.2}), pupilMat=new THREE.MeshBasicMaterial({color:0x000000});
const bodyMat=new THREE.MeshStandardMaterial({vertexColors:true,roughness:0.35,metalness:0.15});
const finMats=SPECIES.map(s=>new THREE.MeshStandardMaterial({color:s.fin,roughness:0.5,side:THREE.DoubleSide,transparent:true,opacity:0.88}));

const BMIN=new THREE.Vector3(-TW/2+0.8,FLOOR_Y+0.9,-TD/2+0.8);
const BMAX=new THREE.Vector3(TW/2-0.8,TH/2-1.0,TD/2-0.8);
const fishes=[];
SPECIES.forEach((sp,si)=>{
  const cx=rand(-5,5), cy=rand(-1,2), cz=rand(-2,2);
  for(let i=0;i<sp.n;i++){
    const g=new THREE.Group();
    const body=new THREE.Mesh(bodyGeos[si],bodyMat); g.add(body);
    const tail=new THREE.Group(); tail.position.x=-0.9; const tm=new THREE.Mesh(tailGeo,finMats[si]); tm.position.x=0.0; tail.add(tm); g.add(tail);
    g.add(new THREE.Mesh(dorsalGeo,finMats[si]));
    g.add(new THREE.Mesh(pelvicGeo,finMats[si]));
    const pects=[];
    for(const s of [-1,1]){
      const e=new THREE.Mesh(eyeGeo,eyeMat); e.position.set(0.6,0.12,0.2*s); g.add(e);
      const pu=new THREE.Mesh(pupilGeo,pupilMat); pu.position.set(0.66,0.13,0.23*s); g.add(pu);
      const pf=new THREE.Mesh(pectGeo,finMats[si]); pf.position.set(0.25,-0.12,0.27*s); pf.userData.s=s; g.add(pf); pects.push(pf);
    }
    const sc=sp.size*rand(0.9,1.15); g.scale.setScalar(sc); g.rotation.order='YZX';
    const pos=new THREE.Vector3(cx+rand(-2,2),cy+rand(-1.5,1.5),cz+rand(-2,2));
    pos.clamp(BMIN,BMAX); g.position.copy(pos);
    const a=rand(0,6.28);
    const vel=new THREE.Vector3(Math.cos(a),rand(-0.2,0.2),Math.sin(a)).multiplyScalar(sp.speed);
    scene.add(g);
    fishes.push({g,tail,pects,pos,vel,si,sc,base:sp.speed*rand(0.9,1.1),ph:rand(0,6.28),fright:0,acc:new THREE.Vector3()});
  }
});

// ---------- pointer ----------
const ptr={x:0,y:0.1,tx:0,ty:0.1,last:-100,burst:false};
function setPtr(e){ const r=renderer.domElement.getBoundingClientRect(); ptr.tx=((e.clientX-r.left)/r.width)*2-1; ptr.ty=-(((e.clientY-r.top)/r.height)*2-1); ptr.last=performance.now()/1000; }
addEventListener('pointermove',setPtr);
addEventListener('pointerdown',e=>{ setPtr(e); ptr.burst=true; });

// cursor marker
const R=4.0;
const marker=new THREE.Group();
marker.add(new THREE.Mesh(new THREE.RingGeometry(R-0.07,R,72),new THREE.MeshBasicMaterial({color:0xcff4ff,transparent:true,opacity:0.28,blending:THREE.AdditiveBlending,depthWrite:false,fog:false})));
marker.add(new THREE.Mesh(new THREE.CircleGeometry(R,48),new THREE.MeshBasicMaterial({color:0x9fe6ff,transparent:true,opacity:0.05,blending:THREE.AdditiveBlending,depthWrite:false,fog:false})));
marker.add(new THREE.Mesh(new THREE.SphereGeometry(0.12,12,10),new THREE.MeshBasicMaterial({color:0xffffff,fog:false})));
scene.add(marker);

// ---------- resize ----------
let camDist=24;
function resize(){
  const w=innerWidth,h=innerHeight; renderer.setSize(w,h,false); renderer.domElement.style.width=w+'px'; renderer.domElement.style.height=h+'px';
  camera.aspect=w/h; camDist=Math.max(24,29/camera.aspect); camera.updateProjectionMatrix();
}
addEventListener('resize',resize); resize();

// ---------- loop ----------
const clock=new THREE.Clock();
const ro=new THREE.Vector3(), rd=new THREE.Vector3(), tmp=new THREE.Vector3(), cp=new THREE.Vector3();
const sep=new THREE.Vector3(), ali=new THREE.Vector3(), coh=new THREE.Vector3(), d3=new THREE.Vector3();
const hint=document.getElementById('hint');
let t=0;

function frame(){
  const dt=Math.min(clock.getDelta(),0.05); t+=dt;
  const now=performance.now()/1000;
  const auto = now-ptr.last>3;
  if(auto){ ptr.tx=Math.sin(t*0.45)*0.55+Math.sin(t*1.1)*0.12; ptr.ty=Math.sin(t*0.7+1)*0.3+0.05; }
  hint.textContent = auto ? 'Demo cursor active — move your mouse near the fish and they swim away' : 'Move your mouse near the fish — they swim away (click to scare them)';
  const k=1-Math.exp(-dt*(auto?3:10)); ptr.x+=(ptr.tx-ptr.x)*k; ptr.y+=(ptr.ty-ptr.y)*k;

  // camera parallax
  camera.position.set(ptr.x*2.5, 1.5+ptr.y*1.2, camDist);
  camera.lookAt(0,-0.3,0); camera.updateMatrixWorld();

  // pointer ray
  ro.copy(camera.position);
  rd.set(ptr.x,ptr.y,0.5).unproject(camera).sub(ro).normalize();
  const th=-ro.z/rd.z; marker.position.copy(ro).addScaledVector(rd,th);

  const burst=ptr.burst; ptr.burst=false;

  for(let i=0;i<fishes.length;i++){
    const f=fishes[i]; const a=f.acc.set(0,0,0);
    sep.set(0,0,0); ali.set(0,0,0); coh.set(0,0,0); let n=0;
    for(let j=0;j<fishes.length;j++){
      if(i===j) continue; const o=fishes[j]; if(o.si!==f.si) continue;
      d3.subVectors(f.pos,o.pos); const d=d3.length();
      if(d<4.5){ n++; ali.add(o.vel); coh.add(o.pos); if(d<1.2&&d>0.001) sep.addScaledVector(d3,1/(d*d)); }
    }
    if(n>0){ ali.divideScalar(n).sub(f.vel).multiplyScalar(0.8); coh.divideScalar(n).sub(f.pos).multiplyScalar(0.35); a.add(ali).add(coh).addScaledVector(sep,2.5); }
    // wander
    a.x+=Math.sin(t*0.5+f.ph)*1.2; a.y+=Math.sin(t*0.4+f.ph*1.7)*0.5; a.z+=Math.cos(t*0.45+f.ph*0.6)*1.2;
    // bounds
    const m=2.0, kb=4;
    if(f.pos.x<BMIN.x+m) a.x+=(BMIN.x+m-f.pos.x)*kb; else if(f.pos.x>BMAX.x-m) a.x-=(f.pos.x-BMAX.x+m)*kb;
    if(f.pos.y<BMIN.y+m*0.6) a.y+=(BMIN.y+m*0.6-f.pos.y)*kb; else if(f.pos.y>BMAX.y-m*0.6) a.y-=(f.pos.y-BMAX.y+m*0.6)*kb;
    if(f.pos.z<BMIN.z+m) a.z+=(BMIN.z+m-f.pos.z)*kb; else if(f.pos.z>BMAX.z-m) a.z-=(f.pos.z-BMAX.z+m)*kb;
    // mouse flee (distance to pointer ray)
    tmp.subVectors(f.pos,ro); const tt=Math.max(0,tmp.dot(rd)); cp.copy(ro).addScaledVector(rd,tt);
    d3.subVectors(f.pos,cp); const dist=d3.length();
    if(dist<R){
      const s=1-dist/R; d3.normalize();
      a.addScaledVector(d3,s*s*70+s*12);
      f.fright=Math.max(f.fright,s*1.2);
    }
    if(burst && dist<R*2){ d3.subVectors(f.pos,cp).normalize(); f.vel.addScaledVector(d3,(1-dist/(R*2))*9); f.fright=1.4; }
    f.fright*=Math.exp(-dt*1.2);
    // cruise
    const sp=f.vel.length()||0.01;
    const target=f.base*(1+f.fright*1.8);
    a.addScaledVector(f.vel,(target-sp)/sp*1.5);
    a.y-=f.vel.y*0.6;
    f.vel.addScaledVector(a,dt);
    const maxS=f.base*1.15+f.fright*f.base*2.6, minS=f.base*0.55;
    const s2=f.vel.length(); if(s2>maxS) f.vel.multiplyScalar(maxS/s2); else if(s2<minS&&s2>0.001) f.vel.multiplyScalar(minS/s2);
    f.pos.addScaledVector(f.vel,dt); f.pos.clamp(BMIN,BMAX);
    // orient
    const sp2=f.vel.length();
    const yaw=Math.atan2(-f.vel.z,f.vel.x), pitch=Math.asin(Math.max(-1,Math.min(1,f.vel.y/sp2)));
    f.g.position.copy(f.pos);
    const wig=Math.sin(t*(6+sp2*1.6)+f.ph);
    f.g.rotation.set(0,yaw+wig*0.06,pitch);
    f.tail.rotation.y=wig*(0.45+f.fright*0.3);
    for(const p of f.pects) p.rotation.z=Math.sin(t*5+f.ph)*0.5*p.userData.s*0+Math.sin(t*5+f.ph)*0.5;
  }

  // weeds
  for(const w of weeds){
    for(let i=0;i<w.list.length;i++){
      const s=w.list[i]; s.rotation.z=Math.sin(t*0.9+w.phase+i*0.55)*w.amp; s.rotation.x=Math.cos(t*0.7+w.phase+i*0.5)*w.amp*0.7;
    }
  }
  // bubbles
  for(let i=0;i<NB;i++){
    const b=bdata[i]; b.y+=b.sp*dt; if(b.y>TH/2-0.25) resetBubble(b,false);
    dummy.position.set(b.bx+Math.sin(t*2+b.ph)*0.12,b.y,b.z);
    const grow=b.s*(1+(b.y-FLOOR_Y)*0.04); dummy.scale.setScalar(grow); dummy.updateMatrix(); bubbles.setMatrixAt(i,dummy.matrix);
  }
  bubbles.instanceMatrix.needsUpdate=true;
  // rays
  for(const r of rays){ r.m.position.x=r.bx+Math.sin(t*r.sp+r.ph)*1.2; r.m.material.opacity=0.08+0.07*(0.5+0.5*Math.sin(t*r.sp*1.7+r.ph)); }
  // surface waves
  const sp=surfGeo.attributes.position;
  for(let i=0;i<sp.count;i++){ const x=sp.getX(i),z=sp.getZ(i); sp.setY(i,Math.sin(x*0.8+t*1.3)*0.06+Math.cos(z*1.1+t*1.1)*0.05); }
  sp.needsUpdate=true; surfGeo.computeVertexNormals();

  renderer.render(scene,camera);
  requestAnimationFrame(frame);
}
frame();
</script>
</body>
</html>
Reasoning trace (8,695 tokens)
I'm planning a single HTML file that uses Three.js through an import map, building a 3D aquarium with a glass tank, sand, plants, rocks, bubbles, light rays, and flocking fish that scatter from the mouse via raycasting. I also want a self-running cursor so the fish visibly scatter without any input, plus a small hint.

I'm laying out the fish with procedural geometry: an ellipsoid body with vertex colors that get lighter toward the belly, a tail pivot that animates, and a small dorsal fin and eyes. Each fish is a group of a few meshes, and the flocking rules cover separation, alignment, cohesion, wandering, and a strong perpendicular flee from the mouse ray.

I'm weighing how to handle the mouse marker: ray distance from fish to the cursor line, with the marker placed where the ray meets the z=0 plane. I'm also considering a 3D-distance alternative, but I'm leaning toward ray distance since it feels more natural. Next I'm sketching the auto-demo, where a virtual pointer takes over on a Lissajous path after 2.5 seconds without mouse movement.

Next I'm settling the scene's surfaces: a glass tank with a faint transparent shell and edge lines, a light blue gradient backdrop, a displaced sand floor, and dodecahedron rocks. For the seaweed, I'm weighing a simpler approach of swaying each blade as a curved tube group rather than a custom shader.

Next I'm setting up the seaweed as chains of nested segments that each sway with a slight phase offset, then adding bubbles as an instanced mesh rising from a few sources. Light rays will be additive gradient planes drifting from the top, and a translucent water surface will cap the tank.

I'm settling the lighting next: a hemisphere light, a directional light from above, and a point light, with tone mapping on the renderer and no shadows. For each fish, I'm setting its heading from the velocity so it faces its direction of travel, using yaw first and then pitch from the vertical component.

Smoothing the heading with a lerp on velocity is already handled, so I'm moving on to the tail. It's a flattened triangular shape in the xy plane, sized by the fish and pivoting at the rear of the body, with its rotation driven by a sine wave. The dorsal fin will be a similar flat shape on top of the body.

Still working out the fish species: five types with different colors and sizes, and I'm giving each one vertex colors on its body so the belly can be lighter. The clownfish gets white bands, and the other species get their own palettes, including a tail color that differs from the body.

Now I'm setting the fish scale to between 0.5 and 0.9, with the tank spanning about 20 wide, 11 tall, and 10 deep. The camera will sit near z 24 with a 45 degree field of view, pulled back further on narrow screens.

For the walls, I'll use a soft steering force that turns fish away as they get close, with normal speeds between 1.5 and 3 and a flee speed up to 7. The flee force points from the closest point on the mouse ray toward the fish, scaled by how close the fish is inside the radius.

I'm setting the mouse marker as a ring at the z=0 plane, sized to roughly match the flee radius, and I'm keeping its faint opacity and camera-facing orientation. Threatened fish get a higher speed cap that fades over time.

Now I'm setting up the pointer handling: it listens for movement and clicks, and if nothing has moved for about three seconds, a virtual cursor takes over on a slow Lissajous path. The pointer position is smoothed with a lerp toward its target. Next I'm planning the bubbles as an instanced mesh of translucent spheres that rise from a few aerator points and reset at the top.

Next I'm setting up the light rays as seven angled additive planes with a fading gradient, and planning the sand floor as a displaced plane sitting just above the bottom of the tank.

I'm setting up the tank's glass with a very faint, non-reflective material and light edge lines, plus a dark base frame and a dark table plane under it. For the backdrop, I'll use a CSS radial gradient behind a transparent renderer, and add a slightly tinted blue water volume with a gradient back wall to give the water some depth.

I'm adding a fog tint to the scene, mostly subtle, and I'm leaving the glass and the rays unfogged. The back wall will be a light cyan-to-deep-blue canvas gradient plane, and the side walls are already covered by the back-facing water box.

I'm working out how the water box should look. Rather than a flat tint on the walls, I'm thinking of mapping one gradient canvas texture across every face of a back-facing box, so the side walls show color instead of fading into the dark background.

I'm settling the camera at (0, 1.5, 25) looking at the origin, so the side walls show up as they recede from the front rim to the back. The glass is only on the front, and the fish stay inside the tank. For the seaweed, I'm starting to block out clusters near the back and sides with their bases around y -5, beginning with a cylinder segment.

Still working on the seaweed: I'm building each blade as a nested chain of tapered cylinder segments that sway with a small phase offset, around 24 blades of varying length, and I'm keeping the greens on a shared material.

I'm adding some rocks and a few pink coral spheres for detail. For the fish, I'm using a standard material with vertex colors and a bit of metalness, with a hemisphere light, a front-top directional light, and a point light under an ACES tone mapping setup. The eyes will be small white spheres placed on the sides of each fish's body, and I'm keeping the fish group uniformly scaled.

I'm placing the eyes on the sides of the body, checking that they sit near the ellipsoid surface, and giving each one a white sphere with a black pupil. The tail will pivot at the rear of the body and flare out, and the dorsal fin will be a flat shape along the top.

Still writing fish geometry...

Still working on the fish update loop: I'm computing separation, alignment, and cohesion among same-species neighbors, adding a wander term, nudging fish back from the tank bounds, and pushing them away from the mouse when they're threatened.

Still working on the fish update loop: I'm letting the fright level decay over time, clamping each fish's speed between a minimum and a maximum that rises when it's threatened, and then nudging the vertical velocity so the fish don't pitch too steeply.

Now I'm setting the mouse influence radius to 4 and the falloff to linear, and I'm adding the overlay UI with a title at the top and a hint at the bottom that changes when the demo cursor is running. For resizing, I'm adjusting the camera distance from the aspect ratio so the tank width still fits on narrow screens.

Still working on the aspect-ratio framing: I'm setting the camera distance so the tank fits the width on narrow screens, then adding a parallax effect where the camera shifts slightly with the pointer position.

Next I'm setting up the bubbles as an instanced mesh of translucent spheres, streaming up from an aerator point with a slight sway and resetting at the top. Then I'm checking whether the water surface plane above the tank is even visible from the camera's height.

Still working on the water surface: I'm checking whether the camera can see the underside of the top face from its position, and I'm considering a separate light surface plane with slow vertex waves instead of relying on the box's gradient.

Still working on the water surface: I'm weighing a standard material with recomputed normals against a simpler basic one, and I'm leaning toward the standard version since the vertex count is small enough to update each frame. Next up is the sand, using vertex colors with some noise so it reads as light tan with variation, and mounds rising up to about 0.6 units.

Still writing the water and light textures...

Still writing fish geometry...

Still writing fish colors...

Still writing fish colors...

I'm adding a click handler that sends a stronger scare through the fish, and I'm still settling how the ring marker and the ray radius should line up at the z=0 plane.

Still writing the fish update code: I'm setting the wander term per fish from its phase so the motion looks organic, and I'm finishing the click burst that pushes nearby fish away from the pointer.

Still writing the fish update code: I'm working out the wander and forward-drive terms, bounding the fish inside the tank with a steering force near the edges, and planning cohesion and alignment against same-species neighbors.

Still writing the fish update code: I'm settling the separation distance and limiting neighbor checks to same-species fish within a radius of about 4.