<!-- SOLIDIVISM · part 62 -->
-sidewalks'; cityGroup.add(walks);

  /* -- plaza: the spawn disc at the center -- */
  const plaza = new THREE.Mesh(new THREE.CylinderGeometry(11, 11, 0.16, 40),
    new THREE.MeshStandardMaterial({ color: 0x3a3325, roughness: 0.9 }));
  plaza.position.y = 0.08; plaza.name = 'the-plaza'; cityGroup.add(plaza);
  const beacon = new THREE.Mesh(new THREE.CylinderGeometry(0.32, 0.5, 9, 10),
    new THREE.MeshStandardMaterial({ color: 0x222222, emissive: 0xd8a833, emissiveIntensity: 0.7 }));
  beacon.position.y = 4.5; beacon.name = 'the-beacon'; cityGroup.add(beacon);

  /* -- street lights: ONE InstancedMesh, emissive heads only.
   *    No light per lamp (spec): the sun + hemisphere do the work. */
  const lampSpots = [];
  for (let a = -extent + PITCH / 2; a <= extent; a += PITCH)
    for (let b = -extent + PITCH / 2; b <= extent; b += PITCH) lampSpots.push([a, b]);
  const lampGeo = new THREE.CylinderGeometry(0.09, 0.13, 5.4, 6); lampGeo.translate(0, 2.7, 0);
  const lampHead = new THREE.SphereGeometry(0.34, 8, 6); lampHead.translate(0, 5.5, 0);
  const lampMesh = new THREE.InstancedMesh(mergeGeometries([lampGeo, lampHead]),
    new THREE.MeshStandardMaterial({ color: 0x30363d, emissive: 0xffca7a, emissiveIntensity: 0.55, roughness: 0.6 }),
    lampSpots.length);
  const m4 = new THREE.Matrix4();
  lampSpots.forEach((s, i) => { m4.makeTranslation(s[0], 0, s[1]); lampMesh.setMatrixAt(i, m4); });
  lampMesh.name = 'the-streetlights'; cityGroup.add(lampMesh);

  /* -- the buildings themselves -- */
  dealLots(pages.length);
  const byColor = new Map();   // color -> geometry list (merged into ONE mesh per color)
  const winXforms = [];        // every window in the city -> one InstancedMesh
  const doorGeos = [];
  pages.forEach((page, i) => buildBuilding(page, i, byColor, winXforms, doorGeos));
  for (const [color, geos] of byColor) {
    const mesh = new THREE.Mesh(mergeGeometries(geos),
      new THREE.MeshStandardMaterial({ color, roughness: 0.85, metalness: 0.05 }));
    mesh.name = 'buildings-' + color.toString(16); cityGroup.add(mesh);
  }
  const winGeo = new THREE.PlaneGeometry(1.15, 1.5);
  const windows = new THREE.InstancedMesh(winGeo,
    new THREE.MeshStandardMaterial({ color: 0x0e141c, emissive: 0xffd489, emissiveIntensity: 0.5, roughness: 0.4 }),
    Math.max(1, winXforms.length));
  winXforms.forEach((t, i) => windows.setMatrixAt(i, t));
  windows.name = 'all-windows'; cityGroup.add(windows);
  if (doorGeos.length) {
    const doors = new THREE.Mesh(mergeGeometries(doorGeos),
      new THREE.MeshStandardMaterial({ color: 0x0a0c10, emissive: 0xd8a833, emissiveIntensity: 0.25, roughness: 0.7 }));
    doors.name = 'all-doors'; cityGroup.add(doors);
  }
  return { extent };
}

/* ---------------- buildBuilding(page) --------------------------------
 * A low-poly box on its lot: body geometry is collected into the
 * per-color merge (the whole city is a handful of draws), windows
 * are collected into the single InstancedMesh, the door is a dark
 * inset with a gold frame, and the rooftop nameplate is a canvas
 * plane that always reads from the street side.                  */
function buildBuilding(page, i, byColor, winXforms, doorGeos) {
  const cat = categoryOf(page.path);
  const title = titleOf(page.path);
  const depth = depthOf(page.path);
  const hsh = hashOf(page.path);
  const h = depth === 0 ? 34 : depth === 1 ? 15 + (hsh % 9) : 8 + (hsh % 5);
  const w = 16 + (hsh % 5), d = 14 + ((hsh >> 3) % 5);
  const lot = LOTS[i];
  const x = lot.x, z = lot.z;

  const body = new THREE.BoxGeometry(w, h, d); body.translate(x, h / 2, z);
  if (!byColor.has(cat.color)) byColor.set(cat.color, []);
  byColor.get(cat.color).push(body);
  const trim = new THREE.BoxGeometry(w + 0.7, 0.6, d + 0.7); trim.translate(x, h + 0.3, z);
  byColor.get(cat.color).push(trim);

  /* windows: front + back rows into the shared instance list */
  const m4 = new THREE.Matrix4(), q = new THREE.Quaternion(), s1 = new THREE.Vector3(1, 1, 1), pv = new THREE.Vector3();
  const cols = Math.max(2, Math.floor(w / 3.4)), rows = Math.max(2, Math.floor(h / 3.2));
  for (let side = 0; side < 2; side++) {
    const fz = z + (side === 0 ? d / 2 + 0.03 : -d / 2 - 0.03);
    q.setFromAxisAngle(new THREE.Vector3(0, 1, 0), side === 0 ? 0 : Math.PI);
    for (let cx = 0; cx < cols; cx++) for (let ry = 0; ry < rows; ry++) {
      if ((hsh >> (cx + ry) & 3) === 0) continue;              /* some windows dark */
      pv.set(x - w / 2 + (cx + 0.5) * (w / cols), 1.9 + ry * ((h - 3) / rows), fz);
      m4.compose(pv, q, s1); winXforms.push(m4.clone());
    }
  }

  /* the door: south face, street side */
  const door = new THREE.BoxGeometry(2.4, 3.4, 0.3); door.translate(x, 1.7, z + d / 2 + 0.1); doorGeos.push(door);

  /* rooftop nameplate, readable from the street */
  const plate = new THREE.Mesh(new THREE.PlaneGeometry(Math.min(w + 4, 20), 3.4),
    new THREE.MeshBasicMaterial({ map: nameplateTexture(title, '#'