# 听雨庭 · The Listening Courtyard

A browser-explorable Chinese courtyard built from Blender-generated GLB assets and rendered with Three.js.

## Run the finished browser project

```bash
cd works/web/listening-courtyard
python3 -m http.server 4174
# open http://localhost:4174
```

A local HTTP server is required because ES modules and GLB files are loaded by the browser. Do not open `index.html` with `file://`.

## Rebuild the Blender assets

Requires Blender 4.3 or newer with the glTF 2.0 exporter enabled.

```bash
cd works/web/listening-courtyard
BLENDER_BIN=/path/to/blender python3 blender/generate.py
```

The script runs Blender in background mode, executes `blender/build.py`, and verifies the GLB header and mesh payload after export. It creates:

- `assets/courtyard.glb` - walls, rooms, furniture, moon gate, pond banks, rocks, pines, lanterns
- `assets/roofs.glb` - curved tiled roofs and roof ridges
- `assets/wildlife.glb` - named koi, frogs, dragonflies, cat, and sparrows for browser animation
- `assets/courtyard.blend` - editable Blender source scene
- `assets/build-report.json` - build inventory and file sizes

The original scene is generated procedurally and uses a fixed seed (`23`) for repeatable geometry. Blender is not required to run the published browser experience once the GLB files exist.

## Controls

- Drag: orbit view
- Mouse wheel / pinch: zoom
- Right mouse drag: pan
- Click the six named views: the camera smoothly transfers between courtyard, gate, pond, living room, tea room, and bedroom
- Drag: orbit the current view
- `入夜`: day/night lighting, lantern glow, and fireflies
- `揭瓦`: toggle the separate roof GLB to reveal room interiors
- `生息`: pause or resume water and wildlife motion
- Click pond: feed koi
- Click cat: call the courtyard cat

## Blender Module Menu

Use the boxes icon at the upper right to open the side modal. The menu includes the architecture GLB, roof GLB, an arranged wildlife collection, and five individual wildlife nodes. Previews have their own scene, camera, lighting, orbit controls, zoom limits, and automatic framing. Returning preserves the courtyard camera, navigation mode, day/night setting, and roof state.

To add a new module, put its exported GLB under `assets/` and add one entry to `MODULES` in `src/modules.js`:

```js
{ id: 'pavilion', name: '新凉亭', category: '建筑', icon: 'landmark', url: 'assets/pavilion.glb', aliases: '亭子 pavilion' }
```

No menu HTML or preview-handler changes are required. A unique `id`, display `name`, and `url` are required. Optional fields:

- `category`: short category label, also searchable.
- `icon`: Lucide icon name; defaults to `box`.
- `aliases`: additional search keywords.
- `node`: preview one named object from a shared GLB instead of the entire scene.
- `layout: 'row'` with `nodes: ['NodeA', 'NodeB']`: spread overlapping templates into a comparison row without modifying the source GLB.

GLBs are loaded lazily and cached by URL. Failed loads can be retried. Full-scene GLB animation clips play through Three.js AnimationMixer; named-node and comparison previews show static model geometry. Existing courtyard procedural wildlife motion is not copied into the isolated model previews.

## 2px Pixel Style

The toolbar's `像素` button is a runtime presentation switch. It keeps the original Blender GLB geometry and interaction state, but renders the Three.js canvas at 50% width/height and enlarges it with crisp pixel sampling. This gives an approximate 2px pixel-art presentation on a normal display: the smallest visible color blocks are roughly two screen pixels wide while the scene remains orbitable, zoomable, and inspectable. Click `原画` to restore the normal render scale. The switch also remains active while opening an isolated Blender module.

This is intentionally a presentation layer rather than a destructive model conversion: the same source geometry can be viewed in both styles, and new modules automatically inherit the switch. A later stricter pixel-art pass can replace the render path with a palette quantizer or Blender pixel-material variant without changing the module registry.

### Menu Regression Test

With the HTTP server running and Playwright installed:

```bash
node tests/modules.mjs
# Optional: TEST_URL=http://localhost:4174 CHROME_BIN=/path/to/chrome node tests/modules.mjs
```

The test checks all eight entries, nonblank WebGL pixel samples, search, Escape behavior, original-scene state restoration, mobile modal bounds, orbit rotation, and zoom.

## Files

- `index.html` - semantic UI shell and controls
- `style.css` - unobstructed editorial-style interface and responsive touch layout
- `src/main.js` - Three.js renderer, GLB loading, water shader, lighting, and interaction
- `src/navigation.js` - orbit controls and camera-transfer presets
- `src/wildlife.js` - subtle procedural motion for koi, frogs, dragonflies, cat, sparrows, and fireflies
- `src/modules.js` - single registry for independently previewable Blender GLB modules; add a new entry here for future models
- `src/module-viewer.js` - modal module menu, actual GLB thumbnails, filtered search, isolated object preview, fit view, and return flow
- `pixel-style.css` - 2px pixel presentation styling
- `blender/build.py` - Blender geometry generator and GLB exporter
- `blender/generate.py` - headless build wrapper and export verification
