---
name: neighbourhood-diorama
description: >
  A town and harbour diorama in one Three.js file, built as a landing-page
  hero. A delivery truck, a plane, a ship and a crane. Use when changing
  the route, the camera or the port. The reference file is diorama.html
  beside this skill. If they disagree, the HTML wins.
---

# Town and harbour

One HTML file. The logo is a data-URI PNG. The corrugation is a canvas texture. No other files. Three.js 0.186.1. `three` and `three/webgpu` both point at `three.webgpu.js`. `three/tsl` at `three.tsl.js`. No addon import.

`WebGPURenderer` only. `antialias` true. Set `renderer._getFallback = null` before `init()`. If `navigator.gpu` is missing, or the backend is not WebGPU after `init()`, stop. No WebGL fallback and no `forceWebGL`.

Do not set `outputEncoding`. `LOGO_TEX.colorSpace` and the corrugation `CanvasTexture` are `SRGBColorSpace`.

The page fills the iframe. `html`, `body` and `.hero` are `height: 100%`. Do not put the hero back on `100svh`.

Open it through a server. `file://` will not load the ES modules. `php -S localhost:8000` or `npx serve`.

## Camera

Phi is measured from +Y. `x = sin(phi) * sin(theta)`, `y = cos(phi)`, `z = sin(phi) * cos(theta)`. Drag right does `theta -= dx`. Do not swap sin and cos on x and z.

Auto spin increases theta (`theta += dt * 0.055`). Idle above 5 seconds turns auto back on. Idle above 9 seconds eases the target home. If you flip the drag, flip the spin too.

Zoom is the slider. The wheel stays with the page. `ZNEAR` is 0.42, `ZFAR` is 1.85. `zoom = ZNEAR * pow(ZFAR / ZNEAR, (100 - v) / 100)`. Plus is closest. The wheel stays with the page.

`frame()` sets `CAM.base = 84 * min(1.62, max(1, 1.55 / (w / h)))`.

## Route

The truck takes the outer ring, left-hand traffic. Cars take the inner ring the other way. Stops are the pads: Ashgrove Tower, The Corner Shop, Depot 4, Airside Cargo, Larkfield Flats.

The loop idles when the host is off screen or the tab is hidden. Use `setAnimationLoop` after `init()`. Do not start a `requestAnimationFrame` loop before the backend check.
