Interactive 3D aquariumOne-line
DeepSeek V4.1 Flash 1017
by DeepSeekReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 17.7 s
- Cost
- $0.0030
- Output tokens
- 6,032
- Spent thinking
- 0 tokens
- Speed
- 347.5 tok/s
- Console errors
- 0
- First token after
- 355 ms
- First visible text after
- 355 ms
- Prompt tokens
- 435
- Code size
- 17.3 KB
- Finish reason
- stop
- Served by
- Wafer
- Model id
deepseek/deepseek-v4.1-flash-1017- Reasoning sent
{"effort":"none"}- Max output
- 64,000 tokens
- Generated
- Nov 11, 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 · 508 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Aquarium</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #001420; }
#app { position: fixed; inset: 0; }
canvas { display: block; }
#overlay {
position: fixed; inset: 0; z-index: 10;
display: flex; align-items: center; justify-content: center;
pointer-events: none;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: #eaf6ff;
}
#msg {
background: rgba(4, 30, 48, 0.55);
border: 1px solid rgba(140, 220, 255, 0.35);
backdrop-filter: blur(6px);
padding: 14px 26px;
border-radius: 999px;
font-size: 15px;
letter-spacing: 0.4px;
box-shadow: 0 6px 30px rgba(0,0,0,0.4);
transition: opacity 0.5s ease, transform 0.5s ease;
text-align: center;
max-width: 90vw;
}
#msg.hide { opacity: 0; transform: translateY(-8px); }
#hint {
position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
z-index: 9; pointer-events: none;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 12.5px; letter-spacing: 0.6px; color: rgba(200, 235, 255, 0.55);
text-align: center;
}
@media (max-width: 480px) {
#msg { font-size: 13px; padding: 11px 18px; }
#hint { font-size: 11px; bottom: 12px; }
}
</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="app"></div>
<div id="overlay"><div id="msg">Click or press a key to start — move your mouse to scare the fish</div></div>
<div id="hint">Mouse near the fish → they dart away</div>
<script type="module">
import * as THREE from 'three';
// ---------- Scene ----------
const app = document.getElementById('app');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x02121f);
scene.fog = new THREE.FogExp2(0x03202f, 0.018);
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 400);
camera.position.set(0, 4.5, 34);
camera.lookAt(0, 2, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.05;
app.appendChild(renderer.domElement);
// ---------- Lights ----------
const hemi = new THREE.HemisphereLight(0x9fe4ff, 0x051a26, 1.15);
scene.add(hemi);
const keyLight = new THREE.DirectionalLight(0xbdefff, 1.6);
keyLight.position.set(10, 30, 18);
scene.add(keyLight);
const rimLight = new THREE.DirectionalLight(0x2f8fbf, 0.7);
rimLight.position.set(-15, 8, -20);
scene.add(rimLight);
// Caustic-ish point lights from above
const glow = new THREE.PointLight(0x66d9ff, 90, 90, 2);
glow.position.set(0, 20, 4);
scene.add(glow);
// ---------- Aquarium volume ----------
const BOUNDS = new THREE.Vector3(30, 18, 20); // half-extents
const halfW = BOUNDS.x, halfH = BOUNDS.y * 0.5, halfD = BOUNDS.z;
const floorY = -halfH;
// Floor sand
const sandGeo = new THREE.PlaneGeometry(halfW * 2, halfD * 2, 60, 40);
{
const pos = sandGeo.attributes.position;
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i), y = pos.getY(i);
pos.setZ(i, Math.sin(x * 0.35) * 0.35 + Math.cos(y * 0.5) * 0.3 + Math.sin((x + y) * 0.2) * 0.4);
}
sandGeo.computeVertexNormals();
}
const sandMat = new THREE.MeshStandardMaterial({ color: 0x2a4a56, roughness: 1, metalness: 0.0, flatShading: true });
const sand = new THREE.Mesh(sandGeo, sandMat);
sand.rotation.x = -Math.PI / 2;
sand.position.y = floorY - 0.4;
scene.add(sand);
// Distant back wall gradient plane (subtle)
const wallMat = new THREE.MeshBasicMaterial({ color: 0x041d2c, transparent: true, opacity: 0.6 });
const backWall = new THREE.Mesh(new THREE.PlaneGeometry(halfW * 2.4, halfH * 2.4), wallMat);
backWall.position.set(0, 2, -halfD - 2);
scene.add(backWall);
// ---------- Helper textures ----------
function makeBubbleTexture() {
const c = document.createElement('canvas');
c.width = c.height = 64;
const ctx = c.getContext('2d');
const g = ctx.createRadialGradient(32, 32, 4, 32, 32, 30);
g.addColorStop(0, 'rgba(220,245,255,0.9)');
g.addColorStop(0.55, 'rgba(160,225,255,0.35)');
g.addColorStop(1, 'rgba(120,200,255,0)');
ctx.fillStyle = g;
ctx.beginPath(); ctx.arc(32, 32, 30, 0, Math.PI * 2); ctx.fill();
const t = new THREE.CanvasTexture(c);
t.colorSpace = THREE.SRGBColorSpace;
return t;
}
const bubbleTex = makeBubbleTexture();
// ---------- Seaweed ----------
const weedMat = new THREE.MeshStandardMaterial({ color: 0x1d6b52, roughness: 0.85, side: THREE.DoubleSide });
const weedGroup = new THREE.Group();
const weedData = [];
function makeWeed(x, z) {
const segs = 6 + Math.floor(Math.random() * 4);
const g = new THREE.Group();
for (let i = 0; i < segs; i++) {
const h = 0.9 + Math.random() * 0.5;
const blade = new THREE.Mesh(new THREE.ConeGeometry(0.14, h, 4, 1, true), weedMat);
blade.position.y = i * h * 0.8;
blade.rotation.y = Math.random() * Math.PI;
blade.castShadow = false;
g.add(blade);
}
g.position.set(x, floorY - 0.2, z);
g.userData.phase = Math.random() * Math.PI * 2;
g.userData.baseSway = 0;
weedGroup.add(g);
weedData.push(g);
return g;
}
for (let i = 0; i < 26; i++) {
const x = (Math.random() - 0.5) * (halfW * 1.8);
const z = -halfD * 0.5 - Math.random() * halfD * 0.55;
makeWeed(x, z);
}
scene.add(weedGroup);
// Rocks
const rockMat = new THREE.MeshStandardMaterial({ color: 0x21414d, roughness: 1, flatShading: true });
for (let i = 0; i < 14; i++) {
const r = 0.8 + Math.random() * 2.2;
const rock = new THREE.Mesh(new THREE.DodecahedronGeometry(r, 0), rockMat);
rock.position.set(
(Math.random() - 0.5) * halfW * 1.9,
floorY + r * 0.35 - 0.3,
-halfD * 0.4 - Math.random() * halfD * 0.65
);
rock.rotation.set(Math.random() * 3, Math.random() * 3, Math.random() * 3);
rock.scale.y = 0.55 + Math.random() * 0.4;
scene.add(rock);
}
// ---------- Bubbles ----------
const bubbleCount = 220;
const bubbleGeo = new THREE.BufferGeometry();
const bubblePos = new Float32Array(bubbleCount * 3);
const bubbleVel = new Float32Array(bubbleCount);
const bubbleScale = new Float32Array(bubbleCount);
for (let i = 0; i < bubbleCount; i++) {
bubblePos[i * 3] = (Math.random() - 0.5) * halfW * 2;
bubblePos[i * 3 + 1] = floorY + Math.random() * halfH * 2;
bubblePos[i * 3 + 2] = (Math.random() - 0.5) * halfD * 2;
bubbleVel[i] = 1.2 + Math.random() * 2.5;
bubbleScale[i] = 0.15 + Math.random() * 0.4;
}
bubbleGeo.setAttribute('position', new THREE.BufferAttribute(bubblePos, 3));
const bubbleMat = new THREE.PointsMaterial({
map: bubbleTex, size: 0.9, transparent: true, opacity: 0.7,
depthWrite: false, blending: THREE.AdditiveBlending, sizeAttenuation: true
});
const bubbles = new THREE.Points(bubbleGeo, bubbleMat);
scene.add(bubbles);
// ---------- Fish ----------
const fishColors = [
0xff8a3d, 0xffd166, 0xef476f, 0x06d6a0, 0x118ab2,
0xf78c6b, 0xa0e7ff, 0xffb703, 0xff6b9d, 0x7b2cbf
];
function createFish(colorHex, size) {
const group = new THREE.Group();
const bodyMat = new THREE.MeshStandardMaterial({ color: colorHex, roughness: 0.45, metalness: 0.15, flatShading: false });
const finMat = new THREE.MeshStandardMaterial({
color: new THREE.Color(colorHex).offsetHSL(0, -0.1, -0.12),
roughness: 0.6, side: THREE.DoubleSide, transparent: true, opacity: 0.92
});
// Body: sphere scaled
const body = new THREE.Mesh(new THREE.SphereGeometry(0.5, 14, 10), bodyMat);
body.scale.set(1.4, 1.0, 0.72);
group.add(body);
// Tail (cone) — pointing -Z (behind fish), fish faces +Z
const tail = new THREE.Mesh(new THREE.ConeGeometry(0.42, 0.75, 8), finMat);
tail.rotation.x = -Math.PI / 2;
tail.position.z = -0.85;
tail.scale.set(1, 1, 0.35);
group.add(tail);
// Top dorsal fin
const dorsal = new THREE.Mesh(new THREE.ConeGeometry(0.28, 0.5, 4), finMat);
dorsal.position.set(0, 0.48, -0.1);
dorsal.scale.set(1, 1, 0.4);
group.add(dorsal);
// Side fins
const finL = new THREE.Mesh(new THREE.ConeGeometry(0.18, 0.5, 4), finMat);
finL.position.set(0.28, 0.05, 0.15);
finL.rotation.z = Math.PI / 2.6;
finL.scale.set(1, 1, 0.35);
group.add(finL);
const finR = finL.clone();
finR.position.x = -0.28;
finR.rotation.z = -Math.PI / 2.6;
group.add(finR);
// Eyes
const eyeGeo = new THREE.SphereGeometry(0.09, 8, 8);
const eyeMat = new THREE.MeshStandardMaterial({ color: 0x0b0b0b, roughness: 0.2 });
const eyeL = new THREE.Mesh(eyeGeo, eyeMat);
eyeL.position.set(0.2, 0.16, 0.52);
group.add(eyeL);
const eyeR = eyeL.clone();
eyeR.position.x = -0.2;
group.add(eyeR);
group.scale.setScalar(size);
group.userData.tail = tail;
group.userData.body = body;
return group;
}
const fishes = [];
const FISH_COUNT = 34;
for (let i = 0; i < FISH_COUNT; i++) {
const size = 0.55 + Math.random() * 0.9;
const color = fishColors[i % fishColors.length];
const fish = createFish(color, size);
// random start pos
fish.position.set(
(Math.random() - 0.5) * halfW * 1.7,
(Math.random() - 0.5) * halfH * 1.5,
(Math.random() - 0.5) * halfD * 1.6
);
const dir = new THREE.Vector3(Math.random() - 0.5, (Math.random() - 0.5) * 0.2, Math.random() - 0.5).normalize();
fish.userData.velocity = dir.multiplyScalar(1.5 + Math.random() * 1.5);
fish.userData.baseSpeed = 2.0 + Math.random() * 1.6;
fish.userData.size = size;
fish.userData.wander = Math.random() * Math.PI * 2;
fish.userData.wobble = Math.random() * Math.PI * 2;
fish.userData.fear = 0;
fish.userData.target = new THREE.Vector3();
fish.userData.hasTarget = false;
scene.add(fish);
fishes.push(fish);
}
// ---------- Mouse interaction ----------
const mouse = new THREE.Vector2(0, 0);
const raycaster = new THREE.Raycaster();
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
const mouseWorld = new THREE.Vector3(0, 0, 0);
let pointerActive = false;
let pointerIdleTimer = 0;
let pointerScreen = { x: 0, y: 0 };
let started = false;
const msgEl = document.getElementById('msg');
function startInteraction() {
if (!started) {
started = true;
msgEl.classList.add('hide');
setTimeout(() => { msgEl.style.display = 'none'; }, 600);
}
}
window.addEventListener('pointermove', (e) => {
pointerScreen.x = (e.clientX / window.innerWidth) * 2 - 1;
pointerScreen.y = -(e.clientY / window.innerHeight) * 2 + 1;
pointerActive = true;
pointerIdleTimer = 0;
startInteraction();
});
window.addEventListener('pointerdown', startInteraction);
window.addEventListener('keydown', startInteraction);
window.addEventListener('touchstart', startInteraction, { passive: true });
window.addEventListener('touchmove', (e) => {
if (e.touches.length) {
const t = e.touches[0];
pointerScreen.x = (t.clientX / window.innerWidth) * 2 - 1;
pointerScreen.y = -(t.clientY / window.innerHeight) * 2 + 1;
pointerActive = true;
pointerIdleTimer = 0;
}
}, { passive: true });
function updateMouseWorld() {
raycaster.setFromCamera(pointerScreen, camera);
// Compute intersection with a plane parallel to camera through origin
const normal = new THREE.Vector3();
camera.getWorldDirection(normal);
plane.setFromNormalAndCoplanarPoint(normal, new THREE.Vector3(0, 2, 0));
raycaster.ray.intersectPlane(plane, mouseWorld);
}
// ---------- Animation ----------
const clock = new THREE.Clock();
const tmpVec = new THREE.Vector3();
const tmpVec2 = new THREE.Vector3();
const up = new THREE.Vector3(0, 1, 0);
function updateFish(dt, t) {
// update mouse world + activity
if (pointerActive) {
updateMouseWorld();
pointerIdleTimer += dt;
if (pointerIdleTimer > 2.5) {
pointerActive = false;
}
}
const mouseActive = pointerActive && started;
const fearRadius = 7.0;
for (let i = 0; i < fishes.length; i++) {
const fish = fishes[i];
const ud = fish.userData;
const v = ud.velocity;
// Wander: gently rotate velocity with noise
ud.wander += dt * (0.6 + Math.sin(t * 0.7 + i) * 0.3);
const wanderStrength = 2.5;
v.x += Math.cos(ud.wander + i) * wanderStrength * dt;
v.y += Math.sin(ud.wander * 1.3 + i * 0.7) * wanderStrength * 0.35 * dt;
v.z += Math.sin(ud.wander * 0.8 + i * 1.3) * wanderStrength * dt;
// Mouse fear
if (mouseActive) {
tmpVec.copy(fish.position).sub(mouseWorld);
const dist = tmpVec.length();
if (dist < fearRadius) {
const strength = (1 - dist / fearRadius);
ud.fear = Math.min(1, ud.fear + dt * 6 * strength);
// flee direction (away from mouse)
tmpVec.normalize();
// add a bit of vertical evade to look lively
tmpVec.y += (Math.random() - 0.5) * 0.3;
v.addScaledVector(tmpVec, strength * 42 * dt);
}
}
// fear decay
ud.fear *= Math.pow(0.12, dt);
if (ud.fear < 0.001) ud.fear = 0;
// Speed clamp
const speed = v.length();
const maxSpeed = ud.baseSpeed + ud.fear * 12;
if (speed > maxSpeed) {
v.multiplyScalar(maxSpeed / speed);
} else if (speed < ud.baseSpeed * 0.55) {
// keep moving
if (speed > 0.0001) v.multiplyScalar((ud.baseSpeed * 0.55) / speed);
else v.set(Math.random() - 0.5, 0, Math.random() - 0.5).normalize().multiplyScalar(ud.baseSpeed);
}
// Steer away from walls (soft bounds)
const margin = 4.0;
const steer = 24 * dt;
const x = fish.position.x, y = fish.position.y, z = fish.position.z;
if (x > halfW - margin) v.x -= steer * (x - (halfW - margin)) / margin;
if (x < -halfW + margin) v.x += steer * ((-halfW + margin) - x) / margin;
if (y > halfH * 0.85 - 0.5) v.y -= steer * (y - (halfH * 0.85 - 0.5)) / margin;
if (y < -halfH * 0.85 + 1.0) v.y += steer * ((-halfH * 0.85 + 1.0) - y) / margin;
if (z > halfD - margin) v.z -= steer * (z - (halfD - margin)) / margin;
if (z < -halfD + margin) v.z += steer * ((-halfD + margin) - z) / margin;
// Apply velocity
fish.position.addScaledVector(v, dt);
// Orientation: fish model faces +Z. Build quaternion aligning +Z with velocity.
if (speed > 0.01) {
tmpVec2.copy(v).normalize();
const targetQuat = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), tmpVec2);
fish.quaternion.slerp(targetQuat, Math.min(1, dt * 6));
}
// Tail wiggle
ud.wobble += dt * (8 + ud.fear * 20 + speed * 1.2);
const tail = ud.tail;
if (tail) {
tail.rotation.y = Math.sin(ud.wobble) * 0.5;
tail.rotation.x = -Math.PI / 2 + Math.sin(ud.wobble * 1.3) * 0.15;
}
// slight body roll
fish.rotateZ(Math.sin(ud.wobble * 0.5) * 0.02);
}
}
function updateWeeds(t, dt) {
for (let i = 0; i < weedData.length; i++) {
const w = weedData[i];
const ph = w.userData.phase;
w.children.forEach((seg, idx) => {
const sway = Math.sin(t * 1.1 + ph + idx * 0.5) * (0.04 + idx * 0.012);
seg.rotation.z = sway;
});
}
}
function updateBubbles(dt, t) {
const pos = bubbleGeo.attributes.position;
for (let i = 0; i < bubbleCount; i++) {
let y = pos.getY(i) + bubbleVel[i] * dt;
let x = pos.getX(i) + Math.sin(t * 1.3 + i) * 0.006;
let z = pos.getZ(i) + Math.cos(t * 1.1 + i * 0.7) * 0.006;
if (y > halfH + 1.5) {
y = floorY - 0.5;
x = (Math.random() - 0.5) * halfW * 2;
z = (Math.random() - 0.5) * halfD * 2;
}
pos.setX(i, x); pos.setY(i, y); pos.setZ(i, z);
}
pos.needsUpdate = true;
}
// Subtle camera drift, plus slight follow of pointer
let camBaseX = 0, camBaseY = 4.5, camBaseZ = 34;
function updateCamera(t) {
const targetX = pointerActive ? pointerScreen.x * 3.5 : 0;
const targetY = pointerActive ? 4.5 - pointerScreen.y * 2.0 : 4.5;
camera.position.x += (targetX - camera.position.x) * 0.02;
camera.position.y += (targetY - camera.position.y) * 0.02;
camera.position.z = 34 + Math.sin(t * 0.15) * 1.2;
camera.lookAt(0, 2.2 + Math.sin(t * 0.3) * 0.3, 0);
}
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05);
const t = clock.elapsedTime;
updateFish(dt, t);
updateWeeds(t, dt);
updateBubbles(dt, t);
updateCamera(t);
// gentle light pulsing
glow.intensity = 80 + Math.sin(t * 0.9) * 20;
renderer.render(scene, camera);
}
// Auto-demo behaviour: if user hasn't interacted, simulate a moving pointer so fish are visibly spooked
let demoTimer = 0;
let demoActive = true;
const demoPointer = { x: 0, y: 0 };
window.addEventListener('pointermove', () => { demoActive = false; }, { once: true });
// Patch updateMouseWorld usage: if demo, we drive pointerScreen
const origUpdateFish = updateFish;
updateFish = function(dt, t) {
if (demoActive) {
// move pointer in a lissajous around fishes
demoTimer += dt;
demoPointer.x = Math.sin(demoTimer * 0.55) * 0.55;
demoPointer.y = Math.sin(demoTimer * 0.83 + 1.0) * 0.35;
pointerScreen.x = demoPointer.x;
pointerScreen.y = demoPointer.y;
pointerActive = true;
}
origUpdateFish(dt, t);
};
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>