Interactive 3D aquariumOne-line
GPT-6.1 Terra
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 7 s
- Cost
- $0.030
- Output tokens
- 5,954
- Spent thinking
- 0 tokens
- Speed
- 90.8 tok/s
- Console errors
- 0
- First token after
- 1.0 s
- First visible text after
- 1.0 s
- Prompt tokens
- 423
- Code size
- 17.0 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6.1-terra- Reasoning sent
{"effort":"none"}- 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 · 425 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stillwater Aquarium</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; }
html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; }
body {
color: #edf8ef;
background: #071e1e;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
#scene { position: absolute; inset: 0; width: 100%; height: 100%; }
#scene canvas { display: block; width: 100%; height: 100%; }
.vignette {
position: absolute; inset: 0; pointer-events: none;
background: linear-gradient(180deg, rgba(3,22,22,.66) 0%, transparent 20%, transparent 75%, rgba(3,18,18,.6) 100%),
linear-gradient(90deg, rgba(4,24,23,.30), transparent 30%, transparent 75%, rgba(4,24,23,.25));
}
header {
position: absolute; top: 32px; left: 42px; right: 42px;
display: flex; justify-content: space-between; align-items: flex-start;
pointer-events: none;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-icon {
width: 38px; height: 38px; border: 1px solid rgba(214,239,219,.38);
border-radius: 50%; display: grid; place-items: center;
color: #d8ecda;
}
.brand-name { font-family: Georgia, serif; font-size: 21px; letter-spacing: -.3px; }
.brand-sub { margin-top: 3px; color: #a9c5b8; font-size: 9px; letter-spacing: .19em; text-transform: uppercase; }
.top-right {
color: #c0d7c8; font-size: 10px; letter-spacing: .17em; text-transform: uppercase;
display: flex; align-items: center; gap: 9px; padding-top: 12px;
}
.dot { width: 6px; height: 6px; background: #b7dbb3; border-radius: 50%; box-shadow: 0 0 9px #a3dfb7; }
.copy {
position: absolute; left: 42px; bottom: 100px; max-width: 380px;
pointer-events: none; text-shadow: 0 2px 20px rgba(0,0,0,.24);
}
.eyebrow { color: #b7d5c3; font-size: 10px; letter-spacing: .2em; text-transform: uppercase; margin-bottom: 12px; }
h1 { font: 400 clamp(39px, 5vw, 64px)/.98 Georgia, serif; letter-spacing: -2px; margin: 0; }
.description { margin: 16px 0 0; max-width: 290px; color: #c6d9ce; font-size: 13px; line-height: 1.65; }
.footer {
position: absolute; bottom: 28px; left: 42px; right: 42px;
display: flex; justify-content: space-between; align-items: end;
color: #b5cec0; font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
pointer-events: none;
}
.instruction { display: flex; align-items: center; gap: 10px; }
.mouse {
width: 17px; height: 24px; border: 1px solid #b8d1c0; border-radius: 9px;
position: relative;
}
.mouse:after { content: ""; position: absolute; left: 7px; top: 4px; width: 1px; height: 5px; background: #b8d1c0; }
.number { font-family: Georgia, serif; font-size: 15px; color: #d9e9dc; letter-spacing: 0; margin-right: 6px; }
.cursor-ring {
position: absolute; width: 56px; height: 56px; border: 1px solid rgba(221,249,226,.48);
border-radius: 50%; pointer-events: none; opacity: 0; transform: translate(-50%, -50%);
transition: opacity .4s; box-shadow: 0 0 20px rgba(162,234,196,.12), inset 0 0 20px rgba(162,234,196,.06);
}
.cursor-ring:before, .cursor-ring:after { content: ""; position: absolute; background: rgba(223,249,229,.6); left: 50%; top: 50%; transform: translate(-50%,-50%); }
.cursor-ring:before { width: 10px; height: 1px; }
.cursor-ring:after { width: 1px; height: 10px; }
@media (max-width: 600px) {
header { left: 22px; right: 22px; top: 23px; }
.top-right { font-size: 9px; }
.copy { left: 22px; bottom: 100px; max-width: 260px; }
h1 { font-size: 45px; }
.description { font-size: 12px; max-width: 240px; }
.footer { left: 22px; right: 22px; bottom: 22px; }
.footer-right { display: none; }
}
</style>
</head>
<body>
<div id="scene"></div>
<div class="vignette"></div>
<header>
<div class="brand">
<div class="brand-icon">
<svg width="23" height="23" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M3 12c2.7-3.7 6.2-5 9.3-4.2 2.4.6 4.1 2.1 5.3 4.2-1.2 2.1-2.9 3.6-5.3 4.2C9.2 17 5.7 15.7 3 12Z" stroke="currentColor" stroke-width="1.1"/>
<path d="m17.5 12 3.5-3v6l-3.5-3Z" stroke="currentColor" stroke-width="1.1" stroke-linejoin="round"/>
<circle cx="7.6" cy="11.1" r=".7" fill="currentColor"/>
</svg>
</div>
<div><div class="brand-name">Stillwater</div><div class="brand-sub">A living aquarium</div></div>
</div>
<div class="top-right"><span class="dot"></span> Live habitat</div>
</header>
<div class="copy">
<div class="eyebrow">A little moment of calm</div>
<h1>Drift a<br>little closer.</h1>
<p class="description">A quiet world beneath the surface. Take a breath and watch the little ones wander.</p>
</div>
<div class="footer">
<div class="instruction"><span class="mouse"></span><span>Move your cursor to interact</span></div>
<div class="footer-right"><span class="number">01</span> / An underwater escape</div>
</div>
<div class="cursor-ring" id="ring"></div>
<script type="module">
import * as THREE from 'three';
const container = document.getElementById('scene');
const ring = document.getElementById('ring');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x103c3a);
scene.fog = new THREE.FogExp2(0x163e3b, 0.027);
const camera = new THREE.PerspectiveCamera(40, innerWidth / innerHeight, 0.1, 120);
camera.position.set(0, 2.7, 19.5);
camera.lookAt(0, 1.7, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.18;
container.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xc5f4d8, 0x183b31, 2.1));
const sun = new THREE.DirectionalLight(0xe5f6d2, 3.1);
sun.position.set(-5, 12, 7);
scene.add(sun);
const fill = new THREE.DirectionalLight(0x6bb9aa, 1.2);
fill.position.set(7, 4, -9);
scene.add(fill);
const rand = (a, b) => a + Math.random() * (b - a);
const mat = (color, roughness = .8, metalness = 0) => new THREE.MeshStandardMaterial({ color, roughness, metalness });
const sand = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), mat(0x8d9b73));
sand.rotation.x = -Math.PI / 2;
sand.position.y = -3.5;
scene.add(sand);
const sandDetail = new THREE.Group();
scene.add(sandDetail);
const pebbleGeo = new THREE.SphereGeometry(1, 8, 6);
const pebbleMats = [mat(0x9da789), mat(0xb8b89a), mat(0x647d69), mat(0x778b72)];
for (let i = 0; i < 220; i++) {
const p = new THREE.Mesh(pebbleGeo, pebbleMats[i % pebbleMats.length]);
p.position.set(rand(-16,16), -3.45, rand(-11, 9));
const s = rand(.025,.11);
p.scale.set(s * 1.6, s * .38, s);
sandDetail.add(p);
}
const rockMaterials = [mat(0x617b6d), mat(0x798c79), mat(0x526d61), mat(0x8a9b82)];
function rock(x, z, sx, sy, sz, index) {
const mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(1, 2), rockMaterials[index % rockMaterials.length]);
mesh.position.set(x, -3.42 + sy * .35, z);
mesh.scale.set(sx, sy, sz);
mesh.rotation.set(rand(-.3,.3), rand(0,6), rand(-.25,.25));
scene.add(mesh);
}
rock(-7.6,-2.8,2.2,1.0,1.3,0);
rock(-6.2,-3.5,1.5,.72,1.2,1);
rock(-4.5,-5.5,1.8,.9,1.2,2);
rock(7.6,-2.7,2.5,1.05,1.5,2);
rock(5.6,-4,1.4,.7,1.1,1);
rock(9,1,1.7,.72,1.2,0);
rock(1,-7,2,.55,1.3,3);
rock(-10,2,1.9,.8,1.4,2);
function leafMesh(length, width, color) {
const segments = 9;
const positions = [], indices = [];
for (let i = 0; i <= segments; i++) {
const t = i / segments;
const w = Math.sin(Math.PI * Math.pow(t, .75)) * width;
const bend = Math.sin(t * 2.5) * length * .13;
positions.push(-w, t * length, bend, w, t * length, bend);
}
for (let i = 0; i < segments; i++) {
const n = i * 2;
indices.push(n,n+1,n+2, n+1,n+3,n+2);
}
const g = new THREE.BufferGeometry();
g.setAttribute('position', new THREE.Float32BufferAttribute(positions,3));
g.setIndex(indices);
g.computeVertexNormals();
return new THREE.Mesh(g, new THREE.MeshStandardMaterial({color, side:THREE.DoubleSide, roughness:.82}));
}
const plants = [];
function plant(x, z, height, count, type = 0) {
const group = new THREE.Group();
group.position.set(x, -3.4, z);
scene.add(group);
const colors = type ? [0x668c62,0x84a877,0x547b59] : [0x356d50,0x4c8960,0x6b9a6a,0x3e7856];
for (let i=0; i<count; i++) {
const len = height * rand(.5,1.15);
const leaf = leafMesh(len, rand(.10,.23) * (type ? 1.4 : 1), colors[i % colors.length]);
leaf.rotation.y = rand(0, Math.PI*2);
leaf.rotation.z = rand(-.55,.55);
leaf.position.x = rand(-.18,.18);
leaf.position.z = rand(-.18,.18);
group.add(leaf);
}
plants.push({group, phase:rand(0,6), speed:rand(.45,.9)});
}
plant(-9.3,-4.8,4.8,16);
plant(-7.9,-5.4,3.3,12,1);
plant(-5.7,-6,3.8,14);
plant(-3.9,-7,2.8,11,1);
plant(9.5,-5,5.2,18);
plant(7.8,-5.8,3.5,14,1);
plant(5.8,-6.5,4.2,15);
plant(3.8,-7.3,2.8,12,1);
plant(-10,1.5,2.2,10,1);
plant(10,1.5,2.7,12);
plant(1.9,-9,2.5,10);
plant(-1.4,-9,2,9,1);
const fishBodyGeo = new THREE.SphereGeometry(1, 24, 16);
const eyeGeo = new THREE.SphereGeometry(.065, 12, 8);
const pupilMat = mat(0x102823, .3);
const eyeWhiteMat = mat(0xe5e0c8, .45);
const palettes = [
[0xeaa66c,0xf3c58f,0xb96349],
[0xe8b77b,0xf5d6a7,0xc57b5c],
[0xd7e0bd,0xf0e8c8,0x91ae9b],
[0xd89073,0xf4b89a,0xa95951],
[0xd3c79a,0xf3e3bc,0x9aa98a]
];
function finGeometry(points) {
const shape = new THREE.Shape();
shape.moveTo(points[0][0],points[0][1]);
for (let i=1;i<points.length;i++) shape.lineTo(points[i][0],points[i][1]);
shape.closePath();
return new THREE.ShapeGeometry(shape);
}
const tailGeo = finGeometry([[0,0],[.52,.36],[.43,.05],[.52,-.36]]);
const dorsalGeo = finGeometry([[-.45,.05],[.02,.41],[.46,.34],[.56,.05]]);
const bottomGeo = finGeometry([[-.1,0],[.34,-.26],[.57,0]]);
const sideFinGeo = finGeometry([[0,0],[.38,-.19],[.3,.1]]);
function makeFish(index) {
const pal = palettes[index % palettes.length];
const root = new THREE.Group();
const model = new THREE.Group();
root.add(model);
const bodyMat = new THREE.MeshStandardMaterial({color:pal[0], roughness:.48, metalness:.05});
const bellyMat = new THREE.MeshStandardMaterial({color:pal[1], roughness:.6});
const finMat = new THREE.MeshStandardMaterial({color:pal[2], side:THREE.DoubleSide, transparent:true, opacity:.87, roughness:.65});
const body = new THREE.Mesh(fishBodyGeo, bodyMat);
body.scale.set(.83,.37,.24);
model.add(body);
const belly = new THREE.Mesh(fishBodyGeo, bellyMat);
belly.scale.set(.62,.19,.225);
belly.position.set(-.08,-.17,0);
model.add(belly);
const tailPivot = new THREE.Group();
tailPivot.position.x = .68;
const tail = new THREE.Mesh(tailGeo,finMat);
tailPivot.add(tail);
model.add(tailPivot);
const dorsal = new THREE.Mesh(dorsalGeo,finMat);
dorsal.position.set(-.05,.24,0);
model.add(dorsal);
const bottom = new THREE.Mesh(bottomGeo,finMat);
bottom.position.set(.05,-.27,0);
model.add(bottom);
for (const side of [-1,1]) {
const white = new THREE.Mesh(eyeGeo,eyeWhiteMat);
white.position.set(-.52,.10,side*.19);
white.scale.set(1,1,.45);
model.add(white);
const eye = new THREE.Mesh(eyeGeo,pupilMat);
eye.scale.set(.55,.55,.35);
eye.position.set(-.54,.105,side*.228);
model.add(eye);
const fin = new THREE.Mesh(sideFinGeo,finMat);
fin.position.set(-.09,-.03,side*.22);
fin.rotation.y = side * .32;
model.add(fin);
}
const scale = rand(.58,1.05) * (index < 4 ? 1.12 : 1);
root.scale.setScalar(scale);
root.position.set(rand(-7,7),rand(-1.6,4.2),rand(-4.5,3));
scene.add(root);
const angle = rand(0,Math.PI*2);
return {
root, model, tail:tailPivot,
velocity:new THREE.Vector3(Math.cos(angle),rand(-.12,.12),Math.sin(angle)*.35).normalize().multiplyScalar(rand(.6,1)),
phase:rand(0,10), speed:rand(.6,1.05), turn:rand(0,10),
screen:new THREE.Vector3()
};
}
const fishes = Array.from({length:17}, (_,i) => makeFish(i));
const bubbleMat = new THREE.MeshPhysicalMaterial({
color:0xc9f8e9, metalness:.1, roughness:.08, transparent:true,
opacity:.33, transmission:.3, side:THREE.DoubleSide, depthWrite:false
});
const bubbleGeo = new THREE.SphereGeometry(1,12,10);
const bubbles = [];
for (let i=0; i<48; i++) {
const mesh = new THREE.Mesh(bubbleGeo,bubbleMat);
const size = rand(.025,.10);
mesh.scale.setScalar(size);
mesh.position.set(rand(-8,8),rand(-3.5,8),rand(-6,2));
scene.add(mesh);
bubbles.push({mesh, speed:rand(.25,.75), phase:rand(0,6), x:mesh.position.x});
}
const dustCount = 320;
const dustPos = new Float32Array(dustCount*3);
for (let i=0;i<dustCount;i++) {
dustPos[i*3]=rand(-14,14);
dustPos[i*3+1]=rand(-3,9);
dustPos[i*3+2]=rand(-10,5);
}
const dustGeo = new THREE.BufferGeometry();
dustGeo.setAttribute('position',new THREE.BufferAttribute(dustPos,3));
const dust = new THREE.Points(dustGeo,new THREE.PointsMaterial({color:0xc5e5cd,size:.035,transparent:true,opacity:.42,depthWrite:false}));
scene.add(dust);
function lightShaft(x, width, opacity) {
const geometry = new THREE.PlaneGeometry(width, 20);
const material = new THREE.MeshBasicMaterial({color:0xc3ebcd,transparent:true,opacity,depthWrite:false,side:THREE.DoubleSide});
const shaft = new THREE.Mesh(geometry,material);
shaft.position.set(x,5,-8);
shaft.rotation.z = -.26;
scene.add(shaft);
}
lightShaft(-5,1.2,.025);
lightShaft(-2.8,2.2,.018);
lightShaft(4.5,1.6,.023);
lightShaft(7, .8,.025);
const mouse = {x:0,y:0,active:false};
let pointerTimer;
window.addEventListener('pointermove', e => {
mouse.x = e.clientX / innerWidth * 2 - 1;
mouse.y = -(e.clientY / innerHeight * 2 - 1);
mouse.active = true;
ring.style.left = e.clientX + 'px';
ring.style.top = e.clientY + 'px';
ring.style.opacity = '1';
clearTimeout(pointerTimer);
pointerTimer = setTimeout(() => { mouse.active = false; ring.style.opacity = '0'; }, 3500);
});
window.addEventListener('pointerleave', () => { mouse.active = false; ring.style.opacity = '0'; });
const clock = new THREE.Clock();
const targetVelocity = new THREE.Vector3();
const escape = new THREE.Vector3();
const temp = new THREE.Vector3();
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(),.04);
const time = clock.elapsedTime;
plants.forEach(p => {
p.group.rotation.z = Math.sin(time * p.speed + p.phase) * .035;
p.group.rotation.x = Math.sin(time * p.speed * .7 + p.phase) * .025;
});
bubbles.forEach(b => {
b.mesh.position.y += b.speed * dt;
b.mesh.position.x = b.x + Math.sin(time * .8 + b.phase) * .12;
if (b.mesh.position.y > 8) b.mesh.position.y = -3.4;
});
dust.rotation.y = Math.sin(time*.04)*.025;
fishes.forEach((f,i) => {
const p = f.root.position;
f.turn += dt * .3;
targetVelocity.set(
Math.sin(f.turn + f.phase) * .13,
Math.sin(time * .45 + f.phase) * .12,
Math.cos(f.turn * .7 + f.phase) * .07
);
if (p.x > 9) targetVelocity.x -= (p.x - 9) * .8;
if (p.x < -9) targetVelocity.x += (-9 - p.x) * .8;
if (p.y > 5.1) targetVelocity.y -= (p.y - 5.1) * .8;
if (p.y < -2.5) targetVelocity.y += (-2.5 - p.y) * .8;
if (p.z > 4) targetVelocity.z -= (p.z - 4) * .6;
if (p.z < -6) targetVelocity.z += (-6 - p.z) * .6;
if (mouse.active) {
f.screen.copy(p).project(camera);
const dx = (f.screen.x - mouse.x) * (innerWidth / innerHeight);
const dy = f.screen.y - mouse.y;
const distance = Math.sqrt(dx*dx+dy*dy);
if (distance < .34) {
const force = (1 - distance / .34) * 4.5;
escape.set(dx,dy,0);
if (escape.lengthSq() < .0001) escape.set(1,.3,0);
escape.normalize();
targetVelocity.addScaledVector(escape,force);
}
}
f.velocity.addScaledVector(targetVelocity,dt);
const speed = f.velocity.length();
const maxSpeed = mouse.active ? 2.4 : 1.2;
if (speed > maxSpeed) f.velocity.multiplyScalar(maxSpeed/speed);
if (speed < f.speed*.5) f.velocity.multiplyScalar((f.speed*.5)/Math.max(speed,.001));
p.addScaledVector(f.velocity,dt);
const desired = Math.atan2(f.velocity.z, -f.velocity.x);
let diff = desired - f.root.rotation.y;
diff = Math.atan2(Math.sin(diff),Math.cos(diff));
f.root.rotation.y += diff * Math.min(1,dt*2.5);
f.model.rotation.z = THREE.MathUtils.lerp(f.model.rotation.z, -f.velocity.y*.18, dt*2);
f.tail.rotation.y = Math.sin(time*9 + f.phase) * (.24 + Math.min(speed,2)*.12);
f.model.position.y = Math.sin(time*2 + f.phase)*.025;
});
renderer.render(scene,camera);
}
window.addEventListener('resize', () => {
camera.aspect = innerWidth/innerHeight;
camera.position.z = innerWidth < 600 ? 24 : 19.5;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
});
if (innerWidth < 600) { camera.position.z = 24; camera.lookAt(0,1.7,0); }
animate();
</script>
</body>
</html>