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
- 1 min 35 s
- Cost
- $0.0055
- Output tokens
- 10,863
- Spent thinking
- 3,366 tokens
- Speed
- 120.3 tok/s
- Console errors
- 0
- First token after
- 4.5 s
- First visible text after
- 40.7 s
- Prompt tokens
- 423
- Code size
- 22.6 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna- Reasoning sent
{"effort":"medium"}- 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 · 554 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Blue Hour Aquarium</title>
<style>
:root {
color-scheme: dark;
--ink: #e9f8ff;
--muted: #a2c5d8;
--aqua: #82e6e8;
}
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
body {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--ink);
background:
radial-gradient(ellipse at 50% 44%, rgba(20, 107, 139, .55) 0%, rgba(7, 40, 67, .8) 42%, transparent 74%),
linear-gradient(145deg, #061523 0%, #09283d 52%, #061724 100%);
}
#scene { position: fixed; inset: 0; }
canvas { display: block; width: 100%; height: 100%; }
.vignette {
position: fixed; inset: 0; pointer-events: none;
background:
linear-gradient(180deg, rgba(3, 14, 24, .48), transparent 26%, transparent 70%, rgba(3, 14, 24, .46)),
radial-gradient(ellipse at center, transparent 48%, rgba(2, 13, 23, .36) 100%);
}
.topbar {
position: fixed; top: 30px; left: 38px; right: 38px;
display: flex; align-items: flex-start; justify-content: space-between;
pointer-events: none;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-mark {
width: 34px; height: 34px; border: 1px solid rgba(155, 237, 240, .44);
border-radius: 50%; display: grid; place-items: center; color: var(--aqua);
box-shadow: 0 0 24px rgba(88, 218, 229, .14), inset 0 0 14px rgba(88, 218, 229, .12);
}
.brand-mark svg { width: 18px; height: 18px; }
.eyebrow {
text-transform: uppercase; letter-spacing: .22em; font-size: 10px;
font-weight: 700; color: #a0dbe2;
}
h1 {
font-size: 18px; line-height: 1; letter-spacing: .02em;
font-weight: 560; margin: 5px 0 0; color: #f2fbff;
}
.live {
display: flex; align-items: center; gap: 9px; margin-top: 7px;
font-size: 10px; text-transform: uppercase; letter-spacing: .18em;
color: #b4d5df;
}
.live-dot {
width: 6px; height: 6px; border-radius: 50%; background: #8df3dc;
box-shadow: 0 0 12px #81ffdc;
}
.scene-note {
margin-top: 2px; padding: 11px 14px; border-radius: 8px;
border: 1px solid rgba(161, 221, 231, .14);
background: rgba(6, 29, 45, .42); backdrop-filter: blur(12px);
display: flex; align-items: center; gap: 16px;
color: #b1d0db; font-size: 11px; letter-spacing: .04em;
}
.note-count { color: #f1fbff; font-size: 14px; font-weight: 600; }
.note-divider { height: 20px; width: 1px; background: rgba(178, 222, 231, .2); }
.instruction {
position: fixed; bottom: 32px; left: 50%; transform: translateX(-50%);
display: flex; align-items: center; gap: 11px; white-space: nowrap;
padding: 11px 17px; border-radius: 30px;
border: 1px solid rgba(157, 221, 230, .2);
background: rgba(4, 25, 40, .54); backdrop-filter: blur(12px);
color: #c9e1e9; font-size: 11px; letter-spacing: .04em;
box-shadow: 0 8px 30px rgba(0,0,0,.12); pointer-events: none;
}
.cursor-icon {
width: 15px; height: 19px; border: 1.4px solid #9ce8e6; border-radius: 8px;
position: relative; opacity: .95;
}
.cursor-icon::after {
content: ""; position: absolute; top: 3px; left: 6px; width: 1px; height: 5px;
background: #9ce8e6;
}
.instruction strong { color: #e9ffff; font-weight: 550; }
.corner-caption {
position: fixed; bottom: 37px; right: 40px; text-align: right;
text-transform: uppercase; letter-spacing: .17em; font-size: 9px;
line-height: 1.8; color: rgba(176, 211, 220, .65); pointer-events: none;
}
@media (max-width: 600px) {
.topbar { top: 20px; left: 18px; right: 18px; }
.brand-mark { width: 30px; height: 30px; }
h1 { font-size: 16px; }
.scene-note { padding: 9px 10px; gap: 9px; font-size: 9px; }
.note-count { font-size: 12px; }
.instruction { bottom: 22px; font-size: 10px; padding: 10px 14px; }
.corner-caption { display: none; }
}
</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="scene"></div>
<div class="vignette"></div>
<header class="topbar">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none">
<path d="M3 12s3.3-5 9-5c4.4 0 7.1 3.1 9 5-1.9 1.9-4.6 5-9 5-5.7 0-9-5-9-5Z" stroke="currentColor" stroke-width="1.35"/>
<path d="m4 12-3-3v6l3-3Z" fill="currentColor"/>
<circle cx="15.5" cy="11" r="1" fill="currentColor"/>
</svg>
</div>
<div>
<div class="eyebrow">The quiet collection</div>
<h1>Blue Hour Aquarium</h1>
</div>
</div>
<div class="scene-note">
<span><span class="note-count">09</span> <span style="opacity:.75">residents</span></span>
<span class="note-divider"></span>
<span><span class="live-dot" style="display:inline-block; vertical-align:middle; margin-right:6px"></span>Water is calm</span>
</div>
</header>
<div class="instruction">
<span class="cursor-icon"></span>
<span><strong>Move your cursor</strong> near a fish — watch it dart away</span>
</div>
<div class="corner-caption">A little world beneath the surface<br>Drag your gaze through the water</div>
<script type="module">
import * as THREE from "three";
const host = document.getElementById("scene");
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.18;
host.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x0b3550, 0.018);
const camera = new THREE.PerspectiveCamera(38, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0.2, 13.1);
camera.lookAt(0, 0, 0);
const aquarium = new THREE.Group();
scene.add(aquarium);
scene.add(new THREE.HemisphereLight(0x9eeaff, 0x173a48, 2.05));
const key = new THREE.DirectionalLight(0xd8f8ff, 2.4);
key.position.set(-5, 8, 8);
scene.add(key);
const blueLight = new THREE.PointLight(0x25bfe0, 70, 18, 2);
blueLight.position.set(3, 2, -1);
aquarium.add(blueLight);
const warmLight = new THREE.PointLight(0xffa86f, 30, 11, 2);
warmLight.position.set(-4, -1.8, 2);
aquarium.add(warmLight);
const tankW = 11.8, tankH = 6.5, tankD = 4.8;
const back = new THREE.Mesh(
new THREE.PlaneGeometry(tankW, tankH),
new THREE.MeshBasicMaterial({ color: 0x0b5271, transparent: true, opacity: 0.23, depthWrite: false })
);
back.position.z = -tankD / 2;
aquarium.add(back);
const sideMat = new THREE.MeshBasicMaterial({ color: 0x2b9bb0, transparent: true, opacity: 0.055, side: THREE.DoubleSide, depthWrite: false });
const leftPane = new THREE.Mesh(new THREE.PlaneGeometry(tankD, tankH), sideMat);
leftPane.rotation.y = Math.PI / 2;
leftPane.position.x = -tankW / 2;
aquarium.add(leftPane);
const rightPane = leftPane.clone();
rightPane.rotation.y = -Math.PI / 2;
rightPane.position.x = tankW / 2;
aquarium.add(rightPane);
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(tankW - .16, tankD - .15),
new THREE.MeshStandardMaterial({ color: 0xb19a70, roughness: 0.95, metalness: 0 })
);
floor.rotation.x = -Math.PI / 2;
floor.position.set(0, -3.12, 0);
aquarium.add(floor);
const floorGlow = new THREE.Mesh(
new THREE.PlaneGeometry(tankW - .4, tankD - .35),
new THREE.MeshBasicMaterial({ color: 0xd2be91, transparent: true, opacity: .1, depthWrite: false })
);
floorGlow.rotation.x = -Math.PI / 2;
floorGlow.position.set(0, -3.105, 0);
aquarium.add(floorGlow);
const glassEdges = new THREE.LineSegments(
new THREE.EdgesGeometry(new THREE.BoxGeometry(tankW, tankH, tankD)),
new THREE.LineBasicMaterial({ color: 0x76dbe8, transparent: true, opacity: .58 })
);
aquarium.add(glassEdges);
const rimMat = new THREE.MeshStandardMaterial({ color: 0x17485b, roughness: .24, metalness: .48, emissive: 0x09222b, emissiveIntensity: .5 });
const topRim = new THREE.Mesh(new THREE.BoxGeometry(tankW + .16, .12, tankD + .12), rimMat);
topRim.position.y = tankH / 2;
aquarium.add(topRim);
const bottomRim = new THREE.Mesh(new THREE.BoxGeometry(tankW + .16, .12, tankD + .12), rimMat);
bottomRim.position.y = -tankH / 2;
aquarium.add(bottomRim);
const rng = (a, b) => a + Math.random() * (b - a);
// Pebbles and little flecks of shell across the sandy floor.
const pebbleMat = [
new THREE.MeshStandardMaterial({ color: 0xd2c49d, roughness: .88 }),
new THREE.MeshStandardMaterial({ color: 0x907d68, roughness: .9 }),
new THREE.MeshStandardMaterial({ color: 0xe6d8b0, roughness: .8 })
];
for (let i = 0; i < 58; i++) {
const pebble = new THREE.Mesh(new THREE.SphereGeometry(1, 9, 7), pebbleMat[i % pebbleMat.length]);
pebble.position.set(rng(-5.65, 5.65), -3.02 + rng(-.005, .025), rng(-2.15, 1.8));
pebble.scale.set(rng(.018, .08), rng(.012, .035), rng(.02, .07));
aquarium.add(pebble);
}
function makeRock(x, z, scale, color) {
const rock = new THREE.Mesh(
new THREE.DodecahedronGeometry(1, 1),
new THREE.MeshStandardMaterial({ color, roughness: .88, flatShading: true })
);
rock.position.set(x, -2.87 + scale * .27, z);
rock.scale.set(scale * 1.35, scale * .7, scale);
rock.rotation.set(rng(-.3, .3), rng(0, 4), rng(-.2, .2));
aquarium.add(rock);
return rock;
}
makeRock(-4.6, -.2, .72, 0x526e6c);
makeRock(-4.05, -.1, .47, 0x778176);
makeRock(4.55, -.9, .78, 0x506b69);
makeRock(3.95, -.7, .46, 0x7d826f);
makeRock(.2, -1.8, .38, 0x68766e);
const seaweedMats = [
new THREE.MeshStandardMaterial({ color: 0x147c70, roughness: .55, emissive: 0x06231e, emissiveIntensity: .25 }),
new THREE.MeshStandardMaterial({ color: 0x32a284, roughness: .5, emissive: 0x082d24, emissiveIntensity: .2 }),
new THREE.MeshStandardMaterial({ color: 0x426f5d, roughness: .65 })
];
function addPlant(x, z, height, bend, mat, thickness = .045) {
const points = [
new THREE.Vector3(x, -3.02, z),
new THREE.Vector3(x + bend * .35, -2.2, z),
new THREE.Vector3(x - bend * .65, -1.25, z + .04),
new THREE.Vector3(x + bend, -3.02 + height, z + .08)
];
const curve = new THREE.CatmullRomCurve3(points);
const mesh = new THREE.Mesh(new THREE.TubeGeometry(curve, 24, thickness, 7, false), mat);
aquarium.add(mesh);
// Slim curved leaves fork away from the main stem.
for (let j = 1; j <= 2; j++) {
const t = .42 + j * .17;
const p = curve.getPoint(t);
const side = (j % 2 ? 1 : -1) * rng(.22, .48);
const leafCurve = new THREE.QuadraticBezierCurve3(
p,
new THREE.Vector3(p.x + side * .45, p.y + .16, p.z + .03),
new THREE.Vector3(p.x + side, p.y + .38, p.z + .05)
);
aquarium.add(new THREE.Mesh(new THREE.TubeGeometry(leafCurve, 10, thickness * .55, 6, false), mat));
}
}
for (let i = 0; i < 9; i++) {
addPlant(rng(-5.35, -3.65), rng(-1.8, -.5), rng(1.3, 2.9), rng(-.4, .4), seaweedMats[i % 3], rng(.025, .06));
}
for (let i = 0; i < 8; i++) {
addPlant(rng(3.7, 5.45), rng(-2, -.4), rng(1.35, 3), rng(-.4, .4), seaweedMats[(i + 1) % 3], rng(.03, .065));
}
for (let i = 0; i < 4; i++) {
addPlant(rng(-1.8, 1.8), rng(-2.1, -1.4), rng(.55, 1.15), rng(-.25, .25), seaweedMats[i % 2], .025);
}
function addCoral(x, z, color, height = 1) {
const material = new THREE.MeshStandardMaterial({ color, roughness: .55, emissive: color, emissiveIntensity: .12 });
const branches = [
[0, 0, 0, 0, .55, 0],
[0, .28, 0, -.3, .7, .02],
[0, .38, 0, .34, .78, -.02],
[-.12, .5, .01, -.2, .94, .03],
[.17, .58, -.01, .38, 1.02, .02],
[-.18, .64, 0, -.4, .83, 0]
];
for (const b of branches) {
const a = new THREE.Vector3(x + b[0] * height, -3.02 + b[1] * height, z + b[2]);
const c = new THREE.Vector3(x + b[3] * height, -3.02 + b[4] * height, z + b[5]);
const dir = new THREE.Vector3().subVectors(c, a);
const stem = new THREE.Mesh(new THREE.CylinderGeometry(.075 * height, .11 * height, dir.length(), 7), material);
stem.position.copy(a).add(c).multiplyScalar(.5);
stem.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.normalize());
aquarium.add(stem);
const tip = new THREE.Mesh(new THREE.SphereGeometry(.085 * height, 8, 7), material);
tip.position.copy(c);
aquarium.add(tip);
}
}
addCoral(-4.35, 1.0, 0xe18474, .85);
addCoral(4.3, .8, 0xd479a8, .78);
addCoral(2.7, -1.5, 0xe3a267, .55);
const fishPalette = [
{ body: 0xff8b4a, fin: 0xffc46b, stripe: 0xffe8bb },
{ body: 0x40c6d0, fin: 0x8af1e8, stripe: 0xd3ffff },
{ body: 0xf2c342, fin: 0xffe88a, stripe: 0xfff5d4 },
{ body: 0xef7090, fin: 0xffa9bd, stripe: 0xffd5dc },
{ body: 0x6999f2, fin: 0xacc7ff, stripe: 0xe2edff },
{ body: 0xff985f, fin: 0xffd092, stripe: 0xffebc8 }
];
const sphere = new THREE.SphereGeometry(1, 20, 14);
const eyeWhite = new THREE.MeshStandardMaterial({ color: 0xf7ffff, roughness: .2 });
const pupilMat = new THREE.MeshStandardMaterial({ color: 0x102538, roughness: .2 });
const highlightMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
const fishes = [];
function finShape(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);
}
function createFish(index, x, y, z, size, palette) {
const group = new THREE.Group();
const bodyMat = new THREE.MeshStandardMaterial({
color: palette.body, roughness: .35, metalness: .08,
emissive: palette.body, emissiveIntensity: .12
});
const finMat = new THREE.MeshStandardMaterial({
color: palette.fin, roughness: .4, side: THREE.DoubleSide,
emissive: palette.fin, emissiveIntensity: .1
});
const body = new THREE.Mesh(sphere, bodyMat);
body.scale.set(.72, .42, .3);
group.add(body);
const tailPivot = new THREE.Group();
tailPivot.position.x = -.58;
group.add(tailPivot);
const tail = new THREE.Mesh(
finShape([[0, 0], [-.48, .34], [-.39, 0], [-.48, -.34]]),
finMat
);
tail.position.z = 0;
tailPivot.add(tail);
const dorsal = new THREE.Mesh(finShape([[-.18, .26], [.04, .62], [.3, .31]]), finMat);
dorsal.position.z = -.01;
group.add(dorsal);
const lowerFin = new THREE.Mesh(finShape([[-.1, -.29], [.14, -.53], [.3, -.3]]), finMat);
lowerFin.position.z = .02;
group.add(lowerFin);
// Soft pale flank mark for a little extra variety and definition.
const markMat = new THREE.MeshStandardMaterial({ color: palette.stripe, roughness: .5, transparent: true, opacity: .74 });
const flank = new THREE.Mesh(sphere, markMat);
flank.scale.set(.34, .25, .014);
flank.position.set(-.08, .02, .294);
group.add(flank);
for (const side of [-1, 1]) {
const eye = new THREE.Mesh(sphere, eyeWhite);
eye.scale.set(.088, .088, .05);
eye.position.set(.43, .12, side * .265);
group.add(eye);
const pupil = new THREE.Mesh(sphere, pupilMat);
pupil.scale.set(.047, .052, .03);
pupil.position.set(.455, .125, side * .307);
group.add(pupil);
const shine = new THREE.Mesh(sphere, highlightMat);
shine.scale.set(.014, .014, .012);
shine.position.set(.47, .15, side * .328);
group.add(shine);
}
const pectoral = new THREE.Mesh(finShape([[-.04, 0], [.27, -.18], [.12, -.31]]), finMat);
pectoral.position.set(.03, -.08, .27);
group.add(pectoral);
group.position.set(x, y, z);
aquarium.add(group);
const direction = Math.random() > .5 ? 1 : -1;
const fish = {
group, tailPivot, pectoral, body, size, index,
velocity: new THREE.Vector3(direction * rng(.45, .85), rng(-.08, .08), rng(-.02, .02)),
direction, phase: Math.random() * Math.PI * 2,
speed: rng(.53, .96),
x, y, z
};
group.scale.set(size, size, size);
fishes.push(fish);
}
const placements = [
[-3.6, 1.65, .25, .83], [-1.5, .75, -.8, .72], [1.3, 1.65, -.35, .82],
[3.35, .45, .55, .72], [-4.1, -.55, 1.05, .67], [-2.0, -.9, -.35, .62],
[.25, -.45, .82, .72], [2.2, -1.0, -.9, .65], [4.0, 1.5, -.9, .58]
];
placements.forEach((p, i) => createFish(i, p[0], p[1], p[2], p[3], fishPalette[i % fishPalette.length]));
// Floating air bubbles with glints.
const bubbles = [];
const bubbleMat = new THREE.MeshPhysicalMaterial({
color: 0xa5eff0, transparent: true, opacity: .34, roughness: .12,
metalness: .05, transmission: .25, thickness: .2
});
for (let i = 0; i < 26; i++) {
const r = rng(.025, .075);
const bubble = new THREE.Mesh(new THREE.SphereGeometry(r, 12, 10), bubbleMat);
bubble.position.set(rng(-5.4, 5.4), rng(-2.8, 2.8), rng(-1.8, 1.7));
aquarium.add(bubble);
const glint = new THREE.Mesh(new THREE.SphereGeometry(r * .18, 7, 6), highlightMat);
glint.position.set(r * -.28, r * .34, r * .84);
bubble.add(glint);
bubbles.push({ mesh: bubble, radius: r, speed: rng(.16, .43), phase: rng(0, 10) });
}
// A few drifting specks catch the light in the water.
const dust = new THREE.Group();
aquarium.add(dust);
for (let i = 0; i < 110; i++) {
const dot = new THREE.Mesh(
new THREE.SphereGeometry(rng(.008, .018), 5, 5),
new THREE.MeshBasicMaterial({ color: 0xb3e9e9, transparent: true, opacity: rng(.08, .3) })
);
dot.position.set(rng(-5.4, 5.4), rng(-2.8, 2.8), rng(-1.8, 1.8));
dot.userData.phase = rng(0, 8);
dust.add(dot);
}
const raycaster = new THREE.Raycaster();
const pointerNdc = new THREE.Vector2();
const pointerWorld = new THREE.Vector3();
const interactionPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointerActive = false;
let lastPointerMove = 0;
let xScale = 1;
function setPointer(event) {
pointerNdc.x = (event.clientX / window.innerWidth) * 2 - 1;
pointerNdc.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointerNdc, camera);
raycaster.ray.intersectPlane(interactionPlane, pointerWorld);
if (pointerWorld) {
pointerWorld.x /= xScale;
pointerActive = true;
lastPointerMove = performance.now();
}
}
window.addEventListener("pointermove", setPointer, { passive: true });
window.addEventListener("touchmove", (e) => {
if (e.touches && e.touches[0]) setPointer(e.touches[0]);
}, { passive: true });
function resize() {
const width = window.innerWidth, height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
// A gently narrower tank on portrait screens keeps its whole silhouette in view.
xScale = Math.min(1, Math.max(.34, (width / height) / 1.28));
aquarium.scale.x = xScale;
}
window.addEventListener("resize", resize);
resize();
const clock = new THREE.Clock();
const smoothPointer = new THREE.Vector3(100, 100, 0);
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), .035);
const time = clock.elapsedTime;
if (pointerActive && performance.now() - lastPointerMove < 1600) {
smoothPointer.lerp(pointerWorld, Math.min(1, dt * 5));
} else {
smoothPointer.lerp(new THREE.Vector3(100, 100, 0), Math.min(1, dt * 1.5));
}
for (const fish of fishes) {
const p = fish.group.position;
fish.phase += dt * (2.5 + fish.speed);
let vx = fish.direction * fish.speed;
let vy = Math.sin(time * .75 + fish.phase) * .12;
const dx = p.x - smoothPointer.x;
const dy = p.y - smoothPointer.y;
const distance = Math.hypot(dx, dy);
if (distance < 2.35) {
const force = (1 - distance / 2.35) * 3.7;
const safe = Math.max(distance, .12);
vx += dx / safe * force;
vy += dy / safe * force;
}
if (p.x > 5.05) vx = -Math.abs(vx) - .35;
if (p.x < -5.05) vx = Math.abs(vx) + .35;
if (p.y > 2.45) vy -= .75;
if (p.y < -2.35) vy += .75;
const desired = new THREE.Vector3(vx, vy, Math.sin(time * .55 + fish.phase) * .045);
fish.velocity.lerp(desired, Math.min(1, dt * 2.5));
p.addScaledVector(fish.velocity, dt);
p.x = THREE.MathUtils.clamp(p.x, -5.2, 5.2);
p.y = THREE.MathUtils.clamp(p.y, -2.5, 2.55);
p.z = THREE.MathUtils.clamp(p.z, -1.8, 1.8);
const heading = fish.velocity.x;
fish.group.scale.x = fish.size * (heading < -.04 ? -1 : 1);
fish.group.scale.y = fish.size;
fish.group.scale.z = fish.size;
fish.group.rotation.y = Math.sin(fish.phase * .5) * .035;
fish.group.rotation.z = THREE.MathUtils.clamp(fish.velocity.y * .16, -.18, .18);
fish.tailPivot.rotation.y = Math.sin(fish.phase * 3.2) * .42;
fish.tailPivot.rotation.z = Math.sin(fish.phase * 3.2) * .08;
fish.pectoral.rotation.x = Math.sin(fish.phase * 2.2) * .22;
}
for (const b of bubbles) {
b.mesh.position.y += b.speed * dt;
b.mesh.position.x += Math.sin(time * .9 + b.phase) * dt * .055;
if (b.mesh.position.y > 3.05) {
b.mesh.position.y = -2.95;
b.mesh.position.x = rng(-5.3, 5.3);
}
}
for (const dot of dust.children) {
dot.position.y += Math.sin(time * .55 + dot.userData.phase) * dt * .025;
dot.position.x += Math.cos(time * .4 + dot.userData.phase) * dt * .018;
}
aquarium.rotation.y = Math.sin(time * .16) * .008;
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>Reasoning trace (3,366 tokens)
**Creating a 3D aquarium code** I'm working on creating code that requires using the exact import map. The goal is to develop a polished 3D aquarium with procedural fish animations and a mouse repel feature. The canvas should cover the full screen, and I might need a module fallback as well. The design includes a transparent glass tank with a blue gradient background, sand flooring, and underwater elements like plants and rocks. I aim for a strong quality rendering with Three.js, adjusting the camera and tank specifications for a polished look.**Designing fish geometry for the aquarium** I'm focusing on the tank's rear panel, which should have a blue-tinted mesh with low opacity. For the aquarium edges, I’ll utilize EdgesGeometry and LineSegments with a crisp cyan material around the top. The fish will have custom geometry, crafted with SphereGeometry for the body and custom ShapeGeometry for the tail. I’m thinking about creating a mesh ellipsoid with the tail triangular in shape and ensuring the eyes and fins are well-positioned. I'll use attractive colors and adjust their motion for swimming oscillations.**Adjusting fish tail geometry** I'm focusing on the tail pivot, which is set at (-0.68, 0, 0) with the tail shape designed locally around that point. The body of the fish is scaled to (0.72, 0.42, 0.28), and the tail root is at (-0.55), using specific shape points for the triangular tail. The fish is oriented to swim in the +X direction, and I'll need to set its position and velocity accordingly, ensuring it moves smoothly in the desired direction.**Exploring fish behavior in 3D** I’m working on projecting the coordinates and interacting with the mouse map in a 3D scene. It looks like I need to have movements for the fish to avoid each other when they’re within a certain distance, while keeping their velocity mostly horizontal. I might use a simple wandering agent with a fleeing force for this. I’m considering how to adjust their scales and orientations based on their movement, and I need to be careful with mirroring geometry and body scaling for the fish. I also want to create vibrant seaweed using procedural curves.**Building the aquarium scene** I’m designing a 3D aquarium scene using Bezier tube geometry for the coral and rock structures, and I plan to have a sandy floor at y=-3.15. I want to incorporate animated bubbles and a UI overlay at the top left with the title "PELAGIC / AQUARIUM." There will be sleek controls, and I need to ensure everything is visible and responsive. I’m considering using an orthographic camera for shape consistency and figuring out how to manage pointer interactions while mapping coordinates from the client to the NDC. I might want to add a visual effect for the mouse pointer as well.**Designing the aquarium UI** I’m focusing on how the pointer interacts with the canvas, which means I need to highlight the UI overlay with pointer movement. For the aquarium tank, I'll use BoxGeometry for the rear panel, positioned at z=-2.45, with dimensions 11.3 wide and 6.3 high. I’ll set a BasicMaterial with color 0x0a4772 and adjust the opacity to 0.25 for some transparency. I’m not sure if I need light beams from caustics, but I’ll add a gradient background behind everything using CSS.**Designing a 3D Model** It sounds like I'm working on a 3D model with border edge cylinders using line segments with a cyan color and some opacity for visual depth. I plan to define the dimensions for the box geometry and set specific z-coordinates to position the edges. I’ll also address the top and bottom bezels while ensuring that the UI overlay doesn’t obstruct the scene too much. It’s about creating a well-designed visual structure that maintains clarity in actions and aesthetics!**Configuring a 3D Scene** I’m thinking about using a potential import map with a script type module for transparent rendering. I plan to set the clear color and output color space while also considering ACES tone mapping for better visuals. I want to ensure that the pointer’s interactions with the world map feel natural, detecting active pointers effectively. It’s essential to handle mouse coordinates in the local window, especially on pointer movement events to avoid any unintended issues. Let's keep refining this!**Considering fish design movements** I’m exploring how the tail can pivot and oscillate, so I'm thinking about how to set tail.rotation.y for side-to-side movement while the camera captures a silhouette. The tail should rotate around y for depth, but also rotate along the z-axis to create a subtle flapping effect. I'm incorporating the eyes' placement on the z-axis, and I'm considering colors and patterns for visual appeal. Finally, I need to ensure the camera dimensions fit the design.**Calculating camera perspective** I'm determining the perspective settings for a camera with a field of view (FOV) of 40 degrees at a distance of z=14.5. It has a horizontal width of 12.8, which raises questions about the distance involved. The vertical FOV also measures 40 degrees at z=14, giving a width of 16.5. Since the tank is 11.8, it seems there’s ample margin in terms of space. I'm analyzing to ensure everything fits well!**Resizing scene analysis** I can resize scene x globally by adjusting the group aquarium root scale with a clamp based on the aspect ratio. The camera setup remains unchanged, so while the visible horizontal span becomes narrower, the projected tank width shrinks proportionally. I also notice that while the camera's vertical span stays constant, the horizontal view adjusts with the aspect ratio—scaling the tank width to .42 results in a visible view width that looks great on mobile.**Figuring out scene adjustments** I’m considering reducing the tank boundaries and adjusting the fish positions while keeping the mesh shape, even if it results in some overlap. For the mobile view, I'm thinking about focusing on just the center, which would include the fish count. That might be acceptable! I could also try setting the mobile camera farther back and adjusting the root scale. Let’s see how those changes play out!