Models 44

Muse Ember

Open in new window

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

How it went

Total time
22.5 s
Cost
$0.0058
Output tokens
11,469
Spent thinking
3,181 tokens
Speed
562.2 tok/s
Console errors
0
First token after
2.1 s
First visible text after
10.4 s
Prompt tokens
441
Code size
24.0 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"high"}
Max output
943,718 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 · 440 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Lumina — 3D Aquarium • Fish Flee Your Cursor</title>
<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>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  html,body{width:100%;height:100%;overflow:hidden;background:#021a2a;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#eafcff}
  #app{position:fixed;inset:0}
  canvas{display:block;width:100%;height:100%}
  .vignette{position:fixed;inset:0;pointer-events:none;background:radial-gradient(ellipse at center,transparent 55%,rgba(0,10,20,.55) 100%);z-index:2}
  .top{position:fixed;left:18px;top:16px;z-index:5;pointer-events:none;max-width:520px}
  .eyebrow{display:flex;align-items:center;gap:8px;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:#7ee8ff;font-weight:800}
  .eyebrow::before{content:"";width:28px;height:2px;background:#36e6ff;border-radius:2px;box-shadow:0 0 12px #36e6ff}
  h1{font-size:42px;line-height:.95;letter-spacing:-.03em;margin-top:8px;text-shadow:0 4px 30px rgba(0,0,0,.5)}
  h1 span{color:#3ce8ff}
  .sub{margin-top:10px;font-size:14px;line-height:1.5;color:#c8f4ff;max-width:460px;text-shadow:0 2px 10px rgba(0,0,0,.6)}
  .pill{display:inline-flex;gap:6px;align-items:center;margin-top:12px;background:rgba(3,40,60,.55);border:1px solid rgba(125,230,255,.35);backdrop-filter:blur(10px);padding:8px 11px;border-radius:999px;font-size:12.5px;color:#dffaff}
  .pill b{color:#fff}
  .dot{width:9px;height:9px;border-radius:50%;background:#ffcf3f;box-shadow:0 0 12px #ffcf3f;animation:pulse 1.4s infinite}
  @keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.5)}}
  .badge{position:fixed;right:16px;top:16px;z-index:6;background:rgba(2,32,52,.65);border:1px solid rgba(125,230,255,.35);backdrop-filter:blur(12px);padding:10px 13px;border-radius:14px;text-align:right;min-width:170px}
  .badge .label{font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:#7ee8ff;font-weight:800}
  .badge .mode{font-size:15px;font-weight:900;margin-top:3px}
  .badge .hint{font-size:11px;color:#bfefff;margin-top:2px}
  .hud{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:6;width:min(920px,calc(100% - 24px));display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:center;background:rgba(2,30,50,.58);border:1px solid rgba(125,230,255,.28);backdrop-filter:blur(14px);padding:10px;border-radius:18px;box-shadow:0 18px 50px rgba(0,0,0,.35)}
  .stat{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:7px 11px;min-width:118px;text-align:center}
  .stat .k{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:#8fefff;font-weight:800}
  .stat .v{font-size:18px;font-weight:900;line-height:1.1}
  button{pointer-events:auto;cursor:pointer;border:0;border-radius:12px;padding:10px 14px;font-weight:900;font-size:13px;letter-spacing:.02em;transition:.18s}
  button:hover{transform:translateY(-1px);filter:brightness(1.1)}
  button:active{transform:translateY(0) scale(.98)}
  .primary{background:linear-gradient(135deg,#19d9ff,#1affc1);color:#00202e;box-shadow:0 8px 22px rgba(25,217,255,.35)}
  .ghost{background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.18)}
  .kbd{font-size:11px;opacity:.75;font-weight:600}
  .legend{position:fixed;right:16px;bottom:92px;z-index:5;background:rgba(2,30,50,.5);border:1px solid rgba(125,230,255,.22);backdrop-filter:blur(10px);border-radius:14px;padding:11px 12px;width:205px;pointer-events:none}
  .legend h3{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:#7ee8ff;margin-bottom:7px}
  .legend p{font-size:12.5px;line-height:1.5;color:#d8f7ff}
  .legend .row{display:flex;gap:8px;align-items:center;margin-top:7px;font-size:12px;color:#c8f4ff}
  .sw{width:12px;height:12px;border-radius:50%;flex:0 0 auto;box-shadow:0 0 10px rgba(0,0,0,.4)}
  @media(max-width:640px){
    h1{font-size:30px}.sub{font-size:12.5px;max-width:320px}
    .badge{display:none}.legend{display:none}
    .top{left:12px;top:12px}.stat{min-width:96px;padding:6px 8px}.stat .v{font-size:15px}
    button{padding:9px 10px;font-size:12px}
  }
</style>
</head>
<body>
<div id="app"></div>
<div class="vignette"></div>

<div class="top">
  <div class="eyebrow">Interactive 3D Aquarium</div>
  <h1>LUMINA<br><span>AQUARIUM</span></h1>
  <p class="sub">Move your cursor <b style="color:#fff">close to a fish</b> — it will sense you, panic and dart away. Click anywhere in the tank to startle them all.</p>
  <div class="pill"><span class="dot"></span><span>Live flee-radius <b>4.2 m</b> &nbsp;•&nbsp; Try moving slowly toward a school</span></div>
</div>

<div class="badge">
  <div class="label">Controller</div>
  <div class="mode" id="mode">🤖 AUTO DEMO</div>
  <div class="hint" id="modeHint">Virtual cursor is hunting fish.<br>Move your mouse to take over.</div>
</div>

<div class="legend">
  <h3>Inside the tank</h3>
  <p>28 fish • bubbles • kelp<br>coral &amp; god-rays</p>
  <div class="row"><span class="sw" style="background:#ff8c1a"></span> Clownfish</div>
  <div class="row"><span class="sw" style="background:#2f7bff"></span> Blue Tang</div>
  <div class="row"><span class="sw" style="background:#ff5aa5"></span> Pink Anthias</div>
  <div class="row"><span class="sw" style="background:#ffe93f"></span> Yellow Surgeon</div>
</div>

<div class="hud">
  <div class="stat"><div class="k">Fleeing now</div><div class="v" id="fleeCount">0 / 28</div></div>
  <div class="stat"><div class="k">Nearest fish</div><div class="v" id="nearest">—</div></div>
  <div class="stat"><div class="k">Panic level</div><div class="v" id="panic">CALM</div></div>
  <button class="primary" id="scatterBtn">💥 Scatter Fish <span class="kbd">[Space]</span></button>
  <button class="ghost" id="feedBtn">🍤 Drop Food <span class="kbd">[F]</span></button>
  <button class="ghost" id="autoBtn">🤖 Auto Tour: ON</button>
</div>

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

const app=document.getElementById('app');
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x042c44);
scene.fog=new THREE.FogExp2(0x07364f,0.028);

const camera=new THREE.PerspectiveCamera(56,innerWidth/innerHeight,0.1,100);
camera.position.set(0,1.4,17.5);

const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.toneMapping=THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure=1.15;
app.appendChild(renderer.domElement);

const TANK={x:14,y:5,z:6, floor:-5, ceil:5};

/* Lights */
scene.add(new THREE.AmbientLight(0x88ccff,0.7));
const sun=new THREE.DirectionalLight(0xbffbff,1.6); sun.position.set(4,12,6); scene.add(sun);
const teal=new THREE.PointLight(0x00ffe0,18,30); teal.position.set(-6,3,4); scene.add(teal);
const pinkLight=new THREE.PointLight(0xff6ec7,10,24); pinkLight.position.set(7,0,3); scene.add(pinkLight);

/* Tank glass */
const glass=new THREE.Mesh(new THREE.BoxGeometry(TANK.x*2,TANK.y*2+1,TANK.z*2), new THREE.MeshBasicMaterial({color:0x9fefff,transparent:true,opacity:0.045,side:THREE.BackSide,depthWrite:false}));
glass.position.y=0.2; scene.add(glass);
const edges=new THREE.LineSegments(new THREE.EdgesGeometry(glass.geometry), new THREE.LineBasicMaterial({color:0x66e6ff,transparent:true,opacity:0.35}));
edges.position.copy(glass.position); scene.add(edges);
const waterTop=new THREE.Mesh(new THREE.PlaneGeometry(TANK.x*2,TANK.z*2), new THREE.MeshBasicMaterial({color:0xbffbff,transparent:true,opacity:0.13,side:THREE.DoubleSide}));
waterTop.rotation.x=Math.PI/2; waterTop.position.y=TANK.ceil; scene.add(waterTop);

/* Sand floor */
const sandGeo=new THREE.PlaneGeometry(34,16,70,34);
sandGeo.rotateX(-Math.PI/2);
const pos=sandGeo.attributes.position;
for(let i=0;i<pos.count;i++){
  const x=pos.getX(i), z=pos.getZ(i);
  let y=Math.sin(x*0.55)*0.28+Math.cos(z*0.7)*0.22+Math.sin(x*0.16+z*0.3)*0.35;
  const edge=Math.max(Math.abs(x)/17,Math.abs(z)/8); y-=Math.max(0,edge-0.75)*1.4;
  pos.setY(i,y);
}
sandGeo.computeVertexNormals();
const sand=new THREE.Mesh(sandGeo,new THREE.MeshStandardMaterial({color:0xe8cf9a,roughness:1}));
sand.position.y=TANK.floor+0.2; scene.add(sand);

/* Rocks, coral, kelp */
const rockMat=new THREE.MeshStandardMaterial({color:0x5a6d7a,roughness:0.95,flatShading:true});
const coralMats=[0xff6b9d,0xff9f43,0xc56cf0,0xff7675,0x48dbfb].map(c=>new THREE.MeshStandardMaterial({color:c,roughness:0.6,emissive:c,emissiveIntensity:0.12}));
function rock(x,z,s){
  const m=new THREE.Mesh(new THREE.DodecahedronGeometry(s,0),rockMat);
  m.position.set(x,TANK.floor+0.4+s*0.25,z); m.rotation.set(Math.random()*3,Math.random()*3,Math.random()*3); m.scale.y=0.65; scene.add(m);
}
[[-11,-2,1.8],[-8,2,1.2],[-3,-4,1.5],[4,-3,1.3],[9,1,1.9],[11,-3,1.1],[2,3,1.0],[-10,3,0.9]].forEach(r=>rock(...r));

function coral(x,z,mat){
  const g=new THREE.Group();
  for(let i=0;i<7;i++){
    const h=0.5+Math.random()*1.1;
    const b=new THREE.Mesh(new THREE.CylinderGeometry(0.06,0.12,h,6),mat);
    b.position.set((Math.random()-0.5)*0.5,h/2,(Math.random()-0.5)*0.5);
    b.rotation.set((Math.random()-0.5)*0.6,0,(Math.random()-0.5)*0.6); g.add(b);
    const tip=new THREE.Mesh(new THREE.SphereGeometry(0.13,8,8),mat); tip.position.set(b.position.x+Math.sin(b.rotation.z)*-h*0.4,h+0.05,b.position.z); g.add(tip);
  }
  g.position.set(x,TANK.floor+0.5,z); scene.add(g);
}
coral(-9,-4,coralMats[0]); coral(8,-4,coralMats[1]); coral(11,3,coralMats[2]); coral(-12,2,coralMats[3]); coral(3,4,coralMats[4]);

const kelps=[];
const kelpMat=new THREE.MeshStandardMaterial({color:0x1db954,roughness:0.7,side:THREE.DoubleSide,transparent:true,opacity:0.92});
const kelpMat2=new THREE.MeshStandardMaterial({color:0x00b894,roughness:0.7,side:THREE.DoubleSide,transparent:true,opacity:0.92});
for(let i=0;i<26;i++){
  const side=i<13?-1:1;
  const x=side*(9+Math.random()*4.5), z=(Math.random()-0.5)*10, h=2.2+Math.random()*3.2;
  const geo=new THREE.PlaneGeometry(0.32,h,1,6); geo.translate(0,h/2,0);
  const m=new THREE.Mesh(geo, Math.random()>0.5?kelpMat:kelpMat2);
  m.position.set(x,TANK.floor+0.3,z); m.rotation.y=Math.random()*Math.PI; m.userData.phase=Math.random()*6; scene.add(m); kelps.push(m);
}

/* Light rays */
const rays=[];
for(let i=0;i<6;i++){
  const mat=new THREE.MeshBasicMaterial({color:0xbffaFF,transparent:true,opacity:0.055,blending:THREE.AdditiveBlending,side:THREE.DoubleSide,depthWrite:false});
  const m=new THREE.Mesh(new THREE.PlaneGeometry(1.6+Math.random()*1.5,11),mat);
  m.position.set(-10+i*4,1,-2+Math.random()*3); m.rotation.z=0.22; m.rotation.y=(Math.random()-0.5)*0.4; scene.add(m); rays.push(m);
}

/* Bubbles & plankton */
const bubbles=[];
const bubbleGeo=new THREE.SphereGeometry(0.09,8,8);
const bubbleMat=new THREE.MeshBasicMaterial({color:0xcfffff,transparent:true,opacity:0.5});
for(let i=0;i<90;i++){
  const m=new THREE.Mesh(bubbleGeo,bubbleMat);
  m.position.set((Math.random()-0.5)*26,-5+Math.random()*10,(Math.random()-0.5)*10);
  m.scale.setScalar(0.5+Math.random()*1.4); m.userData.speed=0.6+Math.random()*1.2; scene.add(m); bubbles.push(m);
}
const pGeo=new THREE.BufferGeometry(); const pCount=420; const pPos=new Float32Array(pCount*3);
for(let i=0;i<pCount;i++){pPos[i*3]=(Math.random()-0.5)*27;pPos[i*3+1]=(Math.random()-0.5)*9.5;pPos[i*3+2]=(Math.random()-0.5)*11;}
pGeo.setAttribute('position',new THREE.BufferAttribute(pPos,3));
const plankton=new THREE.Points(pGeo,new THREE.PointsMaterial({color:0xafffff,size:0.07,transparent:true,opacity:0.65}));
scene.add(plankton);

/* Fish */
function finShape(points,mat){
  const s=new THREE.Shape(); s.moveTo(points[0][0],points[0][1]);
  for(let i=1;i<points.length;i++) s.lineTo(points[i][0],points[i][1]); s.lineTo(0,0);
  return new THREE.Mesh(new THREE.ShapeGeometry(s),mat);
}
function makeFish(cfg){
  const g=new THREE.Group();
  const bodyMat=new THREE.MeshStandardMaterial({color:cfg.color,roughness:0.35,metalness:0.15});
  const accentMat=new THREE.MeshStandardMaterial({color:cfg.accent,roughness:0.5,side:THREE.DoubleSide,emissive:cfg.accent,emissiveIntensity:0.12});
  const body=new THREE.Mesh(new THREE.SphereGeometry(0.5,22,16),bodyMat);
  body.scale.set(0.62,0.88,1.65); g.add(body);
  const belly=new THREE.Mesh(new THREE.SphereGeometry(0.42,16,12),new THREE.MeshStandardMaterial({color:0xffffff,roughness:0.6}));
  belly.scale.set(0.5,0.55,1.35); belly.position.set(0,-0.16,0.08); g.add(belly);
  if(cfg.striped){
    [-0.28,0.18].forEach(z=>{
      const ring=new THREE.Mesh(new THREE.TorusGeometry(0.38,0.07,8,24),new THREE.MeshStandardMaterial({color:0xffffff,roughness:0.5}));
      ring.position.z=z; ring.scale.set(0.82,1.05,1); g.add(ring);
    });
  }
  const tail=new THREE.Group(); tail.position.z=-0.82;
  const tailMesh=finShape([[-0.05,0],[-0.75,0.55],[-0.55,0],[-0.75,-0.55]],accentMat);
  tailMesh.rotation.y=Math.PI/2; tail.add(tailMesh); g.add(tail);
  const dorsal=finShape([[0.25,0],[-0.15,0.38],[-0.45,0]],accentMat);
  dorsal.rotation.y=Math.PI/2; dorsal.position.set(0,0.38,-0.1); g.add(dorsal);
  const finGeo=new THREE.CircleGeometry(0.22,10);
  const finL=new THREE.Mesh(finGeo,accentMat); finL.position.set(0.32,-0.05,0.25); finL.rotation.y=Math.PI/2; g.add(finL);
  const finR=finL.clone(); finR.position.x=-0.32; finR.rotation.y=-Math.PI/2; g.add(finR);
  const eyeMat=new THREE.MeshBasicMaterial({color:0x08101a});
  const eyeW=new THREE.MeshBasicMaterial({color:0xffffff});
  [-1,1].forEach(s=>{
    const e=new THREE.Mesh(new THREE.SphereGeometry(0.075,8,8),eyeMat); e.position.set(s*0.28,0.14,0.58); g.add(e);
    const w=new THREE.Mesh(new THREE.SphereGeometry(0.028,6,6),eyeW); w.position.set(s*0.34,0.17,0.62); g.add(w);
  });
  g.scale.setScalar(cfg.size);
  return {g,tail,finL,finR};
}
const palette=[
 {color:0xff8c1a,accent:0xff6b00,striped:true,size:0.95},{color:0x2f7bff,accent:0xffe93f,striped:false,size:1.25},
 {color:0xff5aa5,accent:0xff2e88,striped:false,size:0.9},{color:0xffe93f,accent:0xff9f1a,striped:false,size:1.15},
 {color:0x00d2c6,accent:0x00ffb3,striped:false,size:1.05},{color:0xb388ff,accent:0x7c4dff,striped:false,size:1.0},
 {color:0xff6b6b,accent:0xff4757,striped:false,size:0.85},{color:0x70a1ff,accent:0xffffff,striped:false,size:1.1},
 {color:0x2ed573,accent:0x00d68f,striped:false,size:0.95},{color:0xffa502,accent:0xff7f50,striped:true,size:1.0}
];
const fish=[];
for(let i=0;i<28;i++){
  const cfg=palette[i%palette.length];
  const {g,tail,finL,finR}=makeFish(cfg);
  const p=new THREE.Vector3((Math.random()-0.5)*20,(Math.random()-0.5)*7,(Math.random()-0.5)*8);
  g.position.copy(p); scene.add(g);
  fish.push({g,tail,finL,finR,vel:new THREE.Vector3(Math.random()-0.5,0,Math.random()-0.5).normalize().multiplyScalar(1.6),
    target:new THREE.Vector3(p.x,p.y,p.z), panic:0, phase:Math.random()*10, cruise:1.2+Math.random()*0.9, wasPanicking:false});
}

/* Cursor indicator + nearest line + ripples */
const cursor=new THREE.Group();
const cs=new THREE.Mesh(new THREE.SphereGeometry(0.16,16,16),new THREE.MeshBasicMaterial({color:0xffde59}));
const ring=new THREE.Mesh(new THREE.RingGeometry(0.32,0.4,32),new THREE.MeshBasicMaterial({color:0xffde59,transparent:true,opacity:0.8,side:THREE.DoubleSide}));
cursor.add(cs,ring); scene.add(cursor);
const lineGeo=new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(),new THREE.Vector3()]);
const nearLine=new THREE.Line(lineGeo,new THREE.LineBasicMaterial({color:0xffde59,transparent:true,opacity:0.85}));
scene.add(nearLine);
const ripples=[];
function ripple(p,color=0xffde59){
  const m=new THREE.Mesh(new THREE.RingGeometry(0.85,1,40),new THREE.MeshBasicMaterial({color,transparent:true,opacity:0.9,side:THREE.DoubleSide,blending:THREE.AdditiveBlending,depthWrite:false}));
  m.position.copy(p); m.userData.life=1; scene.add(m); ripples.push(m);
}

/* Food */
const foods=[];
function dropFood(){
  for(let i=0;i<18;i++){
    const m=new THREE.Mesh(new THREE.SphereGeometry(0.09,6,6),new THREE.MeshStandardMaterial({color:0xffa502,emissive:0xff7b00,emissiveIntensity:0.5}));
    m.position.set(cursor.position.x+(Math.random()-0.5)*2, 4.6, cursor.position.z+(Math.random()-0.5)*2);
    scene.add(m); foods.push({m,life:9});
  }
  ripple(cursor.position,0xffa502);
}

/* Mouse / interaction */
const mouseNDC=new THREE.Vector2(0.4,0.2);
const raycaster=new THREE.Raycaster();
const planeZ=new THREE.Plane(new THREE.Vector3(0,0,1),0);
const mouseWorld=new THREE.Vector3(6,1,0);
let lastInteract=-9999, autoEnabled=true, isAuto=true, elapsed=0;
const clock=new THREE.Clock();

function updatePointer(e){
  const clientX=e.touches?e.touches[0].clientX:e.clientX;
  const clientY=e.touches?e.touches[0].clientY:e.clientY;
  mouseNDC.x=(clientX/innerWidth)*2-1; mouseNDC.y=-(clientY/innerHeight)*2+1;
  lastInteract=elapsed;
}
addEventListener('pointermove',updatePointer);
addEventListener('touchmove',updatePointer,{passive:true});
addEventListener('pointerdown',e=>{
  updatePointer(e);
  // compute world then scatter around it
  raycaster.setFromCamera(mouseNDC,camera);
  const hit=new THREE.Vector3(); raycaster.ray.intersectPlane(planeZ,hit);
  if(hit) ripple(hit,0x4df3ff);
  fish.forEach(f=>{
    const d=f.g.position.distanceTo(hit);
    if(d<7){ const dir=f.g.position.clone().sub(hit).normalize(); f.vel.add(dir.multiplyScalar((7-d)*0.7)); f.panic=1; }
  });
});
addEventListener('keydown',e=>{
  if(e.code==='Space'){e.preventDefault(); scatter();}
  if(e.key==='f'||e.key==='F') dropFood();
});
function scatter(){
  ripple(mouseWorld,0xff5aa5);
  fish.forEach(f=>{ f.vel.add(new THREE.Vector3((Math.random()-0.5), (Math.random()-0.5)*0.6,(Math.random()-0.5)).multiplyScalar(3)); f.panic=1; });
}
document.getElementById('scatterBtn').onclick=scatter;
document.getElementById('feedBtn').onclick=dropFood;
const autoBtn=document.getElementById('autoBtn');
autoBtn.onclick=()=>{ autoEnabled=!autoEnabled; autoBtn.textContent=autoEnabled?'🤖 Auto Tour: ON':'🤖 Auto Tour: OFF'; };

const fleeEl=document.getElementById('fleeCount'), nearEl=document.getElementById('nearest'), panicEl=document.getElementById('panic');
const modeEl=document.getElementById('mode'), modeHint=document.getElementById('modeHint');

const tmp=new THREE.Vector3(), desired=new THREE.Vector3(), steer=new THREE.Vector3();

function animate(){
  requestAnimationFrame(animate);
  const dt=Math.min(clock.getDelta(),0.033); elapsed+=dt;

  /* Determine mouse world */
  isAuto = autoEnabled && (elapsed-lastInteract>2.2);
  if(isAuto){
    mouseWorld.set(Math.sin(elapsed*0.55)*10.5, Math.sin(elapsed*0.85)*2.6+0.6, Math.cos(elapsed*0.38)*3.2);
    cursor.position.copy(mouseWorld);
    modeEl.textContent='🤖 AUTO DEMO'; modeHint.innerHTML='Virtual cursor is hunting fish.<br>Move your mouse to take over.';
  }else{
    raycaster.setFromCamera(mouseNDC,camera);
    const hit=new THREE.Vector3();
    if(raycaster.ray.intersectPlane(planeZ,hit)){
      hit.x=THREE.MathUtils.clamp(hit.x,-13.5,13.5); hit.y=THREE.MathUtils.clamp(hit.y,-4.5,4.8); hit.z=THREE.MathUtils.clamp(hit.z,-5.5,5.5);
      mouseWorld.lerp(hit,0.35); cursor.position.copy(mouseWorld);
    }
    modeEl.textContent='🎯 LIVE CONTROL'; modeHint.innerHTML='You are the predator now.<br>Hover close to a fish!';
  }
  cursor.rotation.x=elapsed*2; cursor.rotation.y=elapsed*1.4;
  const pulse=1+Math.sin(elapsed*5)*0.12; cursor.scale.setScalar(pulse);

  /* Fish AI */
  let fleeing=0, nearestDist=999, nearestFish=null;
  fish.forEach(f=>{
    const p=f.g.position;
    const distToMouse=p.distanceTo(mouseWorld);
    if(distToMouse<nearestDist){nearestDist=distToMouse; nearestFish=f;}
    const inFlee=distToMouse<4.2;
    if(inFlee) fleeing++;
    if(inFlee && !f.wasPanicking && Math.random()<0.3) ripple(p,0xffde59);
    f.wasPanicking=inFlee;
    f.panic += ((inFlee?1:0)-f.panic)*Math.min(1,dt*6);

    // wander target
    if(p.distanceTo(f.target)<0.6 || Math.random()<0.002){
      f.target.set((Math.random()-0.5)*24,(Math.random()-0.5)*8,(Math.random()-0.5)*9);
    }
    desired.copy(f.target).sub(p).normalize().multiplyScalar(f.cruise);
    // food seeking if calm
    if(foods.length && f.panic<0.3){
      let best=null,bd=999; foods.forEach(fd=>{const d=p.distanceTo(fd.m.position); if(d<bd){bd=d;best=fd;}});
      if(best && bd<10) desired.copy(best.m.position).sub(p).normalize().multiplyScalar(f.cruise*1.4);
    }
    // separation
    steer.set(0,0,0);
    fish.forEach(o=>{ if(o!==f){ const d=p.distanceTo(o.g.position); if(d<1.4 && d>0.001) steer.add(p.clone().sub(o.g.position).normalize().multiplyScalar((1.4-d)*1.2)); }});
    // bounds
    if(Math.abs(p.x)>12) steer.x -= Math.sign(p.x)*1.8;
    if(p.y>3.8) steer.y -= 1.6; if(p.y<-3.8) steer.y += 1.6;
    if(Math.abs(p.z)>4.8) steer.z -= Math.sign(p.z)*1.8;
    // flee — the star behaviour
    if(inFlee){
      const strength=1-distToMouse/4.2;
      tmp.copy(p).sub(mouseWorld).normalize(); tmp.y+=0.25; tmp.normalize();
      desired.copy(tmp).multiplyScalar((3.2+strength*5.5));
      // erratic zig-zag while panicking
      desired.x+=Math.sin(elapsed*14+f.phase)*strength*1.6;
      desired.y+=Math.cos(elapsed*11+f.phase)*strength*1.0;
    }
    desired.add(steer);
    const lerpK=0.045+f.panic*0.12;
    f.vel.lerp(desired,lerpK);
    // clamp speed
    const maxSp=2.2+f.panic*6.5;
    if(f.vel.length()>maxSp) f.vel.setLength(maxSp);
    if(f.vel.length()<0.4) f.vel.setLength(0.4);
    p.addScaledVector(f.vel,dt);
    p.x=THREE.MathUtils.clamp(p.x,-13.2,13.2); p.y=THREE.MathUtils.clamp(p.y,-4.4,4.6); p.z=THREE.MathUtils.clamp(p.z,-5.4,5.4);

    // orientation + swimming animation
    tmp.copy(p).add(f.vel); f.g.lookAt(tmp);
    const wagSpeed=6+f.panic*22;
    f.tail.rotation.y=Math.sin(elapsed*wagSpeed+f.phase)*0.55;
    f.finL.rotation.x=Math.sin(elapsed*wagSpeed*0.7+f.phase)*0.5;
    f.finR.rotation.x=-Math.sin(elapsed*wagSpeed*0.7+f.phase)*0.5;
    f.g.rotation.z+=Math.sin(elapsed*3+f.phase)*0.04*(1+f.panic*3);
    // occasional bubble burst when panicking
    if(f.panic>0.7 && Math.random()<0.08){
      const b=bubbles.find(b=>b.position.y>4); if(b){b.position.copy(p); b.userData.speed=2.5;}
    }
  });

  // nearest line
  if(nearestFish && nearestDist<5.5){
    nearLine.visible=true;
    const pts=[mouseWorld, nearestFish.g.position];
    lineGeo.setFromPoints(pts);
    nearLine.material.opacity=0.35+(1-nearestDist/5.5)*0.6;
  } else nearLine.visible=false;

  fleeEl.textContent=fleeing+' / '+fish.length;
  nearEl.textContent=nearestDist.toFixed(1)+' m';
  panicEl.textContent=fleeing>8?'PANIC!':fleeing>2?'ALARMED':'CALM';
  panicEl.style.color=fleeing>8?'#ff8fa3':fleeing>2?'#ffde59':'#7dffb0';

  /* Environment anim */
  kelps.forEach(k=>{ k.rotation.z=Math.sin(elapsed*0.9+k.userData.phase)*0.14; k.rotation.x=Math.cos(elapsed*0.7+k.userData.phase)*0.08; });
  rays.forEach((r,i)=>{ r.material.opacity=0.04+Math.sin(elapsed*0.6+i)*0.02; r.rotation.y+=dt*0.02; });
  bubbles.forEach(b=>{ b.position.y+=b.userData.speed*dt; b.position.x+=Math.sin(elapsed*2+b.position.z)*0.15*dt; if(b.position.y>5.1){b.position.y=-4.8;b.position.x=(Math.random()-0.5)*26;} });
  plankton.rotation.y=elapsed*0.015;
  waterTop.material.opacity=0.10+Math.sin(elapsed*1.2)*0.04;

  // food falling
  for(let i=foods.length-1;i>=0;i--){ const fd=foods[i]; fd.life-=dt; fd.m.position.y-=dt*0.45; fd.m.position.x+=Math.sin(elapsed*3+i)*0.1*dt; if(fd.life<=0){scene.remove(fd.m); foods.splice(i,1);} }

  // ripples
  for(let i=ripples.length-1;i>=0;i--){ const r=ripples[i]; r.userData.life-=dt*1.4; r.scale.setScalar(1+(1-r.userData.life)*3.5); r.material.opacity=Math.max(0,r.userData.life)*0.9; r.lookAt(camera.position); if(r.userData.life<=0){scene.remove(r); ripples.splice(i,1);} }

  // camera parallax
  const targetCamX=isAuto?Math.sin(elapsed*0.3)*1.2:mouseNDC.x*1.4;
  const targetCamY=1.4+(isAuto?Math.cos(elapsed*0.25)*0.5:mouseNDC.y*0.7);
  camera.position.x+=(targetCamX-camera.position.x)*0.03;
  camera.position.y+=(targetCamY-camera.position.y)*0.03;
  camera.lookAt(0,0.3,0);

  renderer.render(scene,camera);
}
animate();

addEventListener('resize',()=>{
  camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix();
  renderer.setSize(innerWidth,innerHeight);
});
</script>
</body>
</html>
Reasoning trace (3,181 tokens)
Building a single-file Threejs import map aquarium with procedural fish, mouse-avoidance behavior, and responsive 1280x800 to 375 viewport.Adding mouse-unprojected world target, flee steering, touch input, and HUD controls.Extending the scene with demo auto-mouse, environment assets, fish model, and UI controls.Adding steering, fleeing, and rendering logic for fish, particles, and UI interactions.