Models 44

Nemotron 3.5 Super VL (free)

by NVIDIAReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
1 min 44 s
Cost
$0
Output tokens
10,571
Spent thinking
7,897 tokens
Speed
102.3 tok/s
Console errors
0
First token after
423 ms
First visible text after
1 min 29 s
Prompt tokens
448
Code size
8.3 KB
Finish reason
stop
Served by
Nvidia
Model id
nvidia/nemotron-3.5-super-vl:free
Reasoning sent
model default
Max output
32,000 tokens
Generated
Nov 9, 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 · 267 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: #000; font-family: system-ui, -apple-system, sans-serif; }
  canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: block; }
  #overlay {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; background: rgba(0, 18, 35, 0.65);
    color: #e0f7fa; text-align: center; padding: 20px; z-index: 10; cursor: pointer; user-select: none;
  }
  #overlay.hidden { display: none; }
  #overlay h1 { margin: 0 0 8px; font-size: clamp(1.5rem, 5vw, 2.5rem); letter-spacing: 2px; text-transform: uppercase; color: #4fc3f7; }
  #overlay p { margin: 4px 0; font-size: clamp(0.9rem, 2vw, 1.1rem); opacity: 0.9; }
  #overlay p:last-child { margin-top: 12px; font-size: 0.85rem; opacity: 0.7; }
</style>
</head>
<body>
<div id="overlay">
  <h1>Aquarium</h1>
  <p>Click or press any key to start</p>
  <p>Move your mouse near the fish to scare them away</p>
</div>

<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>

<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let scene, camera, renderer, controls, raycaster, clock;
let fish = [];
let isStarted = false;

const FISH_COUNT = 12;
const mouseVec = new THREE.Vector2(0, 0);
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
const raycasterInstance = new THREE.Raycaster();
const intersection = new THREE.Vector3();
const clockInstance = new THREE.Clock();

function getMouseWorld() {
  raycasterInstance.setFromCamera(mouseVec, camera);
  return raycasterInstance.ray.intersectPlane(plane, intersection) ? intersection : null;
}

class Fish {
  constructor() {
    this.group = new THREE.Group();

    const hue = 0.55 + Math.random() * 0.15;
    const bodyColor = new THREE.Color().setHSL(hue, 0.8, 0.5);
    const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor });

    const body = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 6), bodyMat);
    body.scale.set(1, 0.55, 0.85);
    this.group.add(body);

    const tail = new THREE.Mesh(new THREE.ConeGeometry(0.15, 0.45, 4), bodyMat);
    tail.rotation.x = Math.PI / 2;
    tail.position.x = -0.42;
    this.group.add(tail);

    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.04, 4, 4), new THREE.MeshBasicMaterial({ color: 0x000000 }));
    eye.position.set(0.22, 0.08, 0.22);
    this.group.add(eye);

    scene.add(this.group);
    this.reset();
  }

  reset() {
    this.position = new THREE.Vector3(
      (Math.random() - 0.5) * 14,
      Math.random() * 2 - 1,
      (Math.random() - 0.5) * 14
    );
    this.group.position.copy(this.position);
    this.target = this.position.clone();
    this.speed = 0.02 + Math.random() * 0.025;
    this.wanderTime = 0;
    this.wanderInterval = 2 + Math.random() * 3;
    this.fleeTime = 0;
    this.velocity = new THREE.Vector3();
  }

  update(delta, mouseWorld, demo) {
    const t = performance.now() * 0.003;

    this.group.rotation.z = Math.sin(t * 6) * 0.15;
    this.group.rotation.x = Math.cos(t * 4) * 0.08;

    if (this.velocity.lengthSq() > 0.00001) {
      this.group.quaternion.setFromUnitVectors(
        new THREE.Vector3(1, 0, 0),
        this.velocity.clone().normalize()
      );
    }

    if (this.fleeTime > 0 && !demo) {
      this.fleeTime -= delta;
      this.position.add(this.velocity.clone().multiplyScalar(delta * 60));
    } else {
      this.fleeTime = 0;
      this.wanderTime += delta;
      if (this.wanderTime > this.wanderInterval) {
        this.wanderTime = 0;
        this.wanderInterval = 2 + Math.random() * 3;
        this.target.set(
          (Math.random() - 0.5) * 14,
          Math.random() * 2 - 1,
          (Math.random() - 0.5) * 14
        );
        this.speed = 0.02 + Math.random() * 0.025;
      }
      const dir = new THREE.Vector3().subVectors(this.target, this.position);
      const d = dir.length();
      if (d < 0.25) {
        this.wanderTime = this.wanderInterval;
      } else {
        dir.normalize();
        this.velocity.copy(dir).multiplyScalar(this.speed);
        this.position.add(this.velocity.clone().multiplyScalar(delta * 60));
        this.position.y += Math.sin(t) * 0.004;
      }
    }

    this.position.x = Math.max(-8.5, Math.min(8.5, this.position.x));
    this.position.z = Math.max(-8.5, Math.min(8.5, this.position.z));
    this.position.y = Math.max(-2.4, Math.min(1.2, this.position.y));

    this.group.position.copy(this.position);

    if (!demo && mouseWorld) {
      const dist = this.position.distanceTo(mouseWorld);
      if (dist < 2.2 && this.fleeTime <= 0) {
        this.fleeTime = 1.2;
        this.velocity = new THREE.Vector3()
          .subVectors(this.position, mouseWorld)
          .normalize()
          .multiplyScalar(0.09);
        this.velocity.y = Math.max(this.velocity.y, 0.02);
      }
    }
  }
}

function init() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x00121f);
  scene.fog = new THREE.Fog(0x00121f, 6, 28);

  camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 100);
  camera.position.set(12, 6, 12);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  document.body.appendChild(renderer.domElement);

  controls = new OrbitControls(camera, renderer.domElement);
  controls.target.set(0, 0, 0);
  controls.enableDamping = true;
  controls.autoRotate = true;
  controls.autoRotateSpeed = 0.8;
  controls.minDistance = 5;
  controls.maxDistance = 40;

  const ambient = new THREE.AmbientLight(0xffffff, 0.6);
  scene.add(ambient);

  const dir = new THREE.DirectionalLight(0xffffff, 1.5);
  dir.position.set(10, 15, 10);
  scene.add(dir);

  const surface = new THREE.Mesh(
    new THREE.PlaneGeometry(40, 40),
    new THREE.MeshStandardMaterial({ color: 0x002233, transparent: true, opacity: 0.5, side: THREE.DoubleSide })
  );
  surface.rotation.x = -Math.PI / 2;
  scene.add(surface);

  const sand = new THREE.Mesh(
    new THREE.PlaneGeometry(40, 40),
    new THREE.MeshStandardMaterial({ color: 0x6b5b45 })
  );
  sand.rotation.x = -Math.PI / 2;
  sand.position.y = -3;
  scene.add(sand);

  for (let i = 0; i < 16; i++) {
    const h = 0.8 + Math.random() * 1.8;
    const plant = new THREE.Mesh(
      new THREE.ConeGeometry(0.18, h, 5),
      new THREE.MeshStandardMaterial({ color: 0x007a4d })
    );
    plant.position.set(Math.random() * 14 - 7, -3 + h / 2, Math.random() * 14 - 7);
    plant.rotation.x = -Math.PI / 2;
    scene.add(plant);
  }

  for (let i = 0; i < FISH_COUNT; i++) fish.push(new Fish());

  window.addEventListener('resize', onResize);
  window.addEventListener('mousemove', onMouseMove);
  window.addEventListener('touchmove', onTouchMove, { passive: false });

  document.getElementById('overlay').addEventListener('click', startExperience);
  window.addEventListener('keydown', startExperience);

  animate();
}

function startExperience() {
  if (isStarted) return;
  isStarted = true;
  document.getElementById('overlay').classList.add('hidden');
  controls.autoRotate = false;
}

function onMouseMove(e) {
  mouseVec.x = (e.clientX / window.innerWidth) * 2 - 1;
  mouseVec.y = -(e.clientY / window.innerHeight) * 2 + 1;
}

function onTouchMove(e) {
  e.preventDefault();
  if (e.touches.length) {
    mouseVec.x = (e.touches[0].clientX / window.innerWidth) * 2 - 1;
    mouseVec.y = -(e.touches[0].clientY / window.innerHeight) * 2 + 1;
  }
}

function onResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  requestAnimationFrame(animate);
  const delta = Math.min(clockInstance.getDelta(), 0.1);

  const demo = !isStarted;
  const mouseWorld = demo ? null : getMouseWorld();

  fish.forEach(f => f.update(delta, mouseWorld, demo));

  controls.update();
  renderer.render(scene, camera);
}

init();
</script>
</body>
</html>
Reasoning trace (7,897 tokens)
The user wants a 3D aquarium with Three.js where fish swim away when the mouse moves near them.

Requirements:
- Single HTML file with inline CSS/JS
- Import map provided for Three.js
- Responsive (1280x800 down to 375px)
- No horizontal scrollbar
- Meaningful visible on first paint (3 seconds)
- Self-playing demo mode behind "Click or press a key to start" message (since it's interactive but can have auto mode)
- Only the allowed CDN URLs (the import map URLs)
- No external images/fonts/network requests except CDN
- No localStorage etc., no alert/confirm/prompt/window.open
- Zero errors in Chrome

Design:
- Scene with water (blue gradient background or plane), sand bottom, some plants/corals maybe simple geometries
- Fish: simple geometry (ellipsoid/cone combined or just a BoxGeometry scaled, or use a group of meshes). Multiple fish swimming around.
- Mouse interaction: raycast from mouse to find fish nearby, or just check distance between mouse world position and fish positions. Simpler: convert mouse to 3D vector on the "water plane" or just use normalized device coordinates and check distance in screen space? Better: unproject mouse to a point at a certain depth (e.g., z=0) and check Euclidean distance to fish positions. If close, fish flee away from mouse.
- Fish behavior: idle swimming (sine wave movement), flee when mouse near, then return to idle.
- Camera: OrbitControls? But addons are allowed. However to keep it simple and avoid dependency issues, I can use PointerLockControls or just a simple auto-rotating camera, or fixed camera with slight movement. Let's use OrbitControls from three/addons/controls/OrbitControls.js. That's an allowed addon.
- "Click or press a key to start" overlay. Before start, demo mode plays (fish swim automatically, camera moves). After start, mouse interaction is active.

Since it's an aquarium, mouse moves near fish -> fish swim away. On desktop, mousemove. On mobile, touch? Could add touch move as well.

Structure:
- HTML: container div, canvas, overlay with message.
- CSS: fullscreen, overlay centered, hide overflow.
- JS:
  - Import Three.js and OrbitControls via import map.
  - Setup scene, camera, renderer.
  - Add lights (ambient, directional).
  - Add water plane (large, blue, transparent maybe), sand plane, some simple plants (cylinders/cones).
  - Fish class: group with body (SphereGeometry or BoxGeometry), tail (Cone), eye. Position random. Velocity. Update method: idle mode -> swim in direction with wiggle; flee mode -> move away from mouse; after timer, resume idle.
  - Mouse vector: normalized device coordinates, raycaster to intersect water plane at y=0 (or scene center depth) to get world target. Or simpler: map mouse to world coordinates assuming fish are at y around 0 and camera looks at center. Let's do: mouse = new THREE.Vector2(); on move, set mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; Then raycaster.setFromCamera(mouse, camera); raycaster.ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0,1,0), 0), targetPoint); This gives world point on y=0 plane. Then check distance from each fish to targetPoint. If < threshold, flee.
  - Animation loop: update fish, renderer.render.
  - Responsive: handle window resize.
  - Start overlay: click or key starts interaction mode. Before start, demo mode: mouse target moves automatically or fish just swim. After start, enable mouse listeners.

Let's write the code.

Fish geometry: Use a group.
- Body: SphereGeometry(0.4, 8, 6) scaled (1, 0.6, 0.8) or just BoxGeometry(0.8, 0.3, 0.5). Let's do a more fish-like shape: lathe? Too complex. Use a combination: body = new THREE.Mesh(new THREE.SphereGeometry(0.35, 8, 6), material); body.scale.set(1, 0.5, 0.8); tail = new THREE.Mesh(new THREE.ConeGeometry(0.2, 0.4, 4), material); tail.rotation.x = Math.PI/2; tail.position.x = -0.4; fin = etc. Color variations: random blues/oranges.

Movement:
- Each fish has: position, velocity (Vector3), target (idle point), speed, wander timer.
- Idle: every few seconds pick a new random target within bounds. Move towards it with speed. Add sine offset to y for swimming up/down.
- Flee: direction = fish.position - mouseWorldPoint. normalize. move away. speed increased. fleeDuration frames.

Camera: OrbitControls with target 0,0,0, autoRotate maybe in demo mode.

Bounds: aquarium box maybe [-8,8] x [-4,4] x [-8,8]? Fish y between -2 and 2.

Water plane: large PlaneGeometry 30x30, color #001e36, transparent opacity 0.8? But if we need to intersect plane y=0, a plane at y=0 is fine. Sand at y=-3.

Let's code carefully.

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>

Then:
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

Overlay:
<div id="overlay"><h1>Aquarium</h1><p>Click or press any key to start</p><p>Move your mouse to scare the fish</p></div>

CSS:
* { margin:0; padding:0; box-sizing:border-box; }
html, body { width:100%; height:100%; overflow:hidden; background:#000; }
canvas { display:block; width:100%; height:100%; }
#overlay { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; background:rgba(0,20,40,0.85); color:#fff; font-family:sans-serif; text-align:center; z-index:10; cursor:pointer; }
#overlay.hidden { display:none; }

JS details:

let scene, camera, renderer, controls, raycaster, mouse, plane;
let fish = [];
let isStarted = false;
let demoMouseTarget = new THREE.Vector3();

Init:
scene = new THREE.Scene();
scene.background = new THREE.Color(0x001520);
scene.fog = new THREE.Fog(0x001520, 5, 30);

camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 100);
camera.position.set(10, 5, 10);

renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);

controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.enableDamping = true;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;

raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2(-9999, -9999);
plane = new THREE.Plane(new THREE.Vector3(0,1,0), 0); // y=0

Lights:
ambient = new THREE.AmbientLight(0x404040, 2);
dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(10, 20, 10);
scene.add(ambient, dirLight);

Aquarium decor:
// Water plane at y=0
const waterGeo = new THREE.PlaneGeometry(40, 40);
const waterMat = new THREE.MeshStandardMaterial({ color: 0x00334d, transparent: true, opacity: 0.6, side: THREE.DoubleSide });
const water = new THREE.Mesh(waterGeo, waterMat);
water.rotation.x = -Math.PI / 2;
scene.add(water);

// Sand
const sandGeo = new THREE.PlaneGeometry(40, 40);
const sandMat = new THREE.MeshStandardMaterial({ color: 0x5c4a3a });
const sand = new THREE.Mesh(sandGeo, sandMat);
sand.rotation.x = -Math.PI / 2;
sand.position.y = -3;
scene.add(sand);

// Plants
for(let i=0;i<20;i++){
  const h = Math.random()*2+1;
  const plant = new THREE.Mesh(new THREE.ConeGeometry(0.2, h, 4), new THREE.MeshStandardMaterial({color: 0x00aa55}));
  plant.position.set(Math.random()*16-8, -3 + h/2, Math.random()*16-8);
  plant.rotation.x = -Math.PI/2;
  scene.add(plant);
}

Fish class:
class Fish {
  constructor() {
    this.group = new THREE.Group();
    // body
    const body = new THREE.Mesh(new THREE.SphereGeometry(0.35, 8, 6), new THREE.MeshStandardMaterial({color: this.randomColor()}));
    body.scale.set(1, 0.5, 0.8);
    this.group.add(body);
    // tail
    const tail = new THREE.Mesh(new THREE.ConeGeometry(0.2, 0.5, 4), new THREE.MeshStandardMaterial({color: this.randomColor()}));
    tail.rotation.x = Math.PI / 2;
    tail.position.x = -0.45;
    this.group.add(tail);
    // eye
    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.05, 4, 4), new THREE.MeshBasicMaterial({color:0x000000}));
    eye.position.set(0.25, 0.1, 0.25);
    this.group.add(eye);
    
    scene.add(this.group);
    this.reset();
  }
  
  randomColor() {
    const colors = [0x1e6f9e, 0x2ecc71, 0xe74c3c, 0xf1c40f, 0x9b59b6, 0xff6b6b];
    return colors[Math.floor(Math.random()*colors.length)];
  }
  
  reset() {
    this.position = new THREE.Vector3(Math.random()*12-6, Math.random()*3-1.5, Math.random()*12-6);
    this.group.position.copy(this.position);
    this.velocity = new THREE.Vector3();
    this.target = new THREE.Vector3().random();
    this.target.multiplyScalar(10).subScalar(5); // bounds
    this.target.y = Math.random()*2 - 1;
    this.speed = Math.random()*0.03 + 0.015;
    this.wanderTimer = 0;
    this.fleeTimer = 0;
    this.fleeSpeed = 0;
  }
  
  update(delta, mouseWorld) {
    const time = performance.now() * 0.002;
    // swimming wiggle
    this.group.rotation.z = Math.sin(time * 5) * 0.2;
    this.group.rotation.x = Math.cos(time * 3) * 0.1;
    
    // face movement direction
    if (this.velocity.lengthSq() > 0.0001) {
      this.group.quaternion.setFromUnitVectors(new THREE.Vector3(1,0,0), this.velocity.clone().normalize());
    }
    
    if (this.fleeTimer > 0) {
      this.fleeTimer -= delta;
      const move = this.velocity.clone().multiplyScalar(delta * 60);
      this.position.add(move);
      // clamp bounds
      this.position.x = Math.max(-9, Math.min(9, this.position.x));
      this.position.z = Math.max(-9, Math.min(9, this.position.z));
      this.position.y = Math.max(-2.5, Math.min(1.5, this.position.y));
    } else {
      // idle wandering
      this.wanderTimer += delta;
      if (this.wanderTimer > 3) {
        this.wanderTimer = 0;
        this.target.set(Math.random()*12-6, Math.random()*3-1.5, Math.random()*12-6);
        this.speed = Math.random()*0.03 + 0.015;
      }
      const dir = new THREE.Vector3().subVectors(this.target, this.position);
      const dist = dir.length();
      if (dist < 0.3) {
        this.wanderTimer = 3; // force new target
      } else {
        dir.normalize();
        this.velocity.copy(dir).multiplyScalar(this.speed);
        this.position.add(this.velocity.clone().multiplyScalar(delta * 60));
        // float up/down
        this.position.y += Math.sin(time) * 0.005;
      }
      this.position.x = Math.max(-9, Math.min(9, this.position.x));
      this.position.z = Math.max(-9, Math.min(9, this.position.z));
      this.position.y = Math.max(-2.5, Math.min(1.5, this.position.y));
    }
    
    this.group.position.copy(this.position);
    
    // check mouse proximity
    if (isStarted && mouseWorld) {
      const d = this.position.distanceTo(mouseWorld);
      if (d < 2.5 && this.fleeTimer <= 0) {
        this.fleeTimer = 60; // frames approx 1 sec at 60fps, but delta based? Let's use seconds.
        this.fleeTimer = 1.5; // seconds
        const fleeDir = new THREE.Vector3().subVectors(this.position, mouseWorld).normalize();
        fleeDir.y = 0; // keep horizontal mostly
        this.velocity.copy(fleeDir).multiplyScalar(0.08);
      }
    }
  }
}

Wait, delta in update: animation loop usually passes delta in seconds. We can just use a clock.

Mouse world:
function updateMouseWorld() {
  raycaster.setFromCamera(mouse, camera);
  const target = new THREE.Vector3();
  if (raycaster.ray.intersectPlane(plane, target)) {
    return target;
  }
  return null;
}

But plane is y=0. Camera looks at scene. If camera is above, ray intersects y=0 fine. We can reuse a vector.

Animation loop:
const clock = new THREE.Clock();
function animate() {
  requestAnimationFrame(animate);
  const delta = clock.getDelta();
  
  if (isStarted) {
    controls.autoRotate = false;
    const mouseWorld = getMouseWorld();
    fish.forEach(f => f.update(delta, mouseWorld));
  } else {
    controls.autoRotate = true;
    // demo mode: fish swim, no mouse interaction, move a fake mouse around slowly?
    const t = performance.now() * 0.001;
    const fakeMouse = new THREE.Vector3(Math.sin(t)*4, 0, Math.cos(t)*4);
    fish.forEach(f => f.update(delta, fakeMouse)); // but this would make them flee from fake mouse all the time? Maybe in demo mode don't check proximity. Let's add a demo parameter: flee=false.
  }
  
  controls.update();
  renderer.render(scene, camera);
}

Better: Fish.update(delta, mouseWorld, isDemo). If demo, ignore fleeing, just wander.

Start:
overlay.addEventListener('click', start);
window.addEventListener('keydown', start); // once

function start() {
  if (isStarted) return;
  isStarted = true;
  overlay.classList.add('hidden');
  controls.autoRotate = false;
}

Resize handler.

Mouse move:
window.addEventListener('mousemove', (e) => {
  mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
});
window.addEventListener('touchmove', (e) => {
  if(e.touches.length>0){
    mouse.x = (e.touches[0].clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(e.touches[0].clientY / window.innerHeight) * 2 + 1;
  }
}, {passive:false});

Wait, if we don't call preventDefault on touchmove, page may scroll. But body overflow hidden so maybe fine. Still add touchstart? Not necessary.

One issue: On first paint before start, the overlay covers everything. That's okay per instructions ("behind a Click or press a key to start message"). The screenshot at 3 seconds might show overlay if no interaction. But the instructions say "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." So the screenshot at 3s will likely show the overlay, which is fine because it's required. But they also said "Something meaningful must be visible on first paint". The overlay is meaningful. The canvas behind is animating though hidden by overlay? Actually overlay covers it. Could make overlay semi-transparent so fish are visible behind. Let's set background: rgba(0,20,40,0.6); so you can see the aquarium moving behind. Good idea.

Also the mouse interaction: fish swim away when mouse near them. Need to ensure fish actually respond. Let's test mentally: raycaster from camera through mouse to y=0 plane. If camera at (10,5,10) looking at origin, mouse at center of screen, ray hits plane near (0,0,0)? Actually camera position (10,5,10), center of screen points to target (0,0,0) roughly. Plane y=0 at origin is between camera and origin? Camera y=5, target y=0, so ray goes down and intersects y=0 somewhere between camera and target. Yes.

Distance threshold: 2.5 units. Fish size ~0.5. Good.

Flee: velocity away, speed 0.08 per frame. Then after 1.5 seconds, resume wandering.

Bounds: fish clamped to -9..9 in x,z and -2.5..1.5 in y. Sand at -3, water surface at 0. Good.

Colors: body material color random. Maybe add some variation.

OrbitControls import path: 'three/addons/controls/OrbitControls.js' should work with the import map.

Edge cases: Need to handle when camera moves such that ray is parallel to plane? y=0 plane normal (0,1,0). If camera looks straight down/up, still intersects unless ray direction y=0. With OrbitControls, unlikely. Add a fallback: if no intersection, use camera position projected? Not needed.

Let's write full code now.

Potential console errors: Make sure no use of localStorage etc. No external images.

Check import map: exactly as provided.

HTML structure:
<!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>...</style>
</head>
<body>
<div id="overlay">...</div>
<script type="importmap">...</script>
<script type="module">...</script>
</body>
</html>

CSS details for responsive:
body { margin:0; overflow:hidden; background:#000; }
canvas { position:absolute; top:0; left:0; width:100%; height:100%; display:block; }
#overlay { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; background:rgba(0,18,35,0.65); color:#e0f7fa; font-family:system-ui, -apple-system, sans-serif; text-align:center; padding:20px; z-index:10; cursor:pointer; user-select:none; }
#overlay.hidden { display:none; }
#overlay h1 { margin:0 0 8px; font-size:clamp(1.5rem, 5vw, 2.5rem); letter-spacing:2px; text-transform:uppercase; color:#4fc3f7; }
#overlay p { margin:4px 0; font-size:clamp(0.9rem, 2vw, 1.1rem); opacity:0.9; }
#overlay p:last-child { margin-top:12px; font-size:0.9rem; opacity:0.7; }

JS module code:

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// globals
let scene, camera, renderer, controls, raycaster, mouse, plane;
let fish = [];
let isStarted = false;
const FISH_COUNT = 12;

const mouseVec = new THREE.Vector2(-1000, -1000);
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
const raycaster = new THREE.Raycaster();
const intersection = new THREE.Vector3();

function getMouseWorld() {
  raycaster.setFromCamera(mouseVec, camera);
  return raycaster.ray.intersectPlane(plane, intersection) ? intersection : null;
}

class Fish { ... }

function init() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x00121f);
  scene.fog = new THREE.Fog(0x00121f, 6, 28);

  camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 100);
  camera.position.set(12, 6, 12);

  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  document.body.appendChild(renderer.domElement);

  controls = new OrbitControls(camera, renderer.domElement);
  controls.target.set(0, 0, 0);
  controls.enableDamping = true;
  controls.autoRotate = true;
  controls.autoRotateSpeed = 0.8;
  controls.minDistance = 5;
  controls.maxDistance = 40;

  // lights
  const ambient = new THREE.AmbientLight(0xffffff, 0.6);
  scene.add(ambient);
  const dir = new THREE.DirectionalLight(0xffffff, 1.5);
  dir.position.set(10, 15, 10);
  scene.add(dir);

  // environment
  // surface water
  const surf = new THREE.Mesh(
    new THREE.PlaneGeometry(40, 40),
    new THREE.MeshStandardMaterial({ color: 0x002233, transparent: true, opacity: 0.5, side: THREE.DoubleSide })
  );
  surf.rotation.x = -Math.PI / 2;
  scene.add(surf);

  // sand
  const sand = new THREE.Mesh(
    new THREE.PlaneGeometry(40, 40),
    new THREE.MeshStandardMaterial({ color: 0x6b5b45 })
  );
  sand.rotation.x = -Math.PI / 2;
  sand.position.y = -3;
  scene.add(sand);

  // plants
  for (let i = 0; i < 16; i++) {
    const h = 0.8 + Math.random() * 1.8;
    const plant = new THREE.Mesh(
      new THREE.ConeGeometry(0.18, h, 5),
      new THREE.MeshStandardMaterial({ color: 0x007a4d })
    );
    plant.position.set(Math.random() * 14 - 7, -3 + h / 2, Math.random() * 14 - 7);
    plant.rotation.x = -Math.PI / 2;
    scene.add(plant);
  }

  // fish
  for (let i = 0; i < FISH_COUNT; i++) fish.push(new Fish());

  // events
  window.addEventListener('resize', onResize);
  window.addEventListener('mousemove', onMouseMove);
  window.addEventListener('touchmove', onTouchMove, { passive: false });
  document.getElementById('overlay').addEventListener('click', startExperience);
  window.addEventListener('keydown', startExperience);

  animate();
}

class Fish {
  constructor() {
    this.group = new THREE.Group();
    const hue = Math.random() * 0.15 + 0.55; // blue/cyan range
    const bodyMat = new THREE.MeshStandardMaterial({ color: new THREE.Color().setHSL(hue, 0.8, 0.5) });
    const body = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 6), bodyMat);
    body.scale.set(1, 0.55, 0.85);
    this.group.add(body);

    const tailMat = new THREE.MeshStandardMaterial({ color: bodyMat.color });
    const tail = new THREE.Mesh(new THREE.ConeGeometry(0.15, 0.45, 4), tailMat);
    tail.rotation.x = Math.PI / 2;
    tail.position.x = -0.42;
    this.group.add(tail);

    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.04, 4, 4), new THREE.MeshBasicMaterial({ color: 0x000000 }));
    eye.position.set(0.22, 0.08, 0.22);
    this.group.add(eye);

    scene.add(this.group);
    this.reset();
  }

  reset() {
    this.position = new THREE.Vector3(
      (Math.random() - 0.5) * 14,
      Math.random() * 2 - 1,
      (Math.random() - 0.5) * 14
    );
    this.group.position.copy(this.position);
    this.target = this.position.clone();
    this.speed = 0.02 + Math.random() * 0.025;
    this.wanderTime = 0;
    this.wanderInterval = 2 + Math.random() * 3;
    this.fleeTime = 0;
    this.velocity = new THREE.Vector3();
  }

  update(delta, mouseWorld, demo) {
    const t = performance.now() * 0.003;

    // body wiggle
    this.group.rotation.z = Math.sin(t * 6) * 0.15;
    this.group.rotation.x = Math.cos(t * 4) * 0.08;

    // orientation towards velocity
    if (this.velocity.lengthSq() > 0.00001) {
      this.group.quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), this.velocity.clone().normalize());
    }

    if (this.fleeTime > 0 && !demo) {
      this.fleeTime -= delta;
      this.position.add(this.velocity.clone().multiplyScalar(delta * 60));
    } else {
      this.fleeTime = 0;
      this.wanderTime += delta;
      if (this.wanderTime > this.wanderInterval) {
        this.wanderTime = 0;
        this.wanderInterval = 2 + Math.random() * 3;
        this.target.set(
          (Math.random() - 0.5) * 14,
          Math.random() * 2 - 1,
          (Math.random() - 0.5) * 14
        );
        this.speed = 0.02 + Math.random() * 0.025;
      }
      const dir = new THREE.Vector3().subVectors(this.target, this.position);
      const d = dir.length();
      if (d < 0.25) {
        this.wanderTime = this.wanderInterval; // trigger new target next frame
      } else {
        dir.normalize();
        this.velocity.copy(dir).multiplyScalar(this.speed);
        this.position.add(this.velocity.clone().multiplyScalar(delta * 60));
        this.position.y += Math.sin(t) * 0.004;
      }
    }

    // clamp inside tank
    this.position.x = Math.max(-8.5, Math.min(8.5, this.position.x));
    this.position.z = Math.max(-8.5, Math.min(8.5, this.position.z));
    this.position.y = Math.max(-2.4, Math.min(1.2, this.position.y));

    this.group.position.copy(this.position);

    // flee from mouse
    if (!demo && mouseWorld) {
      const dist = this.position.distanceTo(mouseWorld);
      if (dist < 2.2 && this.fleeTime <= 0) {
        this.fleeTime = 1.2;
        this.velocity = new THREE.Vector3()
          .subVectors(this.position, mouseWorld)
          .normalize()
          .multiplyScalar(0.09);
        this.velocity.y = Math.max(this.velocity.y, 0.02); // slight upward panic
      }
    }
  }
}

function startExperience() {
  if (isStarted) return;
  isStarted = true;
  const overlay = document.getElementById('overlay');
  if (overlay) overlay.classList.add('hidden');
  controls.autoRotate = false;
}

function onMouseMove(e) {
  mouseVec.x = (e.clientX / window.innerWidth) * 2 - 1;
  mouseVec.y = -(e.clientY / window.innerHeight) * 2 + 1;
}

function onTouchMove(e) {
  e.preventDefault();
  if (e.touches.length) {
    mouseVec.x = (e.touches[0].clientX / window.innerWidth) * 2 - 1;
    mouseVec.y = -(e.touches[0].clientY / window.innerHeight) * 2 + 1;
  }
}

function onResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  requestAnimationFrame(animate);
  const delta = Math.min(clock.getDelta(), 0.1);

  const demo = !isStarted;
  const mouseWorld = demo ? null : getMouseWorld();

  // In demo mode, move an invisible target around so fish have something to react to? Not needed.
  fish.forEach(f => f.update(delta, mouseWorld, demo));

  controls.update();
  renderer.render(scene, camera);
}

const clock = new THREE.Clock();
init();

Wait, `const clock = new THREE.Clock();` must be before animate or at top. Let's place at top.

Also `plane` variable name conflicts with global? I used `const plane = ...` at top and inside init? Let's just define globally once.

Check for any errors:
- `new THREE.Color().setHSL(...)` returns color, okay.
- `setHSL` parameters: h, s, l. hue 0.55-0.7 is cyan/blue. Good.
- `Math.min(window.devicePixelRatio, 2)` fine.
- `renderer.setPixelRatio` okay.

Overlay click and keydown: `startExperience` called on keydown globally. That might be triggered multiple times but guarded by `isStarted`.

The instructions say "Click or press a key to start". We have that.

On first paint: overlay visible, behind it aquarium animating (fish swimming). Since overlay background is semi-transparent, you see movement. Good.

Responsive: CSS handles. Canvas absolute fill. No horizontal scrollbar because body overflow hidden.

No external fonts. Use system-ui.

Check import map requirement: "Load Three.js only through this import map". We include exactly that script tag. Good.

No network requests besides CDN. Good.

No localStorage etc. Good.

I think this is solid. Let's produce final answer exactly one fenced code block with ```html.

Wait, user said: Output rules: Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg ...). No text before or after the code block.

So I must output only the code block.

Double-check if any syntax issues.

In `Fish.update`, `this.velocity` is a Vector3. When fleeing, after fleeTime expires, velocity remains but is overwritten in idle branch. Good.

`getMouseWorld` uses global `mouseVec`. If mouse hasn't moved, it's (-1000,-1000), raycaster will shoot from camera through far away NDC. That might intersect plane somewhere far away but could be behind camera? NDC x=-1000 is outside [-1,1], raycaster still works but direction might be weird. Better initialize mouseVec to center (0,0) so before any movement, the "mouse" is at center of screen. Or clamp. Let's set mouseVec = new THREE.Vector2(0,0); initially. Then if no movement, target is center of screen. Fish near center might flee initially? Distance from fish to center might be <2.2 for some fish, causing them to flee at start. That's fine, it's part of demo. But maybe we want mouse to only affect after actual movement. We can add `let mouseActive = false;` set true on first move. Then `mouseWorld = mouseActive ? getMouseWorld() : null;`. Simpler: initialize mouseVec to (0,0) and accept it. It's okay.

Actually if mouse is at center NDC (0,0), ray goes through center of screen. Intersection with y=0 could be near center. Some fish might be near there. They will flee at start for a second. That's acceptable and looks like they react to something.

I'll initialize mouseVec to (0,0) for safety.

Let's refine `startExperience` to also remove keydown listener? Not necessary.

Also ensure we don't call `e.preventDefault()` in touchmove in a way that blocks scrolling on the page body? Since body overflow hidden, okay. But some browsers may complain about passive listener. We set {passive:false}.

One more thing: The overlay should probably not block clicks on canvas after start because it's hidden (display:none). Good.

Final code. I'll write it cleanly.

Wait, `const plane` at top: `const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);` yes.

`intersection` vector reuse.

Also, `controls.autoRotate = false;` after start. Before start, autoRotate true so camera pans around. Nice demo.

Let's ensure no unused variables cause lint errors (not relevant but okay).

I'll output now.