Interactive 3D aquariumOne-line
GPT-6 Luna
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 36.4 s
- Cost
- $0.0021
- Output tokens
- 4,021
- Spent thinking
- 0 tokens
- Speed
- 139.5 tok/s
- Console errors
- 0
- First token after
- 7.6 s
- First visible text after
- 7.6 s
- Prompt tokens
- 423
- Code size
- 12.4 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna- Reasoning sent
{"effort":"none"}- 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 · 222 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Blue Room — 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;background:#04131d;color:#e9f8f5;font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}
#scene{position:fixed;inset:0}
canvas{display:block;width:100%;height:100%;outline:none}
.shade{position:fixed;inset:0;pointer-events:none;background:linear-gradient(90deg,rgba(2,14,22,.78) 0%,rgba(2,14,22,.42) 34%,transparent 70%),linear-gradient(0deg,rgba(1,12,18,.44),transparent 30%,rgba(2,14,22,.18))}
.topbar{position:fixed;inset:0 0 auto;display:flex;align-items:center;justify-content:space-between;padding:28px 42px;pointer-events:none}
.brand{display:flex;align-items:center;gap:12px;font-size:12px;font-weight:700;letter-spacing:.2em;text-transform:uppercase}
.brand-mark{height:30px;width:30px;border:1px solid rgba(145,230,215,.55);border-radius:50%;display:grid;place-items:center;color:#8ce4d5;font-size:14px}
.status{display:flex;align-items:center;gap:9px;color:rgba(226,245,241,.65);font-size:10px;letter-spacing:.16em;text-transform:uppercase}
.dot{width:6px;height:6px;background:#80dbc7;border-radius:50%;box-shadow:0 0 12px #80dbc7}
.copy{position:fixed;left:9.5%;top:50%;transform:translateY(-43%);width:min(390px,40vw);pointer-events:none}
.eyebrow{display:flex;align-items:center;gap:11px;color:#8ce2d1;font-size:10px;letter-spacing:.25em;text-transform:uppercase;font-weight:700}
.eyebrow:before{content:"";display:block;width:28px;height:1px;background:#8ce2d1}
h1{font-family:Georgia,"Times New Roman",serif;font-size:clamp(43px,6.3vw,82px);line-height:.98;font-weight:400;letter-spacing:-.045em;margin:22px 0 20px}
h1 em{font-weight:400;color:#8edbca}
.description{max-width:305px;color:rgba(224,242,239,.64);font-size:14px;line-height:1.75;margin:0}
.hint{display:flex;align-items:center;gap:12px;margin-top:35px;color:rgba(224,242,239,.72);font-size:11px;letter-spacing:.09em;text-transform:uppercase}
.mouse-icon{width:19px;height:27px;border:1px solid rgba(161,224,212,.7);border-radius:12px;position:relative}
.mouse-icon:before{content:"";position:absolute;top:5px;left:8px;width:2px;height:5px;border-radius:2px;background:#8ce2d1}
.bottom{position:fixed;left:42px;right:42px;bottom:24px;display:flex;align-items:center;justify-content:space-between;color:rgba(218,239,234,.45);font-size:9px;letter-spacing:.18em;text-transform:uppercase;pointer-events:none}
.bottom span:last-child{display:flex;align-items:center;gap:9px}
.bottom span:last-child:before{content:"";width:28px;height:1px;background:rgba(218,239,234,.4)}
@media(max-width:700px){
.topbar{padding:19px 20px}.status{font-size:8px}
.copy{left:8%;top:44%;width:80%;transform:translateY(-25%)}
h1{font-size:clamp(48px,13vw,72px);margin:16px 0}
.description{font-size:13px;max-width:285px}
.hint{margin-top:24px}
.bottom{left:20px;right:20px;bottom:17px;font-size:8px}
.shade{background:linear-gradient(90deg,rgba(2,14,22,.72),rgba(2,14,22,.25) 70%),linear-gradient(0deg,rgba(1,12,18,.48),transparent 48%)}
}
@media(max-height:570px) and (min-width:701px){.copy{top:53%}h1{font-size:56px;margin:15px 0}.hint{margin-top:20px}}
</style>
</head>
<body>
<div id="scene"></div>
<div class="shade"></div>
<header class="topbar">
<div class="brand"><div class="brand-mark">≈</div><span>Blue Room</span></div>
<div class="status"><i class="dot"></i> Living collection / 01</div>
</header>
<main class="copy">
<div class="eyebrow">A quiet little world</div>
<h1>Drift<br>into the <em>blue.</em></h1>
<p class="description">A small moment of calm, just beneath the surface. Follow the light and see what swims by.</p>
<div class="hint"><span class="mouse-icon"></span><span>Move your cursor to explore</span></div>
</main>
<footer class="bottom"><span>Ocean study no. 01</span><span>Move gently</span></footer>
<script type="module">
import * as THREE from 'three';
const host=document.getElementById('scene');
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x082432);
scene.fog=new THREE.FogExp2(0x082432,.035);
const camera=new THREE.PerspectiveCamera(48,innerWidth/innerHeight,.1,100);
camera.position.set(0,0,13);
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.08;
host.appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0x8cc7c5,1.05));
const key=new THREE.PointLight(0x9ce9d8,50,30);key.position.set(-4,5,5);scene.add(key);
const blueLight=new THREE.PointLight(0x246c9a,32,30);blueLight.position.set(5,-3,3);scene.add(blueLight);
const tank=new THREE.Group();scene.add(tank);
const glassMat=new THREE.MeshPhysicalMaterial({color:0x286479,transparent:true,opacity:.075,roughness:.12,metalness:.18,side:THREE.DoubleSide,depthWrite:false});
const back=new THREE.Mesh(new THREE.PlaneGeometry(21,13),new THREE.MeshBasicMaterial({color:0x0a3140}));
back.position.z=-4.2;tank.add(back);
// A fine, luminous frame makes the aquarium read as a glass volume.
const frameMat=new THREE.LineBasicMaterial({color:0x85d4c6,transparent:true,opacity:.27});
const frameGeo=new THREE.EdgesGeometry(new THREE.BoxGeometry(20.8,12.5,8.2));
const frame=new THREE.LineSegments(frameGeo,frameMat);frame.position.set(0,0,-.1);tank.add(frame);
const glass=new THREE.Mesh(new THREE.BoxGeometry(20.8,12.5,8.2),glassMat);glass.position.set(0,0,-.1);tank.add(glass);
// Soft shafts of light in the water.
for(let i=0;i<5;i++){
const ray=new THREE.Mesh(new THREE.PlaneGeometry(.24,10),new THREE.MeshBasicMaterial({color:0x9de7d8,transparent:true,opacity:.045,side:THREE.DoubleSide,depthWrite:false}));
ray.position.set(-6+i*3.2,1.2,-3.9);ray.rotation.z=-.13+(i%3)*.08;tank.add(ray);
}
// Gravel and gently swaying plants.
const gravelMat=new THREE.MeshStandardMaterial({color:0x425e54,roughness:1});
for(let i=0;i<90;i++){
const r=.035+Math.random()*.095;
const pebble=new THREE.Mesh(new THREE.SphereGeometry(r,7,5),gravelMat);
pebble.position.set(-9.8+Math.random()*19.6,-5.65+Math.random()*.18,-3.4+Math.random()*1.3);
pebble.scale.y=.55; tank.add(pebble);
}
const plants=[];
for(let i=0;i<12;i++){
const x=-9.2+i*1.65+(Math.random()-.5)*.55;
const h=.8+Math.random()*2.2;
const points=[new THREE.Vector3(x,-5.65,-2.9),new THREE.Vector3(x+(Math.random()-.5)*.4,-5.65+h*.5,-2.9),new THREE.Vector3(x+(Math.random()-.5)*.8,-5.65+h,-2.9)];
const curve=new THREE.CatmullRomCurve3(points);
const mesh=new THREE.Mesh(new THREE.TubeGeometry(curve,16,.035+Math.random()*.025,5,false),new THREE.MeshStandardMaterial({color:[0x327e70,0x4d9a78,0x27675f][i%3],roughness:.75}));
tank.add(mesh);plants.push({mesh,x,h,phase:Math.random()*6});
}
// Fish geometry: tapered body, fins, tail, and bright eyes, grouped around a swimming pivot.
const fishGroup=new THREE.Group();scene.add(fishGroup);
const fishList=[];
const palettes=[
{body:0xf2aa69,fin:0xd97e52,detail:0xffe1a6},
{body:0x6fd2c3,fin:0x318e91,detail:0xd6fff0},
{body:0xe5d58b,fin:0xb29e58,detail:0xfff5c5},
{body:0xd17d82,fin:0x914f69,detail:0xffd8c4},
{body:0x86aee1,fin:0x526fa8,detail:0xe2f4ff}
];
const sphereGeo=new THREE.SphereGeometry(1,20,14);
function createFish(index){
const p=palettes[index%palettes.length];
const root=new THREE.Group();
const bodyMat=new THREE.MeshStandardMaterial({color:p.body,roughness:.36,metalness:.08});
const finMat=new THREE.MeshStandardMaterial({color:p.fin,roughness:.42,side:THREE.DoubleSide});
const body=new THREE.Mesh(sphereGeo,bodyMat);
body.scale.set(.67,.32,.25);root.add(body);
const tail=new THREE.Mesh(new THREE.BufferGeometry(),finMat);
const tailShape=new THREE.Shape();
tailShape.moveTo(0,0);tailShape.lineTo(-.48,.38);tailShape.lineTo(-.39,0);tailShape.lineTo(-.48,-.38);tailShape.closePath();
const tailMesh=new THREE.Mesh(new THREE.ShapeGeometry(tailShape),finMat);
tailMesh.position.x=-.58;tailMesh.position.z=-.02;root.add(tailMesh);
const dorsalShape=new THREE.Shape();dorsalShape.moveTo(-.12,.2);dorsalShape.lineTo(.05,.52);dorsalShape.lineTo(.34,.22);dorsalShape.closePath();
const dorsal=new THREE.Mesh(new THREE.ShapeGeometry(dorsalShape),finMat);dorsal.position.z=0;root.add(dorsal);
const lowerShape=new THREE.Shape();lowerShape.moveTo(-.12,-.2);lowerShape.lineTo(.08,-.42);lowerShape.lineTo(.3,-.22);lowerShape.closePath();
const lower=new THREE.Mesh(new THREE.ShapeGeometry(lowerShape),finMat);root.add(lower);
const eyeMat=new THREE.MeshStandardMaterial({color:0x09222b,roughness:.2});
const eye=new THREE.Mesh(new THREE.SphereGeometry(.052,10,8),eyeMat);eye.position.set(.46,.07,.213);root.add(eye);
const glint=new THREE.Mesh(new THREE.SphereGeometry(.016,8,6),new THREE.MeshBasicMaterial({color:0xffffff}));glint.position.set(.472,.089,.254);root.add(glint);
const stripeMat=new THREE.MeshBasicMaterial({color:p.detail,transparent:true,opacity:.55});
const stripe=new THREE.Mesh(new THREE.SphereGeometry(1,12,10),stripeMat);stripe.scale.set(.045,.25,.258);stripe.position.set(.02,0,0);root.add(stripe);
fishGroup.add(root);
const z=-1.35+Math.random()*2.3;
const y=-1.8+Math.random()*4.0;
fishList.push({root,tailMesh,index,x:(Math.random()-.5)*13,y,z,baseY:y,phase:Math.random()*6.28,speed:.55+Math.random()*.42, flee:0, fleeX:0,fleeY:0,side:Math.random()>.5?1:-1});
}
for(let i=0;i<11;i++)createFish(i);
const bubbles=[];
const bubbleMat=new THREE.MeshBasicMaterial({color:0xb9eee6,transparent:true,opacity:.22,wireframe:true});
for(let i=0;i<34;i++){
const radius=.025+Math.random()*.045;
const mesh=new THREE.Mesh(new THREE.SphereGeometry(radius,7,6),bubbleMat);
mesh.position.set(-9.5+Math.random()*19,-5.7+Math.random()*10,-2.8+Math.random()*2);
scene.add(mesh);bubbles.push({mesh,speed:.3+Math.random()*.55,phase:Math.random()*6});
}
const pointer=new THREE.Vector2(9,9);
const raycaster=new THREE.Raycaster();
const interactionPlane=new THREE.Plane(new THREE.Vector3(0,0,1),0);
const pointerWorld=new THREE.Vector3();
let hasPointer=false;
function movePointer(event){
const rect=renderer.domElement.getBoundingClientRect();
pointer.x=(event.clientX-rect.left)/rect.width*2-1;
pointer.y=-((event.clientY-rect.top)/rect.height*2-1);
raycaster.setFromCamera(pointer,camera);
if(raycaster.ray.intersectPlane(interactionPlane,pointerWorld))hasPointer=true;
}
renderer.domElement.addEventListener('pointermove',movePointer);
renderer.domElement.addEventListener('pointerleave',()=>{hasPointer=false});
const clock=new THREE.Clock();
function animate(){
requestAnimationFrame(animate);
const t=clock.getElapsedTime();
for(const f of fishList){
const fish=f.root;
const homeX=f.x+Math.sin(t*f.speed+f.phase)*2.15*f.side;
const homeY=f.baseY+Math.sin(t*1.3+f.phase)*.42;
if(hasPointer){
const dx=fish.position.x-pointerWorld.x,dy=fish.position.y-pointerWorld.y;
const dist=Math.sqrt(dx*dx+dy*dy);
if(dist<2.05){
f.flee=Math.max(f.flee,1);
const inv=1/Math.max(dist,.15);
f.fleeX=dx*inv;f.fleeY=dy*inv;
}
}
f.flee=Math.max(0,f.flee-.023);
const fleeEase=f.flee*f.flee;
fish.position.x=THREE.MathUtils.damp(fish.position.x,homeX+f.fleeX*fleeEase*2.7,2.8,1/60);
fish.position.y=THREE.MathUtils.damp(fish.position.y,homeY+f.fleeY*fleeEase*1.65,2.8,1/60);
fish.position.z=f.z+Math.sin(t*.8+f.phase)*.25;
fish.rotation.z=Math.sin(t*1.5+f.phase)*.055+f.fleeY*fleeEase*.16;
fish.rotation.y=f.side===1?0:Math.PI;
f.tailMesh.rotation.y=Math.sin(t*12+f.phase)*.45;
}
plants.forEach(p=>{p.mesh.rotation.z=Math.sin(t*.8+p.phase)*.035});
bubbles.forEach(b=>{
b.mesh.position.y+=b.speed/60;
b.mesh.position.x+=Math.sin(t+b.phase)*.0018;
if(b.mesh.position.y>6.2){b.mesh.position.y=-5.8;b.mesh.position.x=-9.5+Math.random()*19}
});
renderer.render(scene,camera);
}
animate();
function resize(){
camera.aspect=innerWidth/innerHeight;
camera.position.x=innerWidth<700?0:0;
camera.fov=innerWidth<700?56:48;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
}
addEventListener('resize',resize);
</script>
</body>
</html>