Interactive 3D aquariumOne-line
GLM 5.3
by Z.aiReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 3 min 50 s
- Cost
- $0.0058
- Output tokens
- 4,076
- Spent thinking
- 106 tokens
- Speed
- 17.8 tok/s
- Console errors
- 0
- First token after
- 1.5 s
- First visible text after
- 17.5 s
- Prompt tokens
- 424
- Code size
- 11.3 KB
- Finish reason
- stop
- Served by
- Wafer
- Model id
z-ai/glm-5.3- Reasoning sent
model default- 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 · 316 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aquarium — move your mouse near the fish</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#03182a;font-family:Georgia,serif}
#ui{position:fixed;left:0;right:0;top:0;padding:18px 22px;color:#cfeef7;
display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none;
text-shadow:0 1px 6px rgba(0,20,40,.8)}
h1{font-size:22px;font-weight:400;letter-spacing:.06em;margin:0}
#hint{font-size:13px;opacity:.75;margin-top:6px;max-width:340px;line-height:1.5}
#stats{font-size:12px;opacity:.6;text-align:right}
canvas{display:block}
</style>
</head>
<body>
<div id="ui">
<div>
<h1>🐠 The Reef Tank</h1>
<div id="hint">Move your mouse near the fish and they scatter. Click to lure them with a sprinkle of food.</div>
</div>
<div id="stats"></div>
</div>
<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>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x062338);
scene.fog = new THREE.FogExp2(0x062338, 0.028);
const camera = new THREE.PerspectiveCamera(52, innerWidth/innerHeight, 0.1, 100);
camera.position.set(0, 3.5, 16);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.minDistance = 6; controls.maxDistance = 28;
controls.maxPolarAngle = Math.PI * 0.62;
// Lights
scene.add(new THREE.HemisphereLight(0x9fd8ff, 0x0a2a3a, 0.9));
const sun = new THREE.DirectionalLight(0xcaf2ff, 1.4);
sun.position.set(4, 12, 6);
scene.add(sun);
// ---------- Tank ----------
const TANK = {w: 16, h: 9, d: 9}; // half-ish extents used below
const floorY = -4.2, topY = 3.8;
const sand = new THREE.Mesh(
new THREE.PlaneGeometry(34, 20, 60, 40),
new THREE.MeshStandardMaterial({color:0xcbb489, roughness:1})
);
const pos = sand.geometry.attributes.position;
for(let i=0;i<pos.count;i++){
pos.setZ(i, Math.sin(pos.getX(i)*0.5)*0.15 + Math.cos(pos.getY(i)*0.7)*0.1);
}
sand.rotation.x = -Math.PI/2;
sand.position.y = floorY;
scene.add(sand);
// Back glass tint
const backWall = new THREE.Mesh(new THREE.PlaneGeometry(36,22),
new THREE.MeshStandardMaterial({color:0x0a4a63, roughness:0.9, side:THREE.DoubleSide}));
backWall.position.set(0, 4, -9);
scene.add(backWall);
// Rocks
const rockMat = new THREE.MeshStandardMaterial({color:0x6a7580, roughness:0.95, flatShading:true});
const rockGeo = new THREE.DodecahedronGeometry(1, 0);
[[-6.5,-3.6,0.7,-3],[ -5,-3.8,-2.5,2.2],[6.3,-3.7,-1,2.6],[7.2,-3.8,2,1.6],[0,-3.9,-5,3]].forEach(([x,y,z,s])=>{
const r = new THREE.Mesh(rockGeo, rockMat);
r.position.set(x,y,z); r.scale.set(s, s*0.7, s);
r.rotation.set(Math.random()*3, Math.random()*3, Math.random()*3);
scene.add(r);
});
// Seaweed
const weeds = [];
const weedMat = new THREE.MeshStandardMaterial({color:0x2e8b57, roughness:0.8, side:THREE.DoubleSide});
for(let i=0;i<26;i++){
const h = 2.5+Math.random()*3.5;
const g = new THREE.PlaneGeometry(0.35, h, 1, 8);
const m = new THREE.Mesh(g, weedMat);
const x = -9 + Math.random()*18, z = -6 + Math.random()*5;
m.position.set(x, floorY + h/2, z);
m.userData = {phase: Math.random()*6.28, baseX:x};
scene.add(m); weeds.push(m);
}
// Bubbles
const bubbleGeo = new THREE.SphereGeometry(0.07, 8, 8);
const bubbleMat = new THREE.MeshStandardMaterial({color:0xbfe8ff, transparent:true, opacity:0.5, roughness:0.2});
const bubbles = [];
for(let i=0;i<70;i++){
const b = new THREE.Mesh(bubbleGeo, bubbleMat.clone());
b.material.opacity = 0.25+Math.random()*0.4;
b.position.set((Math.random()-0.5)*20, floorY+Math.random()*8, -7+Math.random()*7);
b.userData = {s: 0.4+Math.random()*1.2, w: Math.random()*6.28};
scene.add(b); bubbles.push(b);
}
// ---------- Fish ----------
const FISH_COLORS = [0xff8c42, 0xffd166, 0x06d6a0, 0x4cc9f0, 0xf15bb5, 0xfdf0d5, 0xef476f, 0xffb703];
const fishes = [];
const fishCount = 26;
function makeFish(){
const g = new THREE.Group();
const color = FISH_COLORS[Math.floor(Math.random()*FISH_COLORS_COLORS_LEN())];
function FISH_COLORS_COLORS_LEN(){ return FISH_COLORS.length; }
const s = 0.55 + Math.random()*0.6;
const mat = new THREE.MeshStandardMaterial({color, roughness:0.5, metalness:0.15, flatShading:true});
const body = new THREE.Mesh(new THREE.ConeGeometry(0.32*s, 1.1*s, 7), mat);
body.rotation.z = -Math.PI/2; // point forward +x
g.add(body);
const tailMat = new THREE.MeshStandardMaterial({color, roughness:0.6, side:THREE.DoubleSide, flatShading:true});
const tail = new THREE.Mesh(new THREE.CircleGeometry(0.34*s, 3), tailMat);
tail.position.x = -0.68*s;
tail.rotation.y = Math.PI/2;
g.add(tail);
const fin = new THREE.Mesh(new THREE.CircleGeometry(0.2*s, 3), tailMat);
fin.position.set(0.05*s, 0, 0.28*s);
fin.rotation.x = Math.PI/2;
g.add(fin);
// eye
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.05*s, 6, 6),
new THREE.MeshStandardMaterial({color:0x111111}));
eye.position.set(0.36*s, 0.08*s, 0.2*s);
g.add(eye);
const eye2 = eye.clone(); eye2.position.z = -0.2*s; g.add(eye2);
const SPEED = 1.6 + Math.random()*1.6;
const f = {
obj: g, vel: new THREE.Vector3((Math.random()-.5), (Math.random()-.5)*.3, (Math.random()-.5)).normalize().multiplyScalar(SPEED),
speed: SPEED, maxSpeed: SPEED*2.6,
wiggle: Math.random()*6.28, wander: Math.random()*100,
scale: s, tail, panicked: 0
};
g.position.set((Math.random()-.5)*13, floorY+1+Math.random()*5.5, -5+Math.random()*7);
g.scale.setScalar(s);
scene.add(g);
fishes.push(f);
}
for(let i=0;i<fishCount;i++) makeFish();
// ---------- Interaction ----------
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(0, 0);
const pointerWorld = new THREE.Vector3(0, 0, 0);
const pointerActive = new THREE.Vector3(1, 0, 0); // strength
let haveMouse = false;
const foodDots = [];
const foodMat = new THREE.MeshBasicMaterial({color:0xffe8b0});
const foodGeo = new THREE.SphereGeometry(0.06, 6, 6);
addEventListener('pointermove', e=>{
mouse.x = (e.clientX/innerWidth)*2-1;
mouse.y = -(e.clientY/innerHeight)*2+1;
haveMouse = true;
});
addEventListener('pointerdown', e=>{
// sprinkle food at pointer
raycaster.setFromCamera(new THREE.Vector2((e.clientX/innerWidth)*2-1, -(e.clientY/innerHeight)*2+1), camera);
const t = new THREE.Vector3().copy(raycaster.ray.direction).normalize();
const p = raycaster.ray.origin.clone().add(t.multiplyScalar(11));
p.z = THREE.MathUtils.clamp(p.z, -7.5, 3);
for(let i=0;i<8;i++){
const d = new THREE.Mesh(foodGeo, foodMat);
d.position.copy(p).add(new THREE.Vector3((Math.random()-.5)*0.8, Math.random()*0.5, (Math.random()-.5)*0.5));
d.userData = {vy: -0.15-Math.random()*0.15, life: 6+Math.random()*4};
scene.add(d); foodDots.push(d);
}
});
const clock = new THREE.Clock();
const tmp = new THREE.Vector3();
const desired = new THREE.Vector3();
function steer(f, dt){
const p = f.obj.position;
// mouse threat position: project ray into the tank mid-depth
raycaster.setFromCamera(mouse, camera);
const dir = raycaster.ray.direction.clone().normalize();
const cam = raycaster.ray.origin;
// intersect ray with plane z = p.z (fish's depth) — keeps threat near the fish
const dz = (p.z - cam.z) / dir.z;
const mx = cam.x + dir.x*dz, my = cam.y + dir.y*dz;
pointerWorld.set(THREE.MathUtils.clamp(mx,-11,11), THREE.MathUtils.clamp(my,floorY+.4,topY), p.z);
const dist = p.distanceTo(pointerWorld);
let panicked = 0;
if(haveMouse && dist < 3.2){
panicked = 1 - dist/3.2;
desired.copy(p).sub(pointerWorld).normalize().multiplyScalar(f.maxSpeed);
f.vel.lerp(desired, Math.min(1, panicked*7*dt));
f.panicked = Math.max(f.panicked, panicked);
} else {
f.panicked *= 0.95;
// wander
f.wander += dt;
desired.set(Math.sin(f.wander*0.5)*3, Math.sin(f.wander*0.31)*0.6, Math.cos(f.wander*0.23)*2.4);
const toCenter = tmp.set(-p.x*0.12, (floorY+3 - p.y)*0.15, (-p.z-2)*0.1);
desired.add(toCenter);
// light schooling: drift toward neighbors' average heading
desired.normalize().multiplyScalar(f.speed);
f.vel.lerp(desired, dt*1.4);
}
// food attraction
for(const d of foodDots){
if(p.distanceTo(d.position) < 4.5){
desired.copy(d.position).sub(p).normalize().multiplyScalar(f.speed*1.8);
f.vel.lerp(desired, dt*3);
if(p.distanceTo(d.position) < 0.25){ d.userData.life = 0; }
break;
}
}
// bounds
const bx = 13, bz0 = -7.5, bz1 = 3.5;
if(p.x > bx) f.vel.x -= 6*dt;
if(p.x < -bx) f.vel.x += 6*dt;
if(p.y > topY) f.vel.y -= 5*dt;
if(p.y < floorY+0.5) f.vel.y += 5*dt;
if(p.z > bz1) f.vel.z -= 6*dt;
if(p.z < bz0) f.vel.z += 6*dt;
f.vel.clampLength(0.3, f.maxSpeed);
p.addScaledVector(f.vel, dt);
// orient along velocity
if(f.vel.lengthSq() > 0.001){
const targetYaw = Math.atan2(f.vel.y, Math.abs(f.vel.x)) * (f.vel.x >= 0 ? 1 : 0);
const yaw = Math.atan2(f.vel.z, f.vel.x);
f.obj.rotation.y = -yaw + (f.vel.x >= 0 ? 0 : 0);
// If moving backwards, flip so tail follows (keep head forward)
const pitch = Math.atan2(f.vel.y, Math.hypot(f.vel.x, f.vel.z));
f.obj.rotation.z = -pitch * Math.sign(f.vel.x || 1);
}
}
const statsEl = document.getElementById('stats');
let statTimer = 0;
function animate(){
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05);
const t = clock.elapsedTime;
// fake "auto demo": wiggle mouse gently until user moves it
if(!haveMouse){
mouse.x = Math.sin(t*0.5)*0.55;
mouse.y = Math.sin(t*0.33)*0.3;
}
for(const f of fishes){
steer(f, dt);
f.wiggle += dt * (6 + f.vel.length()*4 + f.panicked*14);
f.tail.rotation.y = Math.PI/2 + Math.sin(f.wiggle)*0.5;
f.obj.position.y += Math.sin(f.wiggle*0.5)*0.002;
}
for(const w of weeds){
w.rotation.z = Math.sin(t*0.9 + w.userData.phase)*0.18;
}
for(let i=bubbles.length-1;i>=0;i--){
const b = bubbles[i];
b.position.y += b.userData.s*dt*1.2;
b.position.x = b.userData.w + Math.sin(t*2 + b.userData.w)*0.25;
if(b.position.y > topY){ b.position.y = floorY+0.1; b.userData.w = (Math.random()-.5)*20; }
}
for(let i=foodDots.length-1;i>=0;i--){
const d = foodDots[i];
d.position.y += d.userData.vy*dt;
d.position.x += Math.sin(t*3+i)*0.05*dt*10;
if(d.position.y < floorY+0.05) d.position.y = floorY+0.05;
d.userData.life -= dt;
if(d.userData.life <= 0){ scene.remove(d); foodDots.splice(i,1); }
}
statTimer += dt;
if(statTimer > 0.5){
statTimer = 0;
const panicking = fishes.filter(f=>f.panicked>0.05).length;
statsEl.textContent = `${fishes.length} fish · ${panicking} startled`;
}
controls.update();
renderer.render(scene, camera);
}
animate();
addEventListener('resize', ()=>{
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>Reasoning trace (106 tokens)
Build a Three.js aquarium: tank, water tint, fish as simple meshes with tail wiggle, boids fleeing from mouse raycast point. Orbit controls optional. Make it pretty: gradient, caustics-ish light, bubbles, seaweed. Responsive. No console errors. Implement fish with group: body (cone/lathe), tail plane wiggle. Flee behavior with 3D mouse point projected onto plane near fish depth. Also idle wander. Write compact code.