Interactive 3D aquariumOne-line
GLM 5.3 Flash
by Z.aiReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 25.7 s
- Cost
- $0.0024
- Output tokens
- 4,704
- Spent thinking
- 475 tokens
- Speed
- 188.6 tok/s
- Console errors
- 0
- First token after
- 790 ms
- First visible text after
- 4.7 s
- Prompt tokens
- 424
- Code size
- 12.3 KB
- Finish reason
- stop
- Served by
- Venice
- Model id
z-ai/glm-5.3-flash- Reasoning sent
{"effort":"high"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 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 · 351 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</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #04202e; }
#app { position: fixed; inset: 0; }
canvas { display: block; }
.ui {
position: fixed; pointer-events: none; color: #dff6ff;
font-family: "Segoe UI", system-ui, sans-serif;
text-shadow: 0 2px 8px rgba(0,20,35,.7);
z-index: 10;
}
#title { top: 20px; left: 24px; }
#title h1 { margin: 0; font-size: 22px; letter-spacing: 4px; font-weight: 600; text-transform: uppercase; }
#title p { margin: 4px 0 0; font-size: 13px; opacity: .75; letter-spacing: 1px; }
#hint {
bottom: 22px; left: 0; right: 0; text-align: center;
font-size: 14px; letter-spacing: 2px; opacity: .8;
animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: .85; } 50% { opacity: .35; } }
#vignette {
position: fixed; inset: 0; pointer-events: none; z-index: 5;
background: radial-gradient(ellipse at center, rgba(0,0,0,0) 55%, rgba(0,15,28,.55) 100%);
}
</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 id="vignette"></div>
<div class="ui" id="title">
<h1>Coral Bay Aquarium</h1>
<p>Move your mouse near the fish — they will scatter</p>
</div>
<div class="ui" id="hint">Move the mouse to start</div>
<script type="module">
import * as THREE from 'three';
const app = document.getElementById('app');
const hint = document.getElementById('hint');
/* ---------- Renderer / Scene / Camera ---------- */
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
app.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a3a52);
scene.fog = new THREE.FogExp2(0x0a3a52, 0.028);
const camera = new THREE.PerspectiveCamera(58, window.innerWidth / window.innerHeight, 0.1, 120);
camera.position.set(0, 2.2, 16);
/* ---------- Lights ---------- */
scene.add(new THREE.HemisphereLight(0x9fd8ff, 0x0a2a3a, 1.1));
const sun = new THREE.DirectionalLight(0xcfefff, 1.6);
sun.position.set(6, 18, 6);
scene.add(sun);
const glow = new THREE.PointLight(0x66d9ff, 40, 40);
glow.position.set(0, 6, 4);
scene.add(glow);
/* ---------- Sand floor ---------- */
{
const g = new THREE.PlaneGeometry(70, 70, 64, 64);
const pos = g.attributes.position;
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i), y = pos.getY(i);
pos.setZ(i, (Math.sin(x * 0.6) + Math.cos(y * 0.5)) * 0.22 + Math.random() * 0.1);
}
g.computeVertexNormals();
const floor = new THREE.Mesh(g, new THREE.MeshStandardMaterial({ color: 0x3a6b70, roughness: 1, metalness: 0 }));
floor.rotation.x = -Math.PI / 2;
floor.position.y = -4;
scene.add(floor);
}
/* ---------- Rocks ---------- */
const rockMat = new THREE.MeshStandardMaterial({ color: 0x35545e, roughness: 0.95, flatShading: true });
for (let i = 0; i < 14; i++) {
const g = new THREE.IcosahedronGeometry(0.6 + Math.random() * 1.4, 1);
const p = g.attributes.position;
for (let j = 0; j < p.count; j++) {
p.setXYZ(j, p.getX(j) * (0.75 + Math.random() * 0.5), p.getY(j) * (0.6 + Math.random() * 0.4), p.getZ(j) * (0.75 + Math.random() * 0.5));
}
g.computeVertexNormals();
const rock = new THREE.Mesh(g, rockMat);
const a = Math.random() * Math.PI * 2, r = 7 + Math.random() * 14;
rock.position.set(Math.cos(a) * r, -4 + Math.random() * 0.8, Math.sin(a) * r * 0.7 - 2);
rock.rotation.set(Math.random(), Math.random(), Math.random());
scene.add(rock);
}
/* ---------- 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 + Math.random() * 3.5;
const g = new THREE.PlaneGeometry(0.45, h, 1, 6);
g.translate(0, h / 2, 0);
const w = new THREE.Mesh(g, weedMat.clone());
w.material.color.offsetHSL(0, 0, (Math.random() - 0.5) * 0.15);
const a = Math.random() * Math.PI * 2, r = 6 + Math.random() * 15;
w.position.set(Math.cos(a) * r, -4, Math.sin(a) * r * 0.7 - 2);
w.rotation.y = Math.random() * Math.PI;
scene.add(w);
weeds.push({ mesh: w, phase: Math.random() * 10, baseX: w.position.x });
}
/* ---------- Bubbles ---------- */
const bubbleGroup = new THREE.Group();
scene.add(bubbleGroup);
const bubbleMat = new THREE.MeshStandardMaterial({ color: 0xbfeaff, transparent: true, opacity: 0.35, roughness: 0.1, metalness: 0.2 });
const bubbles = [];
const bubbleGeo = new THREE.SphereGeometry(1, 10, 10);
for (let i = 0; i < 60; i++) {
const s = 0.03 + Math.random() * 0.09;
const b = new THREE.Mesh(bubbleGeo, bubbleMat);
b.scale.setScalar(s);
b.position.set((Math.random() - 0.5) * 26, -4 + Math.random() * 14, (Math.random() - 0.5) * 14 - 2);
bubbles.push({ mesh: b, speed: 0.6 + Math.random() * 1.2, wob: Math.random() * 10 });
bubbleGroup.add(b);
}
/* ---------- Fish factory ---------- */
const palette = [0xff9a3c, 0xffd23f, 0xff5d73, 0x4fd6ff, 0x9b5dff, 0x3cffa0, 0xffffff, 0xff8fc7];
function makeFish() {
const group = new THREE.Group();
const scale = 0.55 + Math.random() * 0.8;
const color = new THREE.Color(palette[Math.floor(Math.random() * palette.length)]);
color.offsetHSL(0, 0, (Math.random() - 0.5) * 0.1);
const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.45, metalness: 0.25 });
const finMat = new THREE.MeshStandardMaterial({ color: color.clone().offsetHSL(0, 0, -0.12), roughness: 0.6, transparent: true, opacity: 0.9 });
// body
const body = new THREE.Mesh(new THREE.SphereGeometry(0.42, 16, 12), mat);
body.scale.set(1.7, 0.75, 0.6);
group.add(body);
// tail (pivots to wag)
const tailPivot = new THREE.Group();
tailPivot.position.x = -0.75;
const tail = new THREE.Mesh(new THREE.ConeGeometry(0.36, 0.7, 8), finMat);
tail.rotation.z = Math.PI / 2;
tail.position.x = -0.3;
tail.scale.set(1, 1, 0.25);
tailPivot.add(tail);
group.add(tailPivot);
// dorsal fin
const dorsal = new THREE.Mesh(new THREE.ConeGeometry(0.22, 0.42, 6), finMat);
dorsal.position.set(-0.05, 0.42, 0);
dorsal.scale.z = 0.25;
group.add(dorsal);
// side fins
for (const s of [-1, 1]) {
const fin = new THREE.Mesh(new THREE.ConeGeometry(0.14, 0.34, 6), finMat);
fin.position.set(0.05, -0.12, s * 0.28);
fin.rotation.x = s * 1.1;
fin.rotation.z = -0.8;
fin.scale.z = 0.25;
group.add(fin);
}
// eyes
const eyeMat = new THREE.MeshStandardMaterial({ color: 0x101418, roughness: 0.2 });
const eyeGeo = new THREE.SphereGeometry(0.08, 10, 10);
for (const s of [-1, 1]) {
const eye = new THREE.Mesh(eyeGeo, eyeMat);
eye.position.set(0.55, 0.12, s * 0.19);
group.add(eye);
}
group.scale.setScalar(scale);
return { group, tailPivot };
}
/* ---------- Fish behavior ---------- */
const BOUNDS = { x: 14, yMin: -3.2, yMax: 7.5, z: -3 };
const FISH_COUNT = 26;
const fishes = [];
for (let i = 0; i < FISH_COUNT; i++) {
const { group, tailPivot } = makeFish();
const pos = new THREE.Vector3((Math.random() - 0.5) * 20, -2 + Math.random() * 8, (Math.random() - 0.5) * 8 - 2);
group.position.copy(pos);
scene.add(group);
fishes.push({
group, tailPivot, pos,
vel: new THREE.Vector3((Math.random() - 0.5), (Math.random() - 0.5) * 0.4, (Math.random() - 0.5)).multiplyScalar(1.2),
wander: new THREE.Vector3(),
phase: Math.random() * 100,
scale: group.scale.x,
panic: 0
});
}
/* ---------- Mouse handling ---------- */
const mouseNDC = new THREE.Vector2(0, 0);
let mouseActive = false;
const raycaster = new THREE.Raycaster();
function updateMouse(cx, cy) {
mouseNDC.set((cx / window.innerWidth) * 2 - 1, -(cy / window.innerHeight) * 2 + 1);
if (!mouseActive) {
mouseActive = true;
hint.style.display = 'none';
}
}
window.addEventListener('mousemove', e => updateMouse(e.clientX, e.clientY));
window.addEventListener('touchmove', e => {
if (e.touches[0]) updateMouse(e.touches[0].clientX, e.touches[0].clientY);
}, { passive: true });
/* ---------- Camera parallax target ---------- */
const camTarget = new THREE.Vector3(0, 0, 0);
/* ---------- Simulation ---------- */
const _toFish = new THREE.Vector3();
const _closest = new THREE.Vector3();
const _steer = new THREE.Vector3();
const _fleeDir = new THREE.Vector3();
const clock = new THREE.Clock();
function simulate(dt, t) {
raycaster.setFromCamera(mouseNDC, camera);
const ro = raycaster.ray.origin;
const rd = raycaster.ray.direction;
for (const f of fishes) {
// closest point on mouse ray to this fish
_toFish.subVectors(f.pos, ro);
const along = Math.max(0, _toFish.dot(rd));
_closest.copy(rd).multiplyScalar(along).add(ro);
const distToRay = f.pos.distanceTo(_closest);
// wander
f.wander.x += (Math.random() - 0.5) * 2.4 * dt;
f.wander.y += (Math.random() - 0.5) * 1.6 * dt;
f.wander.z += (Math.random() - 0.5) * 2.0 * dt;
f.wander.multiplyScalar(0.96);
_steer.copy(f.wander);
// soft walls
const m = 3;
if (f.pos.x > BOUNDS.x) _steer.x -= (f.pos.x - BOUNDS.x) * 0.9;
if (f.pos.x < -BOUNDS.x) _steer.x -= (f.pos.x + BOUNDS.x) * 0.9;
if (f.pos.y > BOUNDS.yMax) _steer.y -= (f.pos.y - BOUNDS.yMax) * 0.9;
if (f.pos.y < BOUNDS.yMin) _steer.y -= (f.pos.y - BOUNDS.yMin) * 0.9;
if (f.pos.z > BOUNDS.z + 9) _steer.z -= (f.pos.z - (BOUNDS.z + 9)) * 0.9;
if (f.pos.z < BOUNDS.z - 4) _steer.z -= (f.pos.z - (BOUNDS.z - 4)) * 0.9;
// flee from cursor
let speed = 1.6 * f.scale;
if (mouseActive && distToRay < 4.5) {
const urgency = 1 - distToRay / 4.5;
_fleeDir.subVectors(f.pos, _closest).normalize();
_steer.addScaledVector(_fleeDir, urgency * 26);
speed += urgency * 9 * f.scale;
f.panic = Math.min(1, f.panic + urgency * 4 * dt);
}
f.panic = Math.max(0, f.panic - 1.2 * dt);
// schooling cohesion (gentle)
for (let j = 0; j < fishes.length; j += 3) {
const o = fishes[j];
if (o === f) continue;
const d = f.pos.distanceTo(o.pos);
if (d < 2.2 && d > 0.001) {
_steer.addScaledVector(_toFish.copy(o.pos).sub(f.pos).normalize(), -0.6 / d);
}
}
f.vel.addScaledVector(_steer, dt);
// clamp speed
const sp = f.vel.length();
const maxSp = speed;
if (sp > maxSp) f.vel.multiplyScalar(maxSp / sp);
if (sp < maxSp * 0.4) f.vel.multiplyScalar(1 + dt * 0.8);
f.pos.addScaledVector(f.vel, dt);
f.group.position.copy(f.pos);
// orientation — face velocity, slight roll when panicking
if (f.vel.lengthSq() > 0.0001) {
const look = _toFish.copy(f.pos).add(f.vel);
f.group.lookAt(look);
f.group.rotation.z += f.panic * Math.sin(t * 14 + f.phase) * 0.25;
}
// tail wag — faster when panicked
const wagSp = 6 + f.panic * 14;
f.tailPivot.rotation.y = Math.sin(t * wagSp + f.phase) * (0.45 + f.panic * 0.3);
}
}
function animate(t, dt) {
for (const w of weeds) {
w.mesh.rotation.z = Math.sin(t * 0.8 + w.phase) * 0.12;
w.mesh.rotation.x = Math.cos(t * 0.6 + w.phase) * 0.07;
}
for (const b of bubbles) {
b.mesh.position.y += b.speed * dt;
b.mesh.position.x += Math.sin(t * 2 + b.wob) * 0.15 * dt;
if (b.mesh.position.y > 9) {
b.mesh.position.y = -4;
b.mesh.position.x = (Math.random() - 0.5) * 26;
b.mesh.position.z = (Math.random() - 0.5) * 14 - 2;
}
}
glow.intensity = 36 + Math.sin(t * 1.3) * 8;
}
function tick() {
const dt = Math.min(clock.getDelta(), 0.05);
const t = clock.elapsedTime;
simulate(dt, t);
animate(t, dt);
// camera drift + slight mouse parallax
camTarget.x = mouseNDC.x * 2.2;
camTarget.y = 2.2 + mouseNDC.y * 1.2;
camera.position.x += (camTarget.x - camera.position.x) * 0.03;
camera.position.y += (camTarget.y - camera.position.y) * 0.03;
camera.lookAt(0, 1, -2);
renderer.render(scene, camera);
requestAnimationFrame(tick);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
tick();
</script>
</body>
</html>Reasoning trace (475 tokens)
Create a 3D aquarium with Three.js. Fish swim, flee from mouse cursor. Use import map. Fish built from geometry (body cone + tail), boids-like motion with flee behavior from mouse projected into 3D. Add bubbles, caustics-ish background, seaweed, sand floor, light rays maybe. Self-running without keyboard needed (mouse-based). Should look good at first paint. Plan: - Scene: fog underwater blue, sand floor, rocks, seaweed (animated), bubbles rising, fish. - Fish: simple low-poly fish made from scaled sphere/cone, tail wiggle via bone-less animation (rotate tail mesh). - Movement: each fish has velocity, wander target, avoid walls, flee from mouse ray point. Mouse -> unproject to a point at some depth; compute distance, if close, steer away with stronger force and speed boost. - Also add caustic light via moving spotlight? Keep simple. - Title overlay, instructions. - Handle resize. Write code carefully. Fish class: body: sphere scaled (elongated), tail: cone rotated, fins. Colors varied. Eye spheres. Update: steering = wander + bounds avoidance + flee. Flee point: raycast mouse onto plane z = 0? Better: compute point along camera ray at closest distance to fish — for each fish, project fish position onto mouse ray; if distance from fish to ray < fleeRadius, steer away. That's accurate and feels good. Speed: base 1.2, boost when fleeing. Orientation: fish look along velocity; banking slight. Bubbles: points or small spheres rising. Seaweed: cylinders/planes with vertex animation via sine in onBeforeRender or simple rotation sway. Rocks: icosahedron deformed. Sand: big plane with slight noise displacement. Lights: hemisphere + directional + point light. Post: none. Let me write it. Keep code robust, no errors. Tail wiggle: tail.rotation.y = sin(t*speed + phase)*0.4; body slight rotation. Also make fish flee near cursor: use raycaster from camera through mouse NDC; closest point on ray to fish pos. Camera slight parallax with mouse too. Overlay: title top-left, hint bottom. Fade hint after a while? Keep visible. Write the HTML.