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
- 20.9 s
- Cost
- $0.0013
- Output tokens
- 3,188
- Spent thinking
- 320 tokens
- Speed
- 170.3 tok/s
- Console errors
- 0
- First token after
- 2.2 s
- First visible text after
- 5.7 s
- Prompt tokens
- 423
- Code size
- 9.8 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-5-nano- Reasoning sent
{"effort":"low"}- 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 · 307 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>3D Aquarium - Fish swim away from mouse</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
--bg: #0a1020;
--water: #1b6ea8;
--overlay: rgba(0,0,0,0.6);
--text: #e9f0ff;
}
html, body {
margin: 0;
height: 100%;
background: var(--bg);
overflow: hidden;
font-family: system-ui, -apple-system, "Segoe UI", Roboto;
}
#stage {
width: 100%;
height: 100%;
display: block;
}
/* Overlay hint shown before start */
#startup {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--overlay);
color: white;
font-size: 1.2rem;
text-align: center;
padding: 20px;
z-index: 10;
user-select: none;
}
#startup h1 { margin: 0 0 8px 0; font-size: 1.6rem; }
@media (max-width: 700px) {
#startup { font-size: 1rem; padding: 16px; }
#startup h1 { font-size: 1.25rem; }
}
</style>
<!-- Import map for Three.js as requested -->
<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>
<canvas id="stage"></canvas>
<div id="startup" aria-label="Start instructions">
<div>
<h1>3D Aquarium</h1>
<div>Move your mouse near the fish to scare them away. Click or press any key to start.</div>
</div>
</div>
<script type="module">
// Scene setup
import * as THREE from 'three';
// Basic helpers from addons to keep file self-contained
// We'll use OrbitControls but we won't rely on user interaction; instead, we implement our own mouse-to-world mapping.
// (Importing OrbitControls isn't strictly necessary; omit to keep self-contained.)
// If you want to test with controls, you can uncomment the next line.
// import { OrbitControls } from 'three/addons/orbitcontrols.js';
// Core globals
let scene, camera, renderer;
let fishCount = 60;
let fish = [];
let waterPlane;
let mouse = new THREE.Vector2(); // in NDC space
let pointerWorld = new THREE.Vector3(); // projected point on water plane
let raycaster = new THREE.Raycaster();
let isRunning = false;
let lastTime = 0;
const container = document.getElementById('stage');
const startup = document.getElementById('startup');
function init() {
// Renderer
renderer = new THREE.WebGLRenderer({ canvas: container, antialias: true, alpha: false });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
// Scene
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000a1a, 0.015);
// Camera
const aspect = window.innerWidth / window.innerHeight;
camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 200);
camera.position.set(0, 8, 22);
camera.lookAt(0, 0, 0);
// Lights
const ambient = new THREE.AmbientLight(0x88aaff, 0.6);
scene.add(ambient);
const dir = new THREE.DirectionalLight(0xffffff, 0.8);
dir.position.set(5, 15, 10);
scene.add(dir);
// Water / floor plane
const waterGeometry = new THREE.PlaneGeometry(60, 40);
const waterMaterial = new THREE.MeshPhongMaterial({ color: 0x1b6ea8, side: THREE.DoubleSide, shininess: 100, transparent: true, opacity: 0.9 });
waterPlane = new THREE.Mesh(waterGeometry, waterMaterial);
waterPlane.rotation.x = -Math.PI / 2;
waterPlane.position.y = -0.5;
scene.add(waterPlane);
// Simple seabed/floor grid for depth cue
const grid = new THREE.GridHelper(60, 20, 0x114466, 0x0a1a2a);
grid.rotation.x = -Math.PI / 2;
grid.position.y = -0.51;
scene.add(grid);
// Create fish
const fishGroup = new THREE.Group();
for (let i = 0; i < fishCount; i++) {
const f = createFish();
fishGroup.add(f.mesh);
fish.push(f);
}
scene.add(fishGroup);
// Handle resizing
window.addEventListener('resize', onWindowResize, { passive: true });
// Mouse events for start/interaction
window.addEventListener('pointermove', onPointerMove, { passive: true });
window.addEventListener('keydown', onAnyKey, { passive: true });
// Start hidden until user interacts
// Start loop
requestAnimationFrame(animate);
}
function createFish() {
// Simple elongated cone/yellow-orange fish
const body = new THREE.ConeGeometry(0.6, 2, 8);
// The cone points up by default; rotate to horizontal
const material = new THREE.MeshPhongMaterial({ color: 0xffa500, flatShading: true });
const mesh = new THREE.Mesh(body, material);
mesh.geometry.computeBoundingBox();
mesh.castShadow = true;
mesh.receiveShadow = true;
// Slightly rotate to point forward
mesh.rotation.z = Math.PI; // flip to face -Z
// Random initial position within bounds
const x = THREE.MathUtils.randFloatSpread(40);
const y = THREE.MathUtils.randFloat(-2, 6);
const z = THREE.MathUtils.randFloatSpread(40);
mesh.position.set(x, y, z);
// Velocity & personal behavior
const f = {
mesh: mesh,
vel: new THREE.Vector3(
THREE.MathUtils.randFloat(-0.07, 0.07),
THREE.MathUtils.randFloat(-0.02, 0.02),
THREE.MathUtils.randFloat(-0.07, 0.07)
),
accelBounds: new THREE.Vector3(0.15, 0.15, 0.15),
maxSpeed: 0.25,
wanderAngle: THREE.MathUtils.randFloat(0, Math.PI * 2)
};
return f;
}
function onWindowResize() {
const w = window.innerWidth;
const h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
function onPointerMove(event) {
// convert to normalized device coordinates (-1, 1)
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
// For ongoing update of world point we keep it in animate loop
}
function onAnyKey() {
if (!isRunning) {
isRunning = true;
startup.style.display = 'none';
}
}
function updateMouseWorldPoint() {
// Cast ray from camera through mouse into scene, intersect with water plane (y = -0.5)
raycaster.setFromCamera(new THREE.Vector2(mouse.x, mouse.y), camera);
const planeY = -0.5;
// Plane equation: y = planeY
// Compute intersection with the horizontal plane
const dir = raycaster.ray.direction;
const camPos = raycaster.ray.origin;
const t = (planeY - camPos.y) / dir.y;
if (t > 0) {
pointerWorld.copy(camPos).addScaledVector(dir, t);
} else {
// fallback to a point in front
pointerWorld.set(0, planeY, 0);
}
}
function animate(now) {
requestAnimationFrame(animate);
if (!lastTime) lastTime = now;
const dt = Math.min(0.05, (now - lastTime) / 1000);
lastTime = now;
// Update pointer projectively
updateMouseWorldPoint();
// If not started, a subtle idle drift could be shown, but keep minimal
if (isRunning) {
moveFish(dt);
}
// Simple camera motion for life-like feeling (gentle)
camera.position.x += Math.sin(now * 0.0002) * 0.001;
camera.position.z += Math.cos(now * 0.0002) * 0.001;
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
}
function moveFish(dt) {
const influenceRadius = 6.0;
const repelStrength = 1.2;
const avoidanceRadius = 1.8;
for (let i = 0; i < fish.length; i++) {
const f = fish[i];
// Gentle wandering
const wand = Math.sin((i + performance.now() * 0.001) * 1.2) * 0.08;
f.vel.x += wand * dt;
f.vel.z += wand * dt;
// React to mouse pointer projection (pointerWorld is on water plane)
const toPointer = new THREE.Vector3().subVectors(f.mesh.position, pointerWorld);
const dist = toPointer.length();
if (dist < influenceRadius) {
// Move away from pointer (repulsion)
toPointer.normalize();
const force = repelStrength * (1 - dist / influenceRadius);
f.vel.x += toPointer.x * force * dt * 2;
f.vel.z += toPointer.z * force * dt * 2;
}
// Keep inside bounds (rough aquarium box)
const maxX = 25, maxZ = 25, minX = -25, minZ = -25;
if (f.mesh.position.x < minX) f.vel.x = Math.abs(f.vel.x);
if (f.mesh.position.x > maxX) f.vel.x = -Math.abs(f.vel.x);
if (f.mesh.position.z < minZ) f.vel.z = Math.abs(f.vel.z);
if (f.mesh.position.z > maxZ) f.vel.z = -Math.abs(f.vel.z);
// Cap speed
if (f.vel.length() > f.maxSpeed) {
f.vel.normalize().multiplyScalar(f.maxSpeed);
}
// Update position
f.mesh.position.addScaledVector(f.vel, dt * 60); // scale to feel fast enough
// Slight bobbing up/down for realism
f.mesh.position.y += Math.sin(nowHint() + i * 0.5) * 0.002;
// Optional slight tilt to show motion
f.mesh.rotation.y = Math.atan2(-f.vel.x, -f.vel.z);
// Quick reorient if stuck near floor
if (f.mesh.position.y < -0.2) f.mesh.position.y = -0.2;
if (f.mesh.position.y > 2.5) f.mesh.position.y = 2.5;
}
}
// simple time-based function for bobbing
const t0 = performance.now();
function nowHint() {
return (performance.now() - t0) * 0.001;
}
// Initialize
init();
// Accessibility: ensure startup overlay shown until user interacts
// The overlay already exists and will be hidden on first keydown or click
// Add click anywhere on the stage to start if user clicks directly on canvas
container.addEventListener('click', () => { if (!isRunning) { isRunning = true; startup.style.display = 'none'; } }, { passive: true });
</script>
</body>
</html>Reasoning trace (320 tokens)
**Creating a 3D aquarium** I'm thinking about how to create a single HTML file that implements a 3D aquarium using Three.js. The goal is for fish to swim away from the mouse when it gets close. I'll start by loading Three.js via importmap and set up a self-playing demo controlled by AI for the fish's movement. I need to begin with a message like "Click or press a key to start." The canvas should adapt to both desktop and mobile dimensions while creating the fish with simple shapes. Mouse tracking will be vital too!**Mapping mouse interaction in the aquarium** I'm figuring out how to set the aquarium size and map the 2D mouse position to a 3D point within the scene using a raycaster. The idea is to create an invisible plane at the water surface (y=0) to calculate where the mouse intersects. If fish get too close to this point, they should swim away from it. Plus, each fish should swim randomly as well. I plan to initialize with 60 fish, pausing until a click starts the animation without prompts or alerts. I'll need to craft the HTML with inline JS for a responsive canvas!