Interactive 3D aquariumOne-line
DeepSeek V4 Flash 0423
by DeepSeekreasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 25.1 s
- Cost
- $0.0008
- Output tokens
- 4,630
- Spent thinking
- 0 tokens
- Speed
- 189.6 tok/s
- Console errors
- 0
- First token after
- 643 ms
- First visible text after
- 643 ms
- Prompt tokens
- 434
- Code size
- 15.0 KB
- Finish reason
- stop
- Served by
- Wafer
- Model id
deepseek/deepseek-v4-flash- Reasoning sent
model default- Max output
- 393,216 tokens
- Generated
- Nov 13, 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 · 457 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>3D Aquarium – fish avoid mouse</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a1a2b;
overflow: hidden;
font-family: system-ui, -apple-system, sans-serif;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
cursor: default;
}
#overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
color: white;
text-shadow: 0 2px 12px rgba(0,0,0,0.8);
z-index: 10;
transition: opacity 1s ease;
}
#overlay h1 {
font-size: 2.6rem;
font-weight: 400;
letter-spacing: 4px;
margin-bottom: 0.75rem;
background: rgba(0,30,50,0.5);
padding: 0.4rem 1.8rem;
border-radius: 60px;
backdrop-filter: blur(4px);
}
#overlay p {
font-size: 1.2rem;
opacity: 0.8;
background: rgba(0,0,0,0.25);
padding: 0.3rem 1.2rem;
border-radius: 30px;
}
#overlay.hidden {
opacity: 0;
pointer-events: none;
}
/* small hint on mobiles */
@media (max-width: 600px) {
#overlay h1 { font-size: 1.8rem; padding: 0.2rem 1rem; }
#overlay p { font-size: 1rem; }
}
</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="overlay">
<h1>🐟 Aquarium</h1>
<p>move mouse • fish swim away</p>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
// ---------- setup ----------
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a2a3a);
scene.fog = new THREE.FogExp2(0x0a2a3a, 0.007);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(12, 6, 18);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
document.body.prepend(renderer.domElement);
// optional CSS2D for labels (not used, but import shows we can)
// just keep it simple
// ---------- lighting ----------
const ambient = new THREE.AmbientLight(0x446688, 0.6);
scene.add(ambient);
const mainLight = new THREE.DirectionalLight(0xffeedd, 2.2);
mainLight.position.set(8, 20, 10);
mainLight.castShadow = true;
mainLight.shadow.mapSize.width = 1024;
mainLight.shadow.mapSize.height = 1024;
scene.add(mainLight);
const backLight = new THREE.DirectionalLight(0x88ccff, 0.9);
backLight.position.set(-5, 0, -10);
scene.add(backLight);
const fillLight = new THREE.PointLight(0x66aaff, 0.5);
fillLight.position.set(0, 6, 6);
scene.add(fillLight);
// ---------- environment: water effect & background particles ----------
// subtle caustic-like circles
const particleGeo = new THREE.BufferGeometry();
const count = 600;
const pos = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const r = 12 + Math.random() * 14;
const theta = Math.random() * Math.PI * 2;
const phi = Math.random() * Math.PI - Math.PI/2;
pos[i*3] = Math.cos(theta) * Math.cos(phi) * r;
pos[i*3+1] = Math.sin(phi) * r * 0.6 + 1;
pos[i*3+2] = Math.sin(theta) * Math.cos(phi) * r;
}
particleGeo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
const particleMat = new THREE.PointsMaterial({
color: 0x88ccff,
size: 0.12,
transparent: true,
opacity: 0.25,
blending: THREE.AdditiveBlending,
});
const particles = new THREE.Points(particleGeo, particleMat);
scene.add(particles);
// ground – sandy with pebbles
const groundGeo = new THREE.CircleGeometry(14, 40);
const groundMat = new THREE.MeshStandardMaterial({
color: 0x5c8a6b,
roughness: 0.9,
metalness: 0.1,
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -2.4;
ground.receiveShadow = true;
scene.add(ground);
// some rocks / coral
for (let i = 0; i < 22; i++) {
const size = 0.3 + Math.random() * 0.8;
const rock = new THREE.Mesh(
new THREE.DodecahedronGeometry(size * (0.7 + Math.random()*0.6), 0),
new THREE.MeshStandardMaterial({ color: 0xc97a5e + Math.floor(Math.random()*0x553322), roughness: 0.8 })
);
const angle = Math.random() * Math.PI * 2;
const rad = 3 + Math.random() * 8;
rock.position.set(
Math.cos(angle) * rad,
-2.4 + size * 0.3,
Math.sin(angle) * rad
);
rock.rotation.set(Math.random(), Math.random(), Math.random());
rock.castShadow = true;
rock.receiveShadow = true;
scene.add(rock);
}
// seaweed-like stems (simple cylinders)
for (let i = 0; i < 40; i++) {
const height = 0.5 + Math.random() * 1.8;
const stem = new THREE.Mesh(
new THREE.CylinderGeometry(0.06, 0.09, height, 4),
new THREE.MeshStandardMaterial({ color: 0x4c9a6a, roughness: 0.7 })
);
const angle = Math.random() * Math.PI * 2;
const rad = 2 + Math.random() * 9;
stem.position.set(
Math.cos(angle) * rad,
-2.4 + height/2,
Math.sin(angle) * rad
);
stem.rotation.x = (Math.random() - 0.5) * 0.5;
stem.rotation.z = (Math.random() - 0.5) * 0.5;
stem.castShadow = true;
scene.add(stem);
}
// ---------- fish ----------
const fishGroup = new THREE.Group();
scene.add(fishGroup);
// create a simple fish mesh (reusable)
function createFishBody(color = 0xff8844) {
const group = new THREE.Group();
// body (ellipsoid)
const bodyGeo = new THREE.SphereGeometry(0.6, 10, 8);
const bodyMat = new THREE.MeshStandardMaterial({ color, roughness: 0.3, metalness: 0.2 });
const body = new THREE.Mesh(bodyGeo, bodyMat);
body.scale.set(1.3, 0.8, 0.7);
body.castShadow = true;
group.add(body);
// tail
const tailMat = new THREE.MeshStandardMaterial({ color: 0xdd8844, roughness: 0.6 });
const tail = new THREE.Mesh(new THREE.ConeGeometry(0.35, 0.6, 4), tailMat);
tail.position.set(-0.8, 0, 0);
tail.rotation.z = Math.PI/2;
group.add(tail);
// dorsal fin
const finMat = new THREE.MeshStandardMaterial({ color: 0xdd9955, transparent: true, opacity: 0.7 });
const fin = new THREE.Mesh(new THREE.ConeGeometry(0.2, 0.35, 4), finMat);
fin.position.set(0.1, 0.5, 0);
fin.rotation.x = 0.3;
group.add(fin);
// eyes
const eyeMat = new THREE.MeshStandardMaterial({ color: 0xffffff });
const pupilMat = new THREE.MeshStandardMaterial({ color: 0x222222 });
const eyeGeo = new THREE.SphereGeometry(0.16, 8, 8);
const pupilGeo = new THREE.SphereGeometry(0.08, 8, 8);
const eyeL = new THREE.Mesh(eyeGeo, eyeMat);
eyeL.position.set(0.7, 0.22, 0.35);
group.add(eyeL);
const pupilL = new THREE.Mesh(pupilGeo, pupilMat);
pupilL.position.set(0.85, 0.22, 0.38);
group.add(pupilL);
const eyeR = new THREE.Mesh(eyeGeo, eyeMat);
eyeR.position.set(0.7, 0.22, -0.35);
group.add(eyeR);
const pupilR = new THREE.Mesh(pupilGeo, pupilMat);
pupilR.position.set(0.85, 0.22, -0.38);
group.add(pupilR);
return group;
}
// create 24 fish with random colors and positions
const fishObjects = [];
const fishColors = [0xff8844, 0x44aaff, 0xff4d6d, 0x66ddaa, 0xffbb33, 0xcc66cc, 0x88aaff, 0xff9966];
for (let i = 0; i < 26; i++) {
const color = fishColors[Math.floor(Math.random() * fishColors.length)];
const fish = createFishBody(color);
// random position inside aquarium bounds
const radius = 3 + Math.random() * 7;
const angle = Math.random() * Math.PI * 2;
const y = -1.5 + Math.random() * 3.5;
fish.position.set(
Math.cos(angle) * radius,
y,
Math.sin(angle) * radius
);
// random orientation
fish.rotation.y = Math.random() * Math.PI * 2;
// store some custom data
fish.userData = {
speed: 0.3 + Math.random() * 0.4,
angle: Math.random() * Math.PI * 2,
verticalOsc: Math.random() * 0.6,
verticalPhase: Math.random() * 6.28,
radius: radius,
centerY: y,
swimDir: 1,
turnTimer: 0,
avoidSpeed: 0,
avoidDir: 0,
panic: 0,
};
fishGroup.add(fish);
fishObjects.push(fish);
}
// ---------- tiny floating bubbles ----------
const bubbleGeo = new THREE.BufferGeometry();
const bCount = 130;
const bPos = new Float32Array(bCount * 3);
for (let i = 0; i < bCount; i++) {
const r = 2 + Math.random() * 10;
const theta = Math.random() * Math.PI * 2;
bPos[i*3] = Math.cos(theta) * r;
bPos[i*3+1] = -2 + Math.random() * 5;
bPos[i*3+2] = Math.sin(theta) * r;
}
bubbleGeo.setAttribute('position', new THREE.BufferAttribute(bPos, 3));
const bubbleMat = new THREE.PointsMaterial({
color: 0xaaddff,
size: 0.08,
transparent: true,
opacity: 0.25,
blending: THREE.AdditiveBlending,
});
const bubbles = new THREE.Points(bubbleGeo, bubbleMat);
scene.add(bubbles);
// ---------- mouse tracking ----------
const mouseWorld = new THREE.Vector3(0, 0, 10);
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
function updateMouse(clientX, clientY) {
pointer.x = (clientX / window.innerWidth) * 2 - 1;
pointer.y = -(clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
// project onto plane y = 0 (approx water level)
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
const intersectPoint = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, intersectPoint);
if (intersectPoint) {
mouseWorld.copy(intersectPoint);
} else {
// fallback
mouseWorld.set(0, 0, 10);
}
}
window.addEventListener('mousemove', (e) => {
updateMouse(e.clientX, e.clientY);
});
window.addEventListener('touchmove', (e) => {
if (e.touches.length > 0) {
updateMouse(e.touches[0].clientX, e.touches[0].clientY);
}
}, { passive: true });
// also on touch start
window.addEventListener('touchstart', (e) => {
if (e.touches.length > 0) {
updateMouse(e.touches[0].clientX, e.touches[0].clientY);
}
}, { passive: true });
// ---------- hide overlay on interaction ----------
const overlay = document.getElementById('overlay');
function hideOverlay() {
overlay.classList.add('hidden');
}
window.addEventListener('click', hideOverlay);
window.addEventListener('touchstart', hideOverlay, { passive: true });
window.addEventListener('keydown', hideOverlay);
// ---------- animation loop ----------
const clock = new THREE.Clock();
function animate() {
const delta = Math.min(clock.getDelta(), 0.05);
const time = clock.elapsedTime;
// rotate particles slowly
particles.rotation.y += 0.0002;
bubbles.rotation.y += 0.0005;
// animate fish
const avoidRadius = 3.2; // distance that triggers avoidance
const strongAvoidRadius = 1.8;
fishObjects.forEach((fish, idx) => {
const ud = fish.userData;
const pos = fish.position;
// distance from mouse in XZ plane
const dx = pos.x - mouseWorld.x;
const dz = pos.z - mouseWorld.z;
const dist = Math.sqrt(dx*dx + dz*dz);
let avoidX = 0, avoidZ = 0;
let panic = 0;
if (dist < avoidRadius) {
// fish swims away from mouse
const strength = Math.max(0, 1 - dist / avoidRadius);
const dirX = dx / (dist + 0.001);
const dirZ = dz / (dist + 0.001);
const factor = strength * 1.8;
avoidX = dirX * factor;
avoidZ = dirZ * factor;
panic = strength;
// extra speed
ud.avoidSpeed = Math.min(ud.avoidSpeed + 0.6 * delta, 2.0);
} else {
ud.avoidSpeed = Math.max(0, ud.avoidSpeed - 0.2 * delta);
}
// normal idle movement: gentle circle & vertical bob
ud.angle += 0.005 + ud.speed * 0.002;
const targetX = Math.cos(ud.angle + idx) * ud.radius;
const targetZ = Math.sin(ud.angle + idx) * ud.radius;
const targetY = ud.centerY + Math.sin(time * 0.5 + ud.verticalPhase) * ud.verticalOsc * 0.7;
// blend towards target (smooth)
const lerpFactor = 0.02 + panic * 0.06;
pos.x += (targetX - pos.x) * lerpFactor;
pos.z += (targetZ - pos.z) * lerpFactor;
pos.y += (targetY - pos.y) * lerpFactor * 0.5;
// apply avoidance (additive)
pos.x += avoidX * delta * 2.8;
pos.z += avoidZ * delta * 2.8;
// rotation: face movement direction
const dir = new THREE.Vector3(
targetX - pos.x + avoidX * 0.5,
0,
targetZ - pos.z + avoidZ * 0.5
);
if (dir.length() > 0.01) {
const targetRot = Math.atan2(dir.x, dir.z);
let diff = targetRot - fish.rotation.y;
while (diff > Math.PI) diff -= Math.PI * 2;
while (diff < -Math.PI) diff += Math.PI * 2;
fish.rotation.y += diff * 0.1;
}
// slight body tilt when avoiding
fish.rotation.z = -avoidX * 0.08;
fish.rotation.x = avoidZ * 0.06;
});
// camera subtle drift
camera.position.x = 12 + Math.sin(time * 0.02) * 1.2;
camera.position.z = 18 + Math.cos(time * 0.023) * 1.2;
camera.lookAt(0, 0.5, 0);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
// ---------- resize ----------
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// ---------- pointer click to hide overlay ----------
// also hide after 2 sec anyway (auto)
setTimeout(() => {
overlay.classList.add('hidden');
}, 3000);
console.log('🐠 3D aquarium ready – move mouse to scare fish!');
</script>
</body>
</html>