Interactive 3D aquariumOne-line
Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 18.7 s
- Cost
- $0.0050
- Output tokens
- 9,858
- Spent thinking
- 2,302 tokens
- Speed
- 583.4 tok/s
- Console errors
- 0
- First token after
- 1.8 s
- First visible text after
- 7.6 s
- Prompt tokens
- 441
- Code size
- 22.6 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
{"effort":"medium"}- 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 · 323 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>3D Aquarium — Fish Flee From Your Cursor</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#02101f;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#eaf6ff}
#app{position:fixed;inset:0}
canvas{display:block}
.panel{position:fixed;top:14px;left:14px;z-index:10;max-width:340px;background:linear-gradient(160deg,rgba(4,32,58,.78),rgba(3,18,36,.62));backdrop-filter:blur(10px);border:1px solid rgba(125,211,252,.35);border-radius:18px;padding:14px 16px 12px;box-shadow:0 10px 40px rgba(0,0,0,.45)}
.panel h1{margin:0;font-size:22px;letter-spacing:.02em;line-height:1.1}
.panel h1 span{font-size:19px}
.panel p{margin:7px 0 0;font-size:13px;line-height:1.45;color:#c8e8ff}
.panel p b{color:#7dd3fc}
.badge{display:inline-flex;align-items:center;gap:6px;margin-top:9px;background:rgba(14,116,144,.45);border:1px solid rgba(125,211,252,.4);padding:5px 9px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}
.dot{width:8px;height:8px;border-radius:50%;background:#34d399;box-shadow:0 0 10px #34d399}
.stats{position:fixed;top:14px;right:14px;z-index:10;display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}
.stat{background:rgba(2,20,38,.72);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.16);border-radius:14px;padding:9px 12px;min-width:92px;text-align:center}
.stat strong{display:block;font-size:22px;line-height:1;color:#fff}
.stat small{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:#9fd8ff;font-weight:700}
.stat.hot{border-color:#fda4af;background:rgba(80,10,25,.72)}
.stat.hot strong{color:#fda4af}
.controls{position:fixed;left:50%;bottom:14px;transform:translateX(-50%);z-index:10;display:flex;gap:8px;flex-wrap:wrap;justify-content:center;padding:0 10px;width:min(920px,100%)}
button{appearance:none;border:1px solid rgba(125,211,252,.45);background:rgba(3,28,52,.82);color:#e0f2fe;font-weight:800;font-size:12.5px;padding:10px 14px;border-radius:999px;cursor:pointer;backdrop-filter:blur(8px);box-shadow:0 6px 20px rgba(0,0,0,.35);transition:.15s}
button:hover{transform:translateY(-1px);background:#075985;color:#fff}
button.active{background:#0ea5e9;color:#fff;border-color:#7dd3fc}
.hint{position:fixed;bottom:66px;left:50%;transform:translateX(-50%);z-index:9;background:rgba(2,15,30,.68);border:1px solid rgba(255,255,255,.14);padding:7px 13px;border-radius:999px;font-size:12px;white-space:nowrap;color:#dbeafe;backdrop-filter:blur(6px)}
.hint b{color:#fde68a}
.pop{position:fixed;z-index:11;pointer-events:none;font-size:20px;font-weight:900;color:#fde68a;text-shadow:0 2px 10px rgba(0,0,0,.8);transform:translate(-50%,-50%);animation:pop .9s ease-out forwards}
@keyframes pop{0%{opacity:0;transform:translate(-50%,-30%) scale(.5)}25%{opacity:1;transform:translate(-50%,-70%) scale(1.3)}100%{opacity:0;transform:translate(-50%,-190%) scale(1)}}
.legend{position:fixed;right:14px;bottom:66px;z-index:9;background:rgba(2,15,30,.62);border:1px solid rgba(255,255,255,.12);border-radius:12px;padding:8px 10px;font-size:11px;line-height:1.5;color:#cbd5e1}
.legend i{display:inline-block;width:22px;height:8px;border-radius:99px;vertical-align:middle;margin-right:6px}
@media(max-width:640px){
.panel{max-width:calc(100vw - 28px);padding:11px 13px}.panel h1{font-size:18px}.panel p{font-size:12px}
.stats{top:auto;bottom:108px;right:10px}.stat{min-width:76px;padding:7px 8px}.stat strong{font-size:18px}
.legend{display:none}.hint{bottom:120px;font-size:11px;max-width:94vw;white-space:normal;text-align:center}
button{padding:9px 11px;font-size:12px}.controls{bottom:10px;gap:6px}
}
</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="app"></div>
<div class="panel">
<h1><span>🫧</span> 3D AQUARIUM <span>🐠</span></h1>
<p>Move your cursor <b>near a fish</b> and it will panic-dash away! The glowing orb shows your repel zone. Click the water to startle fish nearby.</p>
<div class="badge"><span class="dot" id="modeDot"></span><span id="modeText">AUTO POINTER DEMO</span></div>
</div>
<div class="stats">
<div class="stat hot" id="fleeStat"><strong id="fleeCount">0</strong><small>💨 Fleeing</small></div>
<div class="stat"><strong id="fishCount">0</strong><small>🐟 Fish</small></div>
<div class="stat"><strong id="bubblesCount">0</strong><small>🫧 Bubbles</small></div>
</div>
<div class="hint">🖱️ <b>Move mouse</b> near fish • <b>Click</b> to startle • <b>Drag</b> to orbit</div>
<div class="legend"><i style="background:#22d3ee"></i>repel radius<br><i style="background:#fb923c"></i>fish panic zone</div>
<div class="controls">
<button id="scatterBtn">💥 Scatter all fish</button>
<button id="addBtn">➕ Add a fish</button>
<button id="bubbleBtn">🫧 Bubble burst</button>
<button id="autoBtn" class="active">🤖 Auto pointer: ON</button>
<button id="camBtn">🎥 Reset camera</button>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const app=document.getElementById('app');
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x031a33);
scene.fog=new THREE.FogExp2(0x06283f,0.028);
const camera=new THREE.PerspectiveCamera(52,innerWidth/innerHeight,0.1,100);
const CAM0=new THREE.Vector3(0,1.4,16.5);
camera.position.copy(CAM0);
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=THREE.PCFSoftShadowMap;
renderer.toneMapping=THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure=1.15;
app.appendChild(renderer.domElement);
const controls=new OrbitControls(camera,renderer.domElement);
controls.target.set(0,0.3,0);
controls.enableDamping=true; controls.dampingFactor=.06;
controls.minDistance=7; controls.maxDistance=24;
controls.maxPolarAngle=Math.PI*.62; controls.minPolarAngle=Math.PI*.22;
controls.enablePan=false;
/* lights */
scene.add(new THREE.AmbientLight(0x7cc7ff,.85));
const sun=new THREE.DirectionalLight(0xdff4ff,2.2); sun.position.set(4,10,6); sun.castShadow=true; sun.shadow.mapSize.set(1024,1024); scene.add(sun);
const fill=new THREE.PointLight(0x22d3ee,28,22,1.4); fill.position.set(-5,3,4); scene.add(fill);
const warm=new THREE.PointLight(0xffb86b,18,18,1.6); warm.position.set(6,-2,-2); scene.add(warm);
/* tank */
const TANK={x:7.8,y:3.9,z:3.4};
function box(w,h,d,c,op=1){const m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),new THREE.MeshStandardMaterial({color:c,roughness:.85,metalness:.05,transparent:op<1,opacity:op}));m.castShadow=true;m.receiveShadow=true;return m}
// stand + floor wood
const stand=box(17.4,.7,8.2,0x3b2314); stand.position.y=-4.65; scene.add(stand);
const rim=box(17, .35, 8, 0x0f3d5c); rim.position.y=-4.25; scene.add(rim);
// glass
const glass=new THREE.Mesh(new THREE.BoxGeometry(TANK.x*2,TANK.y*2,TANK.z*2),new THREE.MeshPhysicalMaterial({color:0xa5f3fc,transparent:true,opacity:.07,roughness:.05,metalness:0,side:THREE.DoubleSide}));
scene.add(glass);
const edges=new THREE.LineSegments(new THREE.EdgesGeometry(glass.geometry),new THREE.LineBasicMaterial({color:0x67e8f9,transparent:true,opacity:.75})); scene.add(edges);
// water top
const water=new THREE.Mesh(new THREE.PlaneGeometry(TANK.x*2,TANK.z*2),new THREE.MeshBasicMaterial({color:0x7dd3fc,transparent:true,opacity:.22,side:THREE.DoubleSide}));
water.rotation.x=-Math.PI/2; water.position.y=TANK.y; scene.add(water);
// sand
const sandGeo=new THREE.PlaneGeometry(TANK.x*2, TANK.z*2, 48,24);
{const p=sandGeo.attributes.position; for(let i=0;i<p.count;i++){const x=p.getX(i),y=p.getY(i); p.setZ(i, Math.sin(x*1.3)*.18+Math.cos(y*2)*.12+Math.random()*.06);} sandGeo.computeVertexNormals();}
const sand=new THREE.Mesh(sandGeo,new THREE.MeshStandardMaterial({color:0xe7d49a,roughness:1})); sand.rotation.x=-Math.PI/2; sand.position.y=-TANK.y+.25; sand.receiveShadow=true; scene.add(sand);
// pebbles on sand
for(let i=0;i<70;i++){const s=.08+Math.random()*.22; const st=new THREE.Mesh(new THREE.SphereGeometry(s,7,6),new THREE.MeshStandardMaterial({color:new THREE.Color().setHSL(Math.random(),.25,.45+Math.random()*.3),roughness:.9})); st.scale.y=.45; st.position.set((Math.random()-.5)*15,-TANK.y+.32,(Math.random()-.5)*6.2); scene.add(st);}
/* rocks, coral, plants */
const rockMat=new THREE.MeshStandardMaterial({color:0x64748b,roughness:.95,flatShading:true});
function rock(x,z,s){const r=new THREE.Mesh(new THREE.IcosahedronGeometry(s,1),rockMat); r.position.set(x,-TANK.y+.5,z); r.scale.set(1.4,.8,1); r.rotation.set(Math.random(),Math.random(),Math.random()); r.castShadow=r.receiveShadow=true; scene.add(r);}
rock(-6,-1.5,1.2); rock(-5.2,-.5,.8); rock(5.8,1,1.1); rock(6.6,-1,.7); rock(3.5,-2,.9); rock(-3,2,.7);
function coral(x,z,c,h){const g=new THREE.Group(); const mat=new THREE.MeshStandardMaterial({color:c,roughness:.6,emissive:c,emissiveIntensity:.12});
for(let i=0;i<5;i++){const b=new THREE.Mesh(new THREE.CylinderGeometry(.07,.16,h*(0.7+Math.random()*.6),6),mat); b.position.set((Math.random()-.5)*.7,h/2,(Math.random()-.5)*.7); b.rotation.set((Math.random()-.5)*.7,0,(Math.random()-.5)*.7); b.castShadow=true; g.add(b);}
g.position.set(x,-TANK.y+.35,z); scene.add(g);}
coral(-4.5,2,0xfb7185,1.4); coral(4.8,-2.2,0xc084fc,1.2); coral(1.8,2.4,0xf59e0b,1); coral(-7,1.8,0x34d399,.9);
const plants=[];
function plant(x,z,h){const g=new THREE.Group(); const mat=new THREE.MeshStandardMaterial({color:0x16a34a,roughness:.7,emissive:0x14532d,emissiveIntensity:.25});
for(let i=0;i<6;i++){const blade=new THREE.Mesh(new THREE.ConeGeometry(.14,h,5),mat); blade.position.set((Math.random()-.5)*.8,h/2,(Math.random()-.5)*.8); blade.rotation.z=(Math.random()-.5)*.35; blade.castShadow=true; g.add(blade);}
g.position.set(x,-TANK.y+.35,z); scene.add(g); plants.push({g,ph:Math.random()*6});}
plant(-7,0,3.2); plant(-6.8,2.2,2.4); plant(7,-.5,3.4); plant(6.8,2,2.6); plant(-1,-2.6,2.2); plant(2.8,-2.8,2.8); plant(0,2.8,1.9);
// starfish
const star=new THREE.Mesh(new THREE.CylinderGeometry(.55,.65,.18,5),new THREE.MeshStandardMaterial({color:0xfb923c,roughness:.8})); star.position.set(-2.5,-TANK.y+.45,1.6); star.rotation.y=.6; scene.add(star);
// light rays
for(let i=0;i<4;i++){const ray=new THREE.Mesh(new THREE.PlaneGeometry(1.6,9),new THREE.MeshBasicMaterial({color:0xa5f3fc,transparent:true,opacity:.06,side:THREE.DoubleSide,depthWrite:false})); ray.position.set(-5+i*3.2,1,-1); ray.rotation.z=.25; ray.rotation.y=.4; scene.add(ray);}
/* bubbles */
const bubbles=[];
const bubbleGeo=new THREE.SphereGeometry(.09,10,8);
const bubbleMat=new THREE.MeshBasicMaterial({color:0xcffafe,transparent:true,opacity:.55});
for(let i=0;i<70;i++){const b=new THREE.Mesh(bubbleGeo,bubbleMat); b.position.set((Math.random()-.5)*15,-TANK.y+Math.random()*7.5,(Math.random()-.5)*6); b.scale.setScalar(.5+Math.random()*1.4); b.userData.speed=.35+Math.random()*.8; scene.add(b); bubbles.push(b);}
// burst bubbles pool
const burst=[];
for(let i=0;i<40;i++){const b=new THREE.Mesh(bubbleGeo,new THREE.MeshBasicMaterial({color:0xfff7cc,transparent:true,opacity:0})); b.visible=false; scene.add(b); burst.push(b);}
function bubbleBurst(pos,n=10){let c=0; for(const b of burst){if(b.visible)continue; b.position.copy(pos); b.position.x+=(Math.random()-.5); b.position.y+=(Math.random()-.5); b.visible=true; b.userData.v=new THREE.Vector3((Math.random()-.5)*2,1.5+Math.random()*2,(Math.random()-.5)*2); b.userData.life=1; if(++c>=n)break;}}
/* fish factory — modelled along +Z (head forward) */
const fishes=[];
const eyeBlack=new THREE.MeshBasicMaterial({color:0x111111});
const eyeWhite=new THREE.MeshBasicMaterial({color:0xffffff});
const palettes=[
{body:0xff8c42,tail:0xffd23f,fin:0xff6b35,name:'Clown'},
{body:0x38bdf8,tail:0x1d4ed8,fin:0x7dd3fc,name:'Blue Tang'},
{body:0xfde047,tail:0xf59e0b,fin:0xfff7cc,name:'Sunny'},
{body:0xf472b6,tail:0xe879f9,fin:0xfbcfe8,name:'Bubblegum'},
{body:0x34d399,tail:0x0891b2,fin:0x6ee7b7,name:'Mint'},
{body:0xa78bfa,tail:0x6366f1,fin:0xc4b5fd,name:'Lavender'},
{body:0xf87171,tail:0xffffff,fin:0xfca5a5,name:'Coral'},
{body:0x94a3b8,tail:0xe2e8f0,fin:0xffffff,name:'Silver'},
];
let paletteIdx=0;
function makeFish(size=1,small=false){
const pal=palettes[paletteIdx++%palettes.length];
const g=new THREE.Group();
const bodyMat=new THREE.MeshStandardMaterial({color:pal.body,roughness:.45,metalness:.1,emissive:pal.body,emissiveIntensity:.12});
const finMat=new THREE.MeshStandardMaterial({color:pal.fin,roughness:.5,emissive:pal.fin,emissiveIntensity:.18,side:THREE.DoubleSide});
const tailMat=new THREE.MeshStandardMaterial({color:pal.tail,roughness:.5,emissive:pal.tail,emissiveIntensity:.18,side:THREE.DoubleSide});
const body=new THREE.Mesh(new THREE.SphereGeometry(size,18,14),bodyMat); body.scale.set(.42,.72,1.35); body.castShadow=true; g.add(body);
// belly highlight
const belly=new THREE.Mesh(new THREE.SphereGeometry(size*.82,14,10),new THREE.MeshBasicMaterial({color:0xffffff,transparent:true,opacity:.18})); belly.scale.set(.36,.5,1.1); belly.position.y=-.18*size; g.add(belly);
// eyes
for(const s of [-1,1]){const e=new THREE.Mesh(new THREE.SphereGeometry(size*.16,10,8),eyeBlack); e.position.set(s*size*.3,size*.18,size*.92); g.add(e); const w=new THREE.Mesh(new THREE.SphereGeometry(size*.06,8,6),eyeWhite); w.position.set(s*size*.34,size*.24,size*1.03); g.add(w);
const fin=new THREE.Mesh(new THREE.ConeGeometry(size*.32,size*.85,4),finMat); fin.scale.z=.18; fin.position.set(s*size*.55,-size*.05,size*.1); fin.rotation.z=s*1.2; g.add(fin);}
const dorsal=new THREE.Mesh(new THREE.ConeGeometry(size*.34,size*.8,4),finMat); dorsal.scale.x=.18; dorsal.position.set(0,size*.62,-size*.1); g.add(dorsal);
const tailPivot=new THREE.Group(); tailPivot.position.set(0,0,-size*1.15); g.add(tailPivot);
const tail=new THREE.Mesh(new THREE.ConeGeometry(size*.62,size*1.15,4),tailMat); tail.scale.x=.16; tail.position.z=-size*.55; tail.rotation.x=-Math.PI/2; tailPivot.add(tail);
// stripes for some
if(Math.random()<.45){for(let i=0;i<2;i++){const stripe=new THREE.Mesh(new THREE.TorusGeometry(size*.62,size*.09,8,20),new THREE.MeshBasicMaterial({color:0xffffff})); stripe.rotation.x=Math.PI/2; stripe.scale.set(.7,1,1); stripe.position.z=size*(.25+i*.45); stripe.scale.z=.6; g.add(stripe);}}
g.position.set((Math.random()-.5)*12,(Math.random()-.5)*5,(Math.random()-.5)*4);
scene.add(g);
const f={g,tailPivot,vel:new THREE.Vector3(Math.random()-.5,0,Math.random()-.5).normalize().multiplyScalar(1.2),size,phase:Math.random()*6.28,wander:Math.random()*6.28,fleeing:false,speed:1,small,justStartled:0};
fishes.push(f); return f;
}
for(let i=0;i<14;i++) makeFish(.55+Math.random()*.45);
for(let i=0;i<9;i++){const f=makeFish(.22+Math.random()*.12,true); f.vel.multiplyScalar(1.6);}
// largest hero fish front and center for first paint
const hero=makeFish(1.05); hero.g.position.set(-2.5,0.6,1.6); hero.vel.set(1.4,.1,.2);
/* mouse / repel orb */
const REPEL=4.0;
const orb=new THREE.Mesh(new THREE.SphereGeometry(.22,16,12),new THREE.MeshBasicMaterial({color:0x22d3ee}));
const orbGlow=new THREE.Mesh(new THREE.SphereGeometry(.42,16,12),new THREE.MeshBasicMaterial({color:0x22d3ee,transparent:true,opacity:.22}));
const repelRing=new THREE.Mesh(new THREE.RingGeometry(REPEL-.06,REPEL,64),new THREE.MeshBasicMaterial({color:0x22d3ee,transparent:true,opacity:.28,side:THREE.DoubleSide}));
repelRing.rotation.x=0; // faces camera-ish in XY plane — keep as XY disc
const repelWire=new THREE.Mesh(new THREE.SphereGeometry(REPEL,20,14),new THREE.MeshBasicMaterial({color:0x22d3ee,wireframe:true,transparent:true,opacity:.06}));
scene.add(orb,orbGlow,repelRing,repelWire);
const pointerWorld=new THREE.Vector3(0,0,0);
const mouseNDC=new THREE.Vector2(0,0);
const raycaster=new THREE.Raycaster();
const zPlane=new THREE.Plane(new THREE.Vector3(0,0,1),0);
let lastUserMove=-9999, autoOn=true, autoTarget=null, autoTimer=0, manual=false;
const modeText=document.getElementById('modeText'), autoBtn=document.getElementById('autoBtn');
addEventListener('pointermove',e=>{
mouseNDC.set((e.clientX/innerWidth)*2-1,-(e.clientY/innerHeight)*2+1);
lastUserMove=performance.now(); manual=true;
raycaster.setFromCamera(mouseNDC,camera);
const hit=new THREE.Vector3(); if(raycaster.ray.intersectPlane(zPlane,hit)){hit.x=THREE.MathUtils.clamp(hit.x,-TANK.x,TANK.x); hit.y=THREE.MathUtils.clamp(hit.y,-TANK.y,TANK.y); pointerWorld.lerp(hit,.55);}
});
addEventListener('pointerdown',e=>{
if(e.target.tagName==='BUTTON')return;
// click startle in radius
let hitFish=0;
for(const f of fishes){const d=f.g.position.distanceTo(pointerWorld); if(d<5){f.vel.add(f.g.position.clone().sub(pointerWorld).normalize().multiplyScalar(4)); if(!f.fleeing){popAt(f.g.position,'💨');} f.fleeing=true; hitFish++;}}
if(hitFish){bubbleBurst(pointerWorld,14); flashRepel();}
});
function flashRepel(){repelRing.material.opacity=.9; repelRing.scale.setScalar(1.15); setTimeout(()=>repelRing.scale.setScalar(1),180);}
function popAt(pos,txt='💨!'){const v=pos.clone().project(camera); if(v.z>1)return; const el=document.createElement('div'); el.className='pop'; el.textContent=txt; el.style.left=((v.x*.5+.5)*innerWidth)+'px'; el.style.top=((-v.y*.5+.5)*innerHeight)+'px'; document.body.appendChild(el); setTimeout(()=>el.remove(),950);}
/* UI buttons */
document.getElementById('scatterBtn').onclick=()=>{for(const f of fishes){f.vel.add(new THREE.Vector3((Math.random()-.5), (Math.random()-.5)*.6,(Math.random()-.5)).normalize().multiplyScalar(5)); f.fleeing=true; popAt(f.g.position,'💥');} bubbleBurst(new THREE.Vector3(0,0,0),20); flashRepel();};
document.getElementById('addBtn').onclick=()=>{if(fishes.length<34){makeFish(.5+Math.random()*.5); updateCounts();}};
document.getElementById('bubbleBtn').onclick=()=>bubbleBurst(pointerWorld.clone(),24);
autoBtn.onclick=()=>{autoOn=!autoOn; autoBtn.textContent=autoOn?'🤖 Auto pointer: ON':'🤖 Auto pointer: OFF'; autoBtn.classList.toggle('active',autoOn);};
document.getElementById('camBtn').onclick=()=>{camera.position.copy(CAM0); controls.target.set(0,.3,0);};
const fleeCountEl=document.getElementById('fleeCount'), fishCountEl=document.getElementById('fishCount'), bubblesEl=document.getElementById('bubblesCount');
function updateCounts(){fishCountEl.textContent=fishes.length;}
updateCounts();
/* animation */
const clock=new THREE.Clock();
const tmp=new THREE.Vector3(), tmp2=new THREE.Vector3();
function animate(){
requestAnimationFrame(animate);
const dt=Math.min(clock.getDelta(),.05), t=clock.elapsedTime;
/* auto pointer: hunt a fish every few seconds */
const idle=performance.now()-lastUserMove>4500;
if(autoOn && idle){
autoTimer-=dt;
if(autoTimer<=0 || !autoTarget){autoTarget=fishes[Math.floor(Math.random()*fishes.length)]; autoTimer=2.2+Math.random()*1.6;}
if(autoTarget){tmp.copy(autoTarget.g.position); tmp.x+=Math.sin(t*3)*.4; tmp.y+=Math.cos(t*2.4)*.3; pointerWorld.lerp(tmp,.06);}
} else if(autoOn && !idle){ manual=true; } else manual=false;
modeText.textContent=(autoOn&&idle)?'AUTO POINTER DEMO — FISH FLEEING!':'YOU CONTROL THE POINTER';
document.getElementById('modeDot').style.background=(autoOn&&idle)?'#fbbf24':'#34d399';
document.getElementById('modeDot').style.boxShadow=`0 0 10px ${(autoOn&&idle)?'#fbbf24':'#34d399'}`;
orb.position.copy(pointerWorld); orbGlow.position.copy(pointerWorld);
repelRing.position.copy(pointerWorld); repelWire.position.copy(pointerWorld);
repelRing.lookAt(camera.position);
const pulse=1+Math.sin(t*4)*.04; repelRing.scale.setScalar(pulse); orbGlow.scale.setScalar(1+Math.sin(t*5)*.18);
repelRing.material.opacity=.22+Math.sin(t*3)*.08;
let fleeingNow=0;
for(const f of fishes){
const p=f.g.position;
tmp.copy(p).sub(pointerWorld); const d=tmp.length();
const wasFleeing=f.fleeing;
f.fleeing=d<REPEL;
if(f.fleeing) fleeingNow++;
if(f.fleeing && !wasFleeing){popAt(p,'💨!'); bubbleBurst(p,5); f.justStartled=1;}
f.justStartled=Math.max(0,f.justStartled-dt*1.5);
// steer
const steer=new THREE.Vector3();
// wander
f.wander+=dt*(f.fleeing?2.5:1);
steer.x+=Math.sin(t*.7+f.phase)*.6+Math.cos(f.wander)*.5;
steer.y+=Math.sin(t*.9+f.phase*2)*.35;
steer.z+=Math.cos(t*.6+f.phase)*.5;
// flee
let maxSpeed=f.small?2.4:1.9;
if(f.fleeing && d>0.01){
const strength=(1-d/REPEL); // 1 close → 0 edge
tmp.normalize();
steer.addScaledVector(tmp, 3+strength*9);
maxSpeed=3.5+strength*5.5;
}
// wall avoidance
if(Math.abs(p.x)>TANK.x-1.2) steer.x+=-Math.sign(p.x)*3;
if(p.y>TANK.y-1.2) steer.y-=3; if(p.y<-TANK.y+1.2) steer.y+=3;
if(Math.abs(p.z)>TANK.z-1) steer.z+=-Math.sign(p.z)*3;
// separation
for(const o of fishes){if(o===f)continue; const dd=p.distanceTo(o.g.position); if(dd<1.2&&dd>0){tmp.copy(p).sub(o.g.position).normalize(); steer.addScaledVector(tmp,.7);}}
f.vel.addScaledVector(steer,dt*3.2);
f.vel.clampLength(.4,maxSpeed);
// ease speed back
p.addScaledVector(f.vel,dt);
p.x=THREE.MathUtils.clamp(p.x,-TANK.x+.4,TANK.x-.4); p.y=THREE.MathUtils.clamp(p.y,-TANK.y+.5,TANK.y-.5); p.z=THREE.MathUtils.clamp(p.z,-TANK.z+.4,TANK.z-.4);
// orientation
tmp2.copy(p).add(f.vel); if(tmp2.distanceTo(p)>.001) f.g.lookAt(tmp2);
f.g.rotateX(Math.sin(t*2+f.phase)*.08);
// tail wag faster when fleeing
const wagFreq=f.fleeing?18:8, wagAmp=f.fleeing?.9:.55;
f.tailPivot.rotation.y=Math.sin(t*wagFreq+f.phase)*wagAmp;
f.g.rotation.z+=Math.sin(t*1.5+f.phase)*.002;
const sp=f.vel.length(); f.speed+=(sp-f.speed)*.08;
}
fleeCountEl.textContent=fleeingNow;
document.getElementById('fleeStat').classList.toggle('hot',fleeingNow>0);
bubblesEl.textContent=bubbles.length+burst.filter(b=>b.visible).length;
// ambient anim
for(const b of bubbles){b.position.y+=b.userData.speed*dt; b.position.x+=Math.sin(t*2+b.position.y)*.15*dt; if(b.position.y>TANK.y-.2){b.position.y=-TANK.y+.4; b.position.x=(Math.random()-.5)*15;}}
for(const b of burst){if(!b.visible)continue; b.userData.life-=dt*.7; b.position.addScaledVector(b.userData.v,dt); b.material.opacity=Math.max(0,b.userData.life)*.8; b.scale.setScalar(.7+b.userData.life); if(b.userData.life<=0)b.visible=false;}
for(const pl of plants){pl.g.rotation.z=Math.sin(t*.9+pl.ph)*.08; pl.g.rotation.x=Math.cos(t*.7+pl.ph)*.05;}
water.material.opacity=.18+Math.sin(t)*.05;
fill.intensity=24+Math.sin(t*1.3)*6;
controls.update();
renderer.render(scene,camera);
}
animate();
addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth,innerHeight);});
</script>
</body>
</html>Reasoning trace (2,302 tokens)
Designing a self-contained Threejs 3D aquarium with mouse-driven fish evasion and auto demo mode.Adding autonomous mouse pointer animation and touch support for initial visible fish evasion.Extending the fish behavior with steering, UI overlays, and visual effects while keeping performance constraints.