Interactive 3D aquariumOne-line
GPT-5 Nano
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 19 s
- Cost
- $0.0047
- Output tokens
- 11,810
- Spent thinking
- 7,936 tokens
- Speed
- 155 tok/s
- Console errors
- 0
- First token after
- 2.4 s
- First visible text after
- 1 min 3 s
- Prompt tokens
- 423
- Code size
- 13.3 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-5-nano- Reasoning sent
{"effort":"medium"}- 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 · 403 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>3D Aquarium - Fish Swim Away from Mouse</title>
<style>
:root {
--bgA: #0b1731;
--bgB: #0a1d3a;
}
html, body {
height: 100%;
margin: 0;
overflow: hidden;
background: linear-gradient(135deg, var(--bgA), var(--bgB));
font-family: system-ui, -apple-system, "Segoe UI", Roboto;
color: #eaf7ff;
}
#stage {
position: absolute;
inset: 0;
}
canvas {
width: 100%;
height: 100%;
display: block;
/* prevent scrollbars on edge cases */
outline: none;
}
/* Start overlay: semi-transparent but lets the aquarium peek through */
#startOverlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 10, 40, 0.25);
pointer-events: auto;
z-index: 2;
transition: opacity 0.4s ease;
}
#startOverlay.hidden {
opacity: 0;
pointer-events: none;
}
.panel {
padding: 22px 28px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.28);
border: 1px solid rgba(255,255,255,0.25);
text-align: center;
max-width: 90vw;
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
.panel h1 {
font-size: clamp(16px, 3vw, 28px);
margin: 0 0 6px;
letter-spacing: .4px;
}
.panel p {
margin: 0;
font-size: clamp(12px, 2.2vw, 14px);
color: #e8f3ff;
}
/* Subtle decorative glow for depth cues */
#glow {
position: absolute;
width: 60vmax;
height: 60vmax;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
pointer-events: none;
filter: blur(60px);
background: radial-gradient(circle at 50% 50%, rgba(70, 180, 255, 0.25), transparent 60%);
z-index: 0;
mix-blend-mode: screen;
}
</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="stage" aria-label="3D aquarium stage"></div>
<div id="glow" aria-hidden="true"></div>
<div id="startOverlay" class="overlay" aria-label="Start screen">
<div class="panel">
<h1>Click or press a key to start</h1>
<p>Fish swim away when your cursor comes near. The demo runs in autonomous mode until you start.</p>
<p style="font-size: 11px; opacity: 0.8; margin-top:6px;">Tip: move your mouse around to influence the fish.</p>
</div>
</div>
<script type="module">
// Basic autonomous aquarium with fish that flee from mouse proximity.
// 1) 3D scene with a glassy tank
// 2) Fish created as simple articulated bodies
// 3) On mouse move, convert to a 3D point on the tank plane (y = 0)
// 4) Fish steer away from that point; if no interaction yet, they wander
// 5) Start overlay starts the demo; after start, overlay fades away
import * as THREE from 'three';
// Scene setup
const stage = document.getElementById('stage');
const overlay = document.getElementById('startOverlay');
const glow = document.getElementById('glow');
// Resize-aware renderer
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'high-performance' });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight, false);
stage.appendChild(renderer.domElement);
const scene = new THREE.Scene();
// Slight fog to give depth
scene.fog = new THREE.FogExp2(0x0a2a52, 0.04);
// Interior tank dimensions (units arbitrary)
const W = 60;
const H = 40;
const D = 60;
// Camera inside the tank, looking toward -Z by default
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(0, 0, 0);
camera.lookAt(new THREE.Vector3(0, 0, -1));
// Lighting
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
const point = new THREE.PointLight(0xffffff, 0.8);
point.position.set(0, 20, 0);
scene.add(point);
// Water-like glow background (subtle)
const waterColor = 0x1a6fbd;
// Tank walls (glass-like, surrounding the interior)
const wallMat = new THREE.MeshStandardMaterial({
color: waterColor,
transparent: true,
opacity: 0.28,
roughness: 0.2
});
// Create six walls around the interior
const wallThickness = 0.8;
// Front wall (towards -Z)
const front = new THREE.Mesh(new THREE.BoxGeometry(W, H, wallThickness), wallMat);
front.position.z = -D/2;
scene.add(front);
// Back wall
const back = new THREE.Mesh(new THREE.BoxGeometry(W, H, wallThickness), wallMat);
back.position.z = D/2;
scene.add(back);
// Left wall
const left = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, H, D), wallMat);
left.position.x = -W/2;
scene.add(left);
// Right wall
const right = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, H, D), wallMat);
right.position.x = W/2;
scene.add(right);
// Top wall
const top = new THREE.Mesh(new THREE.BoxGeometry(W, wallThickness, D), wallMat);
top.position.y = H/2;
scene.add(top);
// Bottom wall
const bottom = new THREE.Mesh(new THREE.BoxGeometry(W, wallThickness, D), wallMat);
bottom.position.y = -H/2;
scene.add(bottom);
// Optional subtle grid on the ground floor for depth cue
const grid = new THREE.GridHelper(W, Math.floor(W / 2), 0x99a8b9, 0x0a2540);
grid.rotation.x = -Math.PI / 2;
grid.position.y = -H / 2 + 0.01;
scene.add(grid);
// Fish factory
function createFish(color = 0xffa500) {
const fish = new THREE.Group();
// Materials
const bodyMat = new THREE.MeshStandardMaterial({ color: color, roughness: 0.6, metalness: 0.1 });
// Body: elongated along X
const bodyGeom = new THREE.CylinderGeometry(0.7, 0.6, 3.6, 12, 1, false);
const body = new THREE.Mesh(bodyGeom, bodyMat);
body.rotation.z = Math.PI / 2; // align along X
body.position.x = 0;
fish.add(body);
// Eye
const eyeGeom = new THREE.SphereGeometry(0.15, 8, 8);
const eyeMat = new THREE.MeshStandardMaterial({ color: 0x111111 });
const eye = new THREE.Mesh(eyeGeom, eyeMat);
eye.position.x = 1.6;
eye.position.y = 0.15;
eye.position.z = 0.25;
fish.add(eye);
// Tail
const tailGeom = new THREE.ConeGeometry(0.9, 1.6, 10);
const tail = new THREE.Mesh(tailGeom, bodyMat);
tail.rotation.z = Math.PI / 2;
tail.rotation.y = Math.PI; // point backwards
tail.position.x = -2.3;
fish.add(tail);
// Dorsal fin
const finGeom = new THREE.ConeGeometry(0.3, 0.9, 8);
const fin = new THREE.Mesh(finGeom, bodyMat);
fin.rotation.x = Math.PI / 2;
fin.position.x = 0.7;
fin.position.y = 0.6;
fin.scale.x = 0.9;
fish.add(fin);
// Illuminate a little with ambient tint
const glowMat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.25 });
const glow = new THREE.Mesh(new THREE.SphereGeometry(0.5, 6, 6), glowMat);
glow.position.x = -0.5;
glow.position.y = 0.2;
glow.position.z = 0.15;
fish.add(glow);
// Initial velocity
const vel = new THREE.Vector3(
(Math.random() - 0.5) * 2,
(Math.random() - 0.5) * 0.6,
(Math.random() - 0.5) * 2
);
const maxSpeed = 6 + Math.random() * 4;
return { group: fish, velocity: vel, maxSpeed: maxSpeed, baseColor: color };
}
// Create multiple fish
const fishCount = 28;
const fishes = [];
const interiorMargin = 2.5;
for (let i = 0; i < fishCount; i++) {
const color = i % 5 === 0 ? 0xff9a00 : (i % 5 === 1 ? 0x4ecdc4 : (i % 5 === 2 ? 0xff5e8a : 0x9be7ff));
const f = createFish(color);
f.group.position.set(
THREE.MathUtils.randFloatSpread(W - interiorMargin * 2),
THREE.MathUtils.randFloatSpread(H - interiorMargin * 2),
THREE.MathUtils.randFloatSpread(D - interiorMargin * 2)
);
// Normalize initial direction to avoid stuck at origin
f.velocity.clampLength(0.5, f.maxSpeed);
fishes.push(f);
scene.add(f.group);
}
// Mouse interaction
const mouse = new THREE.Vector2();
let started = false; // when false, overlay remains
let influencePoint = new THREE.Vector3(0, 0, 0);
const planeY = 0; // y=0 plane for projection
const mousePlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), -planeY);
// Convert mouse to 3D point on plane Y=0
const raycaster = new THREE.Raycaster();
function updateMousePoint(clientX, clientY) {
const rect = renderer.domElement.getBoundingClientRect();
const x = ((clientX - rect.left) / rect.width) * 2 - 1;
const y = -((clientY - rect.top) / rect.height) * 2 + 1;
mouse.set(x, y);
raycaster.setFromCamera(mouse, camera);
// Intersect with Y=0 plane
const point = new THREE.Vector3();
const intersect = raycaster.ray.intersectPlane(mousePlane, point);
if (intersect) {
influencePoint.copy(point);
} else {
// Fall back to a drifting point near center
const t = performance.now() * 0.001;
influencePoint.set(Math.sin(t) * 8, 0, Math.cos(t) * 8);
}
}
// Start handler
function startDemo() {
if (started) return;
started = true;
overlay.classList.add('hidden');
overlay.setAttribute('aria-hidden', 'true');
// improve focus for keyboard if needed in the future
renderer.domElement.focus?.();
}
// Attach input handlers to start
overlay.addEventListener('click', startDemo);
window.addEventListener('keydown', (e) => {
// Start on any key if overlay is visible
if (!started) startDemo();
}, { once: true });
// Mouse move: update influence point
renderer.domElement.addEventListener('pointermove', (ev) => {
updateMousePoint(ev.clientX, ev.clientY);
}, { passive: true });
// Initial mouse point away from edges to keep demo interesting
updateMousePoint(window.innerWidth * 0.5, window.innerHeight * 0.5);
// Resize handling
function onResize() {
const w = window.innerWidth;
const h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h, false);
}
window.addEventListener('resize', onResize, { passive: true });
// Simple animation loop
const clock = new THREE.Clock();
// Gentle background drift for early demo visibility (no mouse interaction)
function wanderInfluencePoint(t) {
const radius = 8;
influencePoint.set(
Math.sin(t * 0.25) * radius * 0.8,
0,
Math.cos(t * 0.25) * radius * 0.8
);
}
// Main update
function update() {
const dt = Math.min(0.05, clock.getDelta()); // clamp dt for stability
const currentTime = performance.now() * 0.001;
// When not started, keep a gentle drift locus so the scene remains lively
if (!started) wanderInfluencePoint(currentTime);
// Update each fish
for (const f of fishes) {
// Influence from mouse (or drift if not started)
const toMouse = new THREE.Vector3().subVectors(f.group.position, influencePoint);
const dist = toMouse.length();
const influenceRadius = 8.5;
if (dist < influenceRadius) {
// Repel away from influencePoint
toMouse.normalize();
toMouse.multiplyScalar(2 * dt * (1.0 + (influenceRadius - dist) * 0.25));
f.velocity.add(toMouse);
} else if (!started) {
// Gentle wandering when not started
f.velocity.x += (Math.random() - 0.5) * 0.2 * dt;
f.velocity.y += (Math.random() - 0.5) * 0.1 * dt;
f.velocity.z += (Math.random() - 0.5) * 0.2 * dt;
}
// Boundary respect: bounce off walls
const limitX = W / 2 - 2;
const limitY = H / 2 - 2;
const limitZ = D / 2 - 2;
const pos = f.group.position;
if (pos.x > limitX) { pos.x = limitX; f.velocity.x *= -0.9; }
if (pos.x < -limitX) { pos.x = -limitX; f.velocity.x *= -0.9; }
if (pos.y > limitY) { pos.y = limitY; f.velocity.y *= -0.9; }
if (pos.y < -limitY) { pos.y = -limitY; f.velocity.y *= -0.9; }
if (pos.z > limitZ) { pos.z = limitZ; f.velocity.z *= -0.9; }
if (pos.z < -limitZ) { pos.z = -limitZ; f.velocity.z *= -0.9; }
// Speed cap
if (f.velocity.length() > f.maxSpeed) {
f.velocity.setLength(f.maxSpeed);
}
// Move
f.group.position.addScaledVector(f.velocity, dt);
// Orientation: face along velocity
const target = new THREE.Vector3().addVectors(f.group.position, f.velocity);
f.group.lookAt(target);
}
// Optional subtle glow rotation for depth
glow.style.transform = `translate(-50%, -50%) rotate(${Math.sin(currentTime) * 0.2}rad)`;
renderer.render(scene, camera);
requestAnimationFrame(update);
}
// Start the loop
update();
</script>
</body>
</html>Reasoning trace (7,936 tokens)
**Creating a 3D aquarium demo** I need to generate a single HTML file that showcases a 3D aquarium using Three.js. The file must be self-contained and load Three.js via an import map. I want it to have a self-playing demo mode that begins only after the user clicks or presses a key. Initially, I'll display a "Click or press a key to start" message and ensure the aquarium has 3D water, fish that swim, and interact with mouse movements. The fish should react to mouse proximity by moving away. I'll create a scene with simple geometric shapes for the fish and a background.**Designing fish movement mechanics** I’m aiming for simplicity in the design of the fish, which will be represented as small elongated spheres or cylinders with triangular tails. I’ll create multiple fish that randomly populate the tank. Each fish will swim forward at a certain speed. To make them reactive to the mouse, I'll compute the distance from each fish to the mouse's position by projecting 2D screen coordinates into a 3D space and intersecting it with the water surface. If any fish come within a specified threshold of the mouse, they'll be pushed away.**Setting up the aquarium environment** I need to create a perspective camera for the aquarium, which will have defined boundaries of width 60 units and depth 40 units. The tank dimensions will be W=60, H=30, and D=40. I’ll add the floor and walls for visibility while ensuring that all code remains within a single HTML file. I'll set up Three.js using an import map to include necessary components and some basic controls. For the fish, I'll create a custom mesh comprising a cylindrical body and a triangular tail, ensuring they bounce off the tank walls when they hit the boundaries.**Handling user interaction with the mouse** For user interaction, when the mouse moves, I’ll calculate the world point using `getPointFromMouse(mouseX, mouseY, planeY=0)`. I plan to implement a raycaster to find intersections with the horizontal plane at y=0, defined by a normal vector (0,1,0). I'll generate normalized device coordinates from the mouse position and set the raycaster accordingly. The intersection will allow me to compute the point where fish are positioned, which will be within a defined range in the y-axis.**Implementing fish behavior and user interaction** For each fish, I’ll compute the distance to the mouse point and, if it's within an influence radius, apply a steering vector to change its velocity. I need to make a "Click to start" overlay visible at the beginning, which will allow users to initiate the demonstration by clicking or pressing a key. The overlay must capture both pointer clicks and keydowns, which will require proper focus handling. The demo will need to run automatically in the background, keeping the fish swimming even before the user takes control. I'll simulate a moving point in the tank to ensure there's always some action, and once the user interacts, the control can shift to them. I’ll also ensure everything is set up correctly for Chrome and utilize ES modules to import the necessary components from Three.js.**Setting up the aquarium structure** I'm deciding to import Three.js using `import * as THREE from 'three';` for simplicity. I need to ensure that the 3D canvas fills the entire viewport, so I'll set the body margin to 0 and the canvas dimensions to 100% width and height. To create the aquarium, I'll use a transparent BoxGeometry for the tank and ensure it has glass-like walls. I also want to show the aquarium scene behind a semi-transparent overlay that includes a "Click to start" message, ensuring that the first paint displays something meaningful before user interaction.**Creating the aquarium and fish logic** I want to simulate water in the aquarium using a BoxGeometry with transparent blue material. The tank will also be a larger transparent box to contain it. I’ll ensure the fish appear inside this setup. For the fish, I'll create a class that defines their body, head, and tail using geometries like CylinderGeometry and ConeGeometry. We'll add logic for their movement, including avoiding walls and responding to mouse position. Initially, I plan to create around 20 fish placed randomly inside the tank. I'll implement an overlay for user interaction, which fades out when the simulation starts, allowing for smooth gameplay.**Configuring the aquarium setup** I'm thinking about how to position the camera and tank walls in my 3D aquarium. I’ll place the camera at (0, 0, 0), looking towards negative Z. To create the environment, I need to position the tank walls effectively, ensuring the interior dimensions are 60 units wide, 40 units high, and 60 units deep. The front wall will be at z = -30, so it's directly in front of the camera. I'll use BoxGeometry for the walls, making them semi-transparent to give the illusion of glass. The material will have an appropriate color and opacity to achieve this effect while still being able to see the fish inside.**Creating aquarium walls and fish models** I'm working on defining the aquarium's geometry. For the front and back walls, I’ll use BoxGeometry to create walls at the correct positions. The interior dimensions will be set, and while I don't need to render the water itself, the fish will be inside the transparent walls. I'll rotate the fish shapes into place, ensuring the body is aligned correctly. The fish will also interact with the mouse for movement. I need to use an import map in my HTML to properly reference the Three.js library, making sure everything is set up accurately.**Finalizing HTML and styles** I need to make sure no external images or fonts are used; I’ll stick to the default fonts. Adding CSS styles will help prevent annoying scrollbars. The screenshot is managed by the test harness, so ensuring the initial overlay isn't too heavy is key. I must be careful with cross-compatibility and make sure all the necessary variables are properly defined. Alright, time to put together the final HTML code block! This should be fun!