Models 44

Claude Haiku 5.5

by AnthropicReasoningClean
Open in new window

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

How it went

Total time
43.1 s
Cost
$0.0055
Output tokens
10,949
Spent thinking
2,735 tokens
Speed
268.6 tok/s
Console errors
0
First token after
2.3 s
First visible text after
17.9 s
Prompt tokens
649
Code size
15.2 KB
Finish reason
stop
Served by
Claude Platform on AWS
Model id
anthropic/claude-haiku-5.5
Reasoning sent
{"effort":"low"}
Max output
32,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 · 464 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 – Fish Flee From Your Cursor</title>
<style>
  :root { color-scheme: dark; }
  html, body {
    margin: 0; height: 100%; overflow: hidden;
    background: linear-gradient(#5fd3e6 0%, #1a7fa0 35%, #0a4a6e 70%, #03243a 100%);
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    color: #e9fbff;
  }
  #c { position: fixed; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; }
  #hud {
    position: fixed; top: 16px; left: 18px; pointer-events: none;
    text-shadow: 0 1px 6px rgba(0,0,0,.35);
  }
  #hud h1 { margin: 0; font-size: 20px; font-weight: 650; letter-spacing: .3px; }
  #hud p { margin: 4px 0 0; font-size: 13px; opacity: .85; }
  #start {
    position: fixed; left: 50%; bottom: 36px; transform: translateX(-50%);
    background: rgba(3,30,50,.55); border: 1px solid rgba(255,255,255,.25);
    padding: 10px 18px; border-radius: 999px; font-size: 14px;
    backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    transition: opacity .6s ease; pointer-events: none; white-space: nowrap;
    animation: pulse 2.4s ease-in-out infinite;
  }
  #start.hidden { opacity: 0; }
  @keyframes pulse { 0%,100% { opacity: .95; } 50% { opacity: .55; } }
  @media (max-width: 480px) {
    #hud h1 { font-size: 17px; }
    #hud p { font-size: 12px; }
    #start { font-size: 13px; bottom: 22px; }
  }
</style>
</head>
<body>
<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>

<canvas id="c"></canvas>
<div id="hud">
  <h1>Aquarium</h1>
  <p>Move your cursor near the fish and they swim away.</p>
</div>
<div id="start">Click or press a key to start</div>

<script type="module">
import * as THREE from 'three';

const canvas = document.getElementById('c');
const startMsg = document.getElementById('start');

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight, false);
renderer.outputColorSpace = THREE.SRGBColorSpace;

const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x1a7fa0, 0.035);

const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 200);
camera.position.set(0, 1.5, 30);
camera.lookAt(0, 0, 0);

// Lights
scene.add(new THREE.AmbientLight(0xbfefff, 0.9));
const sun = new THREE.DirectionalLight(0xffffff, 1.4);
sun.position.set(-6, 14, 8);
scene.add(sun);
const fill = new THREE.DirectionalLight(0x66ccff, 0.5);
fill.position.set(10, -4, -6);
scene.add(fill);

// Tank bounds
const LIM = { x: 13, y: 6, z: 4.5 };
const FLOOR_Y = -7.2;
const SURFACE_Y = 7.5;

// ---------- Tank: surface, floor, glass-ish back wall ----------
const surface = new THREE.Mesh(
  new THREE.PlaneGeometry(60, 60),
  new THREE.MeshBasicMaterial({ color: 0x9ff3ff, transparent: true, opacity: 0.12, side: THREE.DoubleSide, depthWrite: false })
);
surface.rotation.x = -Math.PI / 2;
surface.position.y = SURFACE_Y;
scene.add(surface);

// Sand texture (procedural)
function makeSandTexture() {
  const c = document.createElement('canvas');
  c.width = c.height = 256;
  const g = c.getContext('2d');
  g.fillStyle = '#d9b77a';
  g.fillRect(0, 0, 256, 256);
  for (let i = 0; i < 3000; i++) {
    const v = 150 + Math.random() * 90;
    g.fillStyle = `rgba(${v},${v * 0.82},${v * 0.55},${0.25 + Math.random() * 0.4})`;
    const s = Math.random() * 3 + 1;
    g.fillRect(Math.random() * 256, Math.random() * 256, s, s);
  }
  const t = new THREE.CanvasTexture(c);
  t.wrapS = t.wrapT = THREE.RepeatWrapping;
  t.repeat.set(6, 6);
  t.colorSpace = THREE.SRGBColorSpace;
  return t;
}
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(60, 40),
  new THREE.MeshStandardMaterial({ map: makeSandTexture(), roughness: 1 })
);
floor.rotation.x = -Math.PI / 2;
floor.position.y = FLOOR_Y;
scene.add(floor);

// Dune bumps on floor
const dunes = new THREE.Group();
for (let i = 0; i < 12; i++) {
  const d = new THREE.Mesh(
    new THREE.SphereGeometry(1 + Math.random() * 2, 16, 8),
    new THREE.MeshStandardMaterial({ color: 0xcaa86a, roughness: 1 })
  );
  d.scale.y = 0.25;
  d.position.set((Math.random() - 0.5) * 34, FLOOR_Y - 0.1, -6 - Math.random() * 6);
  dunes.add(d);
}
scene.add(dunes);

// Light rays from the surface
const rayMat = new THREE.MeshBasicMaterial({
  color: 0xfff6c8, transparent: true, opacity: 0.07,
  blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide
});
const rays = [];
for (let i = 0; i < 7; i++) {
  const r = new THREE.Mesh(new THREE.PlaneGeometry(2.2 + Math.random() * 2, 22), rayMat);
  r.position.set(-12 + i * 4 + Math.random() * 2, 0, -5 - Math.random() * 4);
  r.rotation.z = -0.22;
  scene.add(r);
  rays.push(r);
}

// ---------- Seaweed (chained segments that sway) ----------
const weedMat = (c) => new THREE.MeshStandardMaterial({ color: c, roughness: 0.6 });
const weedColors = [0x2e8b57, 0x3aa566, 0x1f7a4d, 0x4fbf7a];
const seaweeds = [];
for (let i = 0; i < 16; i++) {
  const x = -13 + Math.random() * 26;
  const z = -4 - Math.random() * 4;
  const root = new THREE.Group();
  root.position.set(x, FLOOR_Y, z);
  scene.add(root);
  let parent = root;
  const segs = [];
  const mat = weedMat(weedColors[i % weedColors.length]);
  const segCount = 6 + Math.floor(Math.random() * 3);
  const segH = 1.1;
  for (let s = 0; s < segCount; s++) {
    const seg = new THREE.Group();
    seg.position.y = s === 0 ? 0 : segH;
    const mesh = new THREE.Mesh(new THREE.CapsuleGeometry(0.16 - s * 0.012, segH * 0.9, 4, 8), mat);
    mesh.position.y = segH * 0.5;
    seg.add(mesh);
    parent.add(seg);
    parent = seg;
    segs.push(seg);
  }
  seaweeds.push({ segs, phase: Math.random() * Math.PI * 2 });
}

// Rocks
for (let i = 0; i < 8; i++) {
  const rock = new THREE.Mesh(
    new THREE.DodecahedronGeometry(0.6 + Math.random() * 1.1, 0),
    new THREE.MeshStandardMaterial({ color: 0x5b6d70, roughness: 0.95, flatShading: true })
  );
  rock.position.set(-12 + Math.random() * 24, FLOOR_Y + 0.3, -3 - Math.random() * 5);
  rock.rotation.set(Math.random(), Math.random() * 3, Math.random());
  rock.scale.y = 0.7;
  scene.add(rock);
}

// ---------- Fish ----------
const fishPalette = [0xff7a2e, 0xffc23d, 0x3fb8ff, 0xff4d6d, 0x9b6bff, 0x37d6b0, 0xf5f5f5, 0xff9ec7];

function makeFish(color, scale) {
  const g = new THREE.Group();
  const bodyMat = new THREE.MeshStandardMaterial({ color, roughness: 0.4, metalness: 0.05 });
  const body = new THREE.Mesh(new THREE.SphereGeometry(1, 20, 14), bodyMat);
  body.scale.set(0.55, 0.62, 1.5);
  g.add(body);

  // Belly lighter stripe
  const belly = new THREE.Mesh(
    new THREE.SphereGeometry(1, 16, 10),
    new THREE.MeshStandardMaterial({ color: 0xffffff, transparent: true, opacity: 0.35, roughness: 0.5 })
  );
  belly.scale.set(0.4, 0.3, 1.2);
  belly.position.y = -0.22;
  g.add(belly);

  // Tail pivot at back of body
  const tailPivot = new THREE.Group();
  tailPivot.position.z = -1.35;
  const tailGeo = new THREE.ConeGeometry(0.7, 1.1, 4);
  tailGeo.rotateX(Math.PI / 2);   // apex towards body (+Z), base at back
  tailGeo.scale(0.2, 1, 1);
  const tail = new THREE.Mesh(tailGeo, bodyMat);
  tail.position.z = -0.5;
  tailPivot.add(tail);
  g.add(tailPivot);

  // Dorsal fin
  const dorsalGeo = new THREE.ConeGeometry(0.4, 0.9, 3);
  dorsalGeo.scale(0.15, 1, 1);
  const dorsal = new THREE.Mesh(dorsalGeo, bodyMat);
  dorsal.position.set(0, 0.8, -0.1);
  g.add(dorsal);

  // Side fins
  const finGeo = new THREE.ConeGeometry(0.3, 0.8, 3);
  finGeo.rotateZ(Math.PI / 2);
  finGeo.scale(1, 1, 0.3);
  [-1, 1].forEach((side) => {
    const fin = new THREE.Mesh(finGeo, bodyMat);
    fin.position.set(side * 0.55, -0.15, 0.2);
    fin.rotation.y = side * 0.5;
    g.add(fin);
  });

  // Eyes
  [-1, 1].forEach((side) => {
    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.16, 10, 8), new THREE.MeshBasicMaterial({ color: 0xffffff }));
    eye.position.set(side * 0.3, 0.14, 1.05);
    g.add(eye);
    const pupil = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 6), new THREE.MeshBasicMaterial({ color: 0x111111 }));
    pupil.position.set(side * 0.36, 0.14, 1.16);
    g.add(pupil);
  });

  g.scale.setScalar(scale);
  g.userData.tail = tailPivot;
  return g;
}

const fishes = [];
const FISH_COUNT = 16;
for (let i = 0; i < FISH_COUNT; i++) {
  const scale = 0.7 + Math.random() * 0.6;
  const mesh = makeFish(fishPalette[i % fishPalette.length], scale);
  const pos = new THREE.Vector3(
    (Math.random() - 0.5) * LIM.x * 2,
    (Math.random() - 0.5) * LIM.y * 2,
    (Math.random() - 0.5) * LIM.z * 2
  );
  const vel = new THREE.Vector3((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 0.6, (Math.random() - 0.5) * 0.6);
  mesh.position.copy(pos);
  scene.add(mesh);
  fishes.push({
    mesh, pos, vel,
    target: new THREE.Vector3(),
    retarget: Math.random() * 3,
    baseSpeed: 1.6 + Math.random() * 0.8,
    flee: 0,
    phase: Math.random() * Math.PI * 2,
    wagSpeed: 6 + Math.random() * 3
  });
  pickTarget(fishes[i]);
}

function pickTarget(f) {
  f.target.set(
    (Math.random() - 0.5) * LIM.x * 2,
    (Math.random() - 0.5) * LIM.y * 1.6,
    (Math.random() - 0.5) * LIM.z * 2
  );
  f.retarget = 2 + Math.random() * 4;
}

// ---------- Bubbles ----------
const bubbleGeo = new THREE.SphereGeometry(1, 8, 6);
const bubbleMat = new THREE.MeshStandardMaterial({
  color: 0xffffff, transparent: true, opacity: 0.35, roughness: 0.1, metalness: 0.0
});
const bubbles = [];
for (let i = 0; i < 40; i++) {
  const b = new THREE.Mesh(bubbleGeo, bubbleMat);
  resetBubble(b, true);
  scene.add(b);
  bubbles.push(b);
}
function resetBubble(b, initial) {
  const s = 0.05 + Math.random() * 0.12;
  b.scale.setScalar(s);
  b.position.set(
    (Math.random() - 0.5) * LIM.x * 2,
    initial ? FLOOR_Y + Math.random() * (SURFACE_Y - FLOOR_Y) : FLOOR_Y + 0.2,
    -2 - Math.random() * 4
  );
  b.userData.speed = 0.6 + Math.random() * 0.9;
  b.userData.wobble = Math.random() * Math.PI * 2;
}

// ---------- Cursor indicator ----------
const cursorMarker = new THREE.Mesh(
  new THREE.SphereGeometry(0.35, 16, 12),
  new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.0, depthWrite: false })
);
scene.add(cursorMarker);

// ---------- Pointer handling ----------
const raycaster = new THREE.Raycaster();
const ndc = new THREE.Vector2();
const zPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
const mouseWorld = new THREE.Vector3(9999, 9999, 0);
const hit = new THREE.Vector3();
let userActive = false;
let lastPointerTime = -100;
let started = false;

function setPointer(clientX, clientY) {
  ndc.x = (clientX / window.innerWidth) * 2 - 1;
  ndc.y = -(clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(ndc, camera);
  if (raycaster.ray.intersectPlane(zPlane, hit)) {
    mouseWorld.copy(hit);
  }
  lastPointerTime = clock.elapsedTime;
  userActive = true;
}

function markStarted() {
  if (!started) {
    started = true;
    startMsg.classList.add('hidden');
  }
}

canvas.addEventListener('pointermove', (e) => { setPointer(e.clientX, e.clientY); });
canvas.addEventListener('pointerdown', (e) => { setPointer(e.clientX, e.clientY); markStarted(); });
canvas.addEventListener('pointerleave', () => { userActive = false; });
window.addEventListener('keydown', () => markStarted());
window.addEventListener('click', () => markStarted());

// ---------- Resize ----------
window.addEventListener('resize', () => {
  const w = window.innerWidth, h = window.innerHeight;
  renderer.setSize(w, h, false);
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
});

// ---------- Simulation ----------
const clock = new THREE.Clock();
const tmp = new THREE.Vector3();
const dir = new THREE.Vector3();
const FLEE_RADIUS = 7.5;

function updateDemoCursor(t) {
  // Used when no user is interacting: a virtual cursor wanders through the tank
  const x = Math.sin(t * 0.35) * 9 + Math.sin(t * 0.9) * 2;
  const y = Math.sin(t * 0.27 + 1.3) * 4.5;
  mouseWorld.set(x, y, 0);
}

function update(dt, t) {
  if (userActive && t - lastPointerTime > 6) userActive = false;
  if (!userActive) updateDemoCursor(t);

  const pointerOn = userActive || !started || true;
  cursorMarker.position.copy(mouseWorld);
  cursorMarker.material.opacity = userActive ? 0.12 : 0.05;

  for (const f of fishes) {
    // Flee from cursor
    tmp.copy(f.pos).sub(mouseWorld);
    const d = tmp.length();
    let fleeAmt = 0;
    if (d < FLEE_RADIUS && d > 0.0001) {
      fleeAmt = Math.pow(1 - d / FLEE_RADIUS, 1.5);
      dir.copy(tmp).divideScalar(d);
      f.vel.addScaledVector(dir, fleeAmt * 38 * dt);
      // Sideways swirl so they don't all flee in a line
      f.vel.x += -dir.y * fleeAmt * 6 * dt;
      f.vel.y += dir.x * fleeAmt * 6 * dt;
    }
    f.flee += (fleeAmt - f.flee) * Math.min(1, dt * 6);

    // Wander toward target
    f.retarget -= dt;
    if (f.retarget <= 0 || f.pos.distanceTo(f.target) < 1.2) pickTarget(f);
    tmp.copy(f.target).sub(f.pos).normalize().multiplyScalar(f.baseSpeed);
    f.vel.addScaledVector(tmp.sub(f.vel), Math.min(1, dt * 0.8));

    // Keep inside tank
    const margin = 1.6;
    if (f.pos.x > LIM.x - margin) f.vel.x -= dt * 14;
    if (f.pos.x < -LIM.x + margin) f.vel.x += dt * 14;
    if (f.pos.y > LIM.y - margin) f.vel.y -= dt * 14;
    if (f.pos.y < FLOOR_Y + 2 + margin * 0.5 - 2) f.vel.y += dt * 14;
    if (f.pos.z > LIM.z - margin) f.vel.z -= dt * 10;
    if (f.pos.z < -LIM.z + margin) f.vel.z += dt * 10;

    // Speed limits (faster when fleeing)
    const maxSp = f.baseSpeed + f.flee * 7;
    const sp = f.vel.length();
    if (sp > maxSp) f.vel.multiplyScalar(maxSp / sp);
    if (sp < 0.7) f.vel.setLength(0.7);

    f.pos.addScaledVector(f.vel, dt);
    f.mesh.position.copy(f.pos);

    // Orientation: nose (+Z) along velocity
    const v = f.vel;
    const yaw = Math.atan2(v.x, v.z);
    const pitch = -Math.atan2(v.y, Math.hypot(v.x, v.z));
    const roll = -Math.sin(t * 2 + f.phase) * 0.08 - f.flee * 0.3 * Math.sign(v.x || 1);
    f.mesh.rotation.set(pitch, yaw, roll, 'YXZ');

    // Tail wag (faster when fleeing)
    const wag = Math.sin(t * (f.wagSpeed + f.flee * 14) + f.phase);
    f.mesh.userData.tail.rotation.y = wag * (0.35 + f.flee * 0.3);
  }

  // Seaweed sway
  for (const w of seaweeds) {
    for (let i = 0; i < w.segs.length; i++) {
      const s = w.segs[i];
      s.rotation.z = Math.sin(t * 1.1 + w.phase + i * 0.45) * (0.05 + i * 0.03);
      s.rotation.x = Math.cos(t * 0.8 + w.phase + i * 0.3) * 0.04;
    }
  }

  // Bubbles
  for (const b of bubbles) {
    b.position.y += b.userData.speed * dt;
    b.position.x += Math.sin(t * 2 + b.userData.wobble) * 0.3 * dt;
    if (b.position.y > SURFACE_Y - 0.2) resetBubble(b, false);
  }

  // Light rays drift
  rays.forEach((r, i) => {
    r.material.opacity = 0.05 + Math.sin(t * 0.7 + i) * 0.025;
    r.position.x += Math.sin(t * 0.2 + i) * dt * 0.1;
  });

  // Slow camera sway
  camera.position.x = Math.sin(t * 0.12) * 1.2;
  camera.position.y = 1.5 + Math.sin(t * 0.17) * 0.5;
  camera.lookAt(0, 0, 0);
}

function loop() {
  const dt = Math.min(clock.getDelta(), 0.05);
  const t = clock.elapsedTime;
  update(dt, t);
  renderer.render(scene, camera);
  requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
Reasoning trace (2,735 tokens)
I'm planning a single HTML file that uses a Three.js r170 module through an import map, building a 3D aquarium with procedurally drawn fish, water, bubbles, a sand floor, seaweed, and lighting. The fish should scatter when the mouse gets close, and I'm weighing whether to show a "Click or press a key to start" overlay over a demo that's already running.

I'm sketching out the fish and the mouse interaction now: fish get a cone-and-sphere body with fins and wander toward a target, and when the pointer gets near they flee with a force that drops off with distance. I'm also planning a wandering simulated cursor for demo mode, plus rising bubbles, swaying seaweed, and a translucent water surface.

I'm settling the camera at z=30 and the tank bounds, then using a z=0 plane raycast to turn the mouse into a 3D point with a 6-unit flee radius. Fish will steer away from that point with a force that weakens with distance, and I'm planning to smooth each fish's orientation toward its velocity.

Next I'm setting the fish's facing from its velocity: yaw from the x and z components and pitch from the y component, with the body running along +Z. For the tail I'm wagging it with a sine tied to speed, and I'm building the fish from a scaled sphere body plus a cone tail attached as a child group behind it.

I'm checking the tail's orientation: rotating the cone so its apex points forward into the body, and I'm considering flattening it on the X axis so the fin lies in the vertical plane.

I'm finishing the fish's tail by flattening the cone along X so the fin sits in the vertical plane. I'm also adding a dorsal fin and small eyes with pupils. For the scene, I'm using a transparent canvas over a CSS gradient with fog for depth, a tilted translucent water surface near the top, and a speckled sand plane at the bottom.

I'm setting up the seaweed as twelve chains of eight segments, each one nested inside the last so the sway bends the whole stalk. For the bubbles, I'm using about sixty small translucent spheres that rise and reset at the bottom. For the pointer, I'm raycasting the mouse onto the z=0 plane, and I'm planning a simulated cursor that takes over in demo mode after a period without input.

I'm wrapping up the interaction details: a demo cursor that runs until the first pointer move, a small glowing marker at the mouse point, and hiding the start message after the first click or keypress. Next I'll handle resizing and start spawning the fish.

Still working through the fish update: each fish gets pushed away from the mouse point with a force that grows stronger the closer it gets, and its top speed rises while it flees.

I'm working through the fish's boundary handling next: when a fish nears a wall, I push its velocity back inward. Then I'm adding a small damping step so speed never drops below a floor. The bubbles will be about forty simple meshes, and the pointer marker will be a small transparent sphere. The lights are an ambient fill and a directional light from above, with fog for depth.

Next I'm setting the page background as a CSS gradient running from a light teal at the top to a deep blue at the bottom. I'm also adding a small HUD title in the top-left corner that reads "Aquarium — move near the fish," and then I'll write out the HTML.