/* eslint-disable */
/* =========================================================
   TEO IoT Dashboard — Three.js low-poly room digital twin.
   Style: low-poly architectural interiors (think poly.pizza
   bedroom / art-gallery aesthetic) rendered in TEO palette.
   - Full-height exterior walls
   - Interior walls between zones with doorway openings
   - Per-zone floor tint + interior equipment props
     (transformers, battery stacks, server racks, conveyors,
     pallet stacks)
   - Devices float as glowing markers on stems, on top of
     equipment, with mesh lines to gateways
   - Camera defaults to a cinematic isometric showing
     interior depth; orbit to inspect any zone
   ========================================================= */

// Per-site layouts. Each zone has a kind (drives interior props)
// and (optionally) doorOpenings: which walls should have a gap.
//   doors: array of 'N'|'S'|'E'|'W' — walls along that side render with a gap.
// Each zone may carry an optional `floor` (1-indexed). When the site has
// multiple floors the Twin3D renders only zones matching the selected floor.
const ZONE_LAYOUTS_3D = {
  // -----------------------------------------------------------------
  // Form Property · Lower Campfield — 2 floors derived from floor plans.
  // Building footprint is wider than tall: ~96 x 44 world units.
  // Atrium runs through the middle of both floors (void on F2).
  // -----------------------------------------------------------------
  'FORM-LCF-01': [
    // ---- Floor 1 ----
    { name:'Reception',    kind:'reception',     x:-38, z:-12, w:16, d:14, floor:1, doors:['E','S'] },
    { name:'Meeting rooms',kind:'meeting-rooms', x:-16, z:-14, w:30, d:10, floor:1, doors:['S','E','W'] },
    { name:'Phone booths', kind:'phone-booths',  x:20,  z:-14, w:30, d:10, floor:1, doors:['S','W'] },
    { name:'Hot desks N',  kind:'hot-desks',     x:-26, z:0,   w:24, d:14, floor:1, doors:['E','N','S'] },
    { name:'Atrium',       kind:'atrium-f1',     x:0,   z:0,   w:18, d:18, floor:1, doors:['E','W','N','S'] },
    { name:'Hot desks S',  kind:'hot-desks',     x:26,  z:0,   w:24, d:14, floor:1, doors:['W','N','S'] },
    { name:'Plant',        kind:'plant',         x:-38, z:12,  w:16, d:14, floor:1, doors:['E','N'] },
    { name:'Meeting rooms',kind:'meeting-rooms', x:-16, z:14,  w:30, d:10, floor:1, doors:['N','E','W'] },
    { name:'Meeting rooms',kind:'meeting-rooms', x:20,  z:14,  w:30, d:10, floor:1, doors:['N','W'] },
    // ---- Floor 2 ----
    { name:'Offices NW',   kind:'private-offices', x:-26, z:-12, w:24, d:14, floor:2, doors:['E','S'] },
    { name:'Offices NE',   kind:'private-offices', x:26,  z:-12, w:24, d:14, floor:2, doors:['W','S'] },
    { name:'Atrium',       kind:'atrium-f2',       x:0,   z:0,   w:18, d:18, floor:2, doors:['E','W','N','S'] },
    { name:'Offices SW',   kind:'private-offices', x:-26, z:12,  w:24, d:14, floor:2, doors:['E','N'] },
    { name:'Offices SE',   kind:'private-offices', x:26,  z:12,  w:24, d:14, floor:2, doors:['W','N'] },
    { name:'Meeting pods', kind:'meeting-pods',    x:-38, z:0,   w:16, d:14, floor:2, doors:['E'] },
    { name:'Wellness',     kind:'wellness',        x:38,  z:0,   w:16, d:14, floor:2, doors:['W'] },
    { name:'Plant',        kind:'plant',           x:0,   z:14,  w:18, d:8,  floor:2, doors:['N'] },
  ],

  // -----------------------------------------------------------------
  // Form Property · Bonded Warehouse — heritage industrial coworking
  // -----------------------------------------------------------------
  'FORM-BW-01': [
    { name:'Reception',     kind:'reception',     x:-38, z:0,   w:16, d:22, doors:['E'] },
    { name:'Main floor',    kind:'open-coworking',x:-12, z:0,   w:30, d:22, doors:['E','W'] },
    { name:'Hot desks',     kind:'hot-desks',     x:18,  z:-7,  w:22, d:10, doors:['W','S'] },
    { name:'Meeting rooms', kind:'meeting-rooms', x:18,  z:7,   w:22, d:10, doors:['W','N'] },
    { name:'Kitchen · café',kind:'kitchen-cafe',  x:38,  z:-7,  w:14, d:10, doors:['W'] },
    { name:'Event space',   kind:'event-space',   x:38,  z:7,   w:14, d:10, doors:['W'] },
    { name:'Plant',         kind:'plant',         x:-38, z:14,  w:16, d:8,  doors:['N'] },
  ],

  // -----------------------------------------------------------------
  // Form Property · XYZ Building — modern open-plan coworking
  // -----------------------------------------------------------------
  'FORM-XYZ-01': [
    { name:'Reception',    kind:'reception',     x:-38, z:0,  w:16, d:22, doors:['E'] },
    { name:'Open plan',    kind:'open-coworking',x:-8,  z:0,  w:38, d:22, doors:['E','W'] },
    { name:'Phone booths', kind:'phone-booths',  x:22,  z:-8, w:18, d:8,  doors:['W','S'] },
    { name:'Meeting rooms',kind:'meeting-rooms', x:22,  z:8,  w:18, d:8,  doors:['W','N'] },
    { name:'Kitchen · café',kind:'kitchen-cafe', x:38,  z:-8, w:14, d:8,  doors:['W'] },
    { name:'Core',         kind:'core',          x:38,  z:8,  w:14, d:8,  doors:['W'] },
    { name:'Plant',        kind:'plant',         x:-38, z:14, w:16, d:8,  doors:['N'] },
  ],

  // -----------------------------------------------------------------
  // Form Property · Spinningfields Estate — mixed-use multi-block
  // -----------------------------------------------------------------
  'FORM-SPN-01': [
    { name:'Public realm',     kind:'public-realm',  x:-38, z:0,   w:16, d:22, doors:['E'] },
    { name:'Building A',       kind:'estate-block',  x:-14, z:-7,  w:24, d:10, doors:['S','E'] },
    { name:'Retail concourse', kind:'retail',        x:-14, z:7,   w:24, d:10, doors:['N','E'] },
    { name:'Building B',       kind:'estate-block',  x:14,  z:-7,  w:24, d:10, doors:['W','S','E'] },
    { name:'Car park',         kind:'car-park',      x:14,  z:7,   w:24, d:10, doors:['W','N','E'] },
    { name:'Energy centre',    kind:'energy-centre', x:38,  z:0,   w:16, d:22, doors:['W'] },
  ],

  // -----------------------------------------------------------------
  // Form Property · No1 Spinningfields — single premium tower (single-floor view)
  // -----------------------------------------------------------------
  'FORM-NO1-01': [
    { name:'Reception', kind:'reception',     x:-30, z:-10, w:20, d:18, doors:['E','S'] },
    { name:'Core',      kind:'core',          x:-8,  z:-10, w:12, d:18, doors:['S','E','W'] },
    { name:'Floor 2',   kind:'private-offices',x:18, z:-10, w:30, d:18, doors:['W','S'] },
    { name:'Floor 3',   kind:'open-coworking',x:-12, z:10,  w:36, d:18, doors:['N','E'] },
    { name:'Plant',     kind:'plant',         x:24,  z:10,  w:18, d:18, doors:['N','W'] },
  ],

  // -----------------------------------------------------------------
  // Form Property · St Johns — mixed commercial
  // -----------------------------------------------------------------
  'FORM-STJ-01': [
    { name:'Reception',    kind:'reception',     x:-38, z:0,  w:16, d:22, doors:['E'] },
    { name:'Open plan',    kind:'open-coworking',x:-10, z:-7, w:32, d:10, doors:['E','S','W'] },
    { name:'Meeting rooms',kind:'meeting-rooms', x:-10, z:7,  w:32, d:10, doors:['N','E','W'] },
    { name:'Kitchen · café',kind:'kitchen-cafe', x:20,  z:-7, w:16, d:10, doors:['W'] },
    { name:'Event space',  kind:'event-space',   x:20,  z:7,  w:16, d:10, doors:['W'] },
    { name:'Roof terrace', kind:'roof-terrace',  x:38,  z:0,  w:14, d:22, doors:['W'] },
    { name:'Plant',        kind:'plant',         x:-38, z:14, w:16, d:8,  doors:['N'] },
  ],

  // -----------------------------------------------------------------
  // Form Property · Eco House (single-dwelling, EPC A)
  // -----------------------------------------------------------------
  // Compact footprint: smaller than commercial buildings (~52 x 30 wu).
  'FORM-ECO-01': [
    { name:'Living',     kind:'living',       x:-12, z:-7, w:20, d:12, doors:['E','S'] },
    { name:'Kitchen',    kind:'eco-kitchen',  x:10,  z:-7, w:18, d:12, doors:['W','S'] },
    { name:'Bedroom',    kind:'bedroom',      x:-14, z:7,  w:16, d:10, doors:['N','E'] },
    { name:'Bathroom',   kind:'bathroom',     x:0,   z:7,  w:8,  d:10, doors:['N','E','W'] },
    { name:'Plant',      kind:'eco-plant',    x:12,  z:7,  w:12, d:10, doors:['N','W'] },
    { name:'Solar roof', kind:'solar-roof',   x:24,  z:0,  w:12, d:22, doors:['W'] },
  ],

  // -----------------------------------------------------------------
  // Form Property · Riverside Apartments (block of flats, 2 floors)
  // -----------------------------------------------------------------
  'FORM-RIV-01': [
    // ---- Floor 1 ----
    { name:'Lobby',         kind:'communal-lobby',  x:-30, z:-6, w:20, d:14, floor:1, doors:['E','S'] },
    { name:'Flats · floor 1',kind:'apartment-row',  x:6,   z:-6, w:48, d:14, floor:1, doors:['W','S'] },
    { name:'Plant',         kind:'eco-plant',       x:-30, z:10, w:20, d:10, floor:1, doors:['N','E'] },
    { name:'Bike + storage',kind:'utility',         x:6,   z:10, w:48, d:10, floor:1, doors:['N','W'] },
    // ---- Floor 2 ----
    { name:'Flats · floor 2',kind:'apartment-row',  x:-10, z:-6, w:48, d:14, floor:2, doors:['E','S'] },
    { name:'Communal',      kind:'communal-room',   x:22,  z:-6, w:18, d:14, floor:2, doors:['W','S'] },
    { name:'Solar roof',    kind:'solar-roof',      x:0,   z:10, w:60, d:10, floor:2, doors:['N'] },
  ],
};

const STATE_HEX_3D = {
  ok:   0x3A6FF8,
  warn: 0xE0A13B,
  down: 0xC44A4A,
  idle: 0x878787,
};

// ----- Low-poly material palette (TEO-aligned but with subtle variation) -----
const PAL = {
  ground:    0x05080F,   // outside the building
  floor:     {           // per-zone floor tints
    transformer: 0x1A2240,
    battery:     0x162039,
    sensor:      0x111A33,
    logistics:   0x1B1E2E,
    sortation:   0x1E2233,
    // Manufacturing
    cnc:         0x141A2E,
    assembly:    0x18203A,
    paint:       0x1F2540,
    plant:       0x141B30,
    // HVAC / hotel
    'vrf-outdoor': 0x121830,
    chiller:       0x10172E,
    'guest-rooms': 0x1A1F38,
    'ahu-room':    0x161D34,
    // Campus
    lecture:   0x17203A,
    classroom: 0x1A2240,
    study:     0x161F38,
    office:    0x161D34,
    // Workspace (Form Property)
    reception:        0x1B2440,
    'meeting-rooms':  0x141C36,
    'phone-booths':   0x161E38,
    'hot-desks':      0x172040,
    'atrium-f1':      0x1C2548,   // brighter — feature space
    'atrium-f2':      0x101633,   // darker — void looking down
    'private-offices':0x141B33,
    'meeting-pods':   0x18213E,
    wellness:         0x1F2A4A,
    'open-coworking': 0x172040,
    'kitchen-cafe':   0x1A2240,
    'event-space':    0x1C2548,
    core:             0x111733,
    // Estate / mixed-use
    'public-realm':   0x12182E,
    'estate-block':   0x162039,
    retail:           0x1C2548,
    'car-park':       0x0E1426,
    'energy-centre':  0x14213A,
    'roof-terrace':   0x1B2848,
    // Housing / eco
    living:           0x1C2540,
    'eco-kitchen':    0x1A2640,
    bedroom:          0x161E36,
    bathroom:         0x122336,
    'eco-plant':      0x101A30,
    'solar-roof':     0x141F38,
    'apartment-row':  0x172040,
    'communal-lobby': 0x1C2748,
    'communal-room':  0x1A2540,
    utility:          0x101633,
    default:   0x131A30,
  },
  exterior:  0x1F2742,   // exterior walls
  interior:  0x2A325A,   // interior partition walls
  wallTrim:  0x3A6FF8,   // top trim accent
  prop:      {
    body:    0x2E3658,
    bodyDark:0x1E243F,
    metal:   0x4A5478,
    accent:  0x7A9CFF,
    pallet:  0x6A6052,   // muted tan — only off-palette element, for contrast
    belt:    0x1A1E2E,
  },
};

// =============================================================
// Helpers for building procedural geometry
// =============================================================

function makeWallSegments(THREE, side, x, z, length, height, thickness, openings, material) {
  // Build one wall (a single span) along `side` of a zone, with optional doorway gaps.
  // side: 'N' | 'S' (run along X axis) or 'E' | 'W' (run along Z axis)
  const group = new THREE.Group();
  const isNS = (side === 'N' || side === 'S');
  // Default: single full-length wall; if openings is true, split into 2 pieces with a 4-unit gap in the middle
  const segments = [];
  if (openings) {
    const gap = Math.min(5.5, length * 0.4);
    const seg = (length - gap) / 2;
    segments.push({ off: -length/2 + seg/2,           len: seg });
    segments.push({ off:  length/2 - seg/2,           len: seg });
  } else {
    segments.push({ off: 0, len: length });
  }
  segments.forEach(s => {
    const geo = isNS
      ? new THREE.BoxGeometry(s.len, height, thickness)
      : new THREE.BoxGeometry(thickness, height, s.len);
    const mesh = new THREE.Mesh(geo, material);
    if (isNS) mesh.position.set(x + s.off, height/2, z);
    else      mesh.position.set(x, height/2, z + s.off);
    group.add(mesh);
    // Top trim (thin coloured strip on top edge — TEO accent)
    const trimGeo = isNS
      ? new THREE.BoxGeometry(s.len + 0.02, 0.05, thickness + 0.02)
      : new THREE.BoxGeometry(thickness + 0.02, 0.05, s.len + 0.02);
    const trim = new THREE.Mesh(
      trimGeo,
      new THREE.MeshStandardMaterial({ color:PAL.wallTrim, emissive:PAL.wallTrim, emissiveIntensity:0.4, roughness:0.6 })
    );
    if (isNS) trim.position.set(x + s.off, height + 0.025, z);
    else      trim.position.set(x, height + 0.025, z + s.off);
    group.add(trim);
  });
  return group;
}

// Place interior props for a zone based on kind. Returns a Group.
function makeZoneProps(THREE, zone) {
  const g = new THREE.Group();
  const matBody     = new THREE.MeshStandardMaterial({ color:PAL.prop.body, roughness:0.85 });
  const matBodyDark = new THREE.MeshStandardMaterial({ color:PAL.prop.bodyDark, roughness:0.9 });
  const matMetal    = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.45, metalness:0.45 });
  const matAccent   = new THREE.MeshStandardMaterial({ color:PAL.prop.accent, emissive:PAL.prop.accent, emissiveIntensity:0.3 });
  const matPallet   = new THREE.MeshStandardMaterial({ color:PAL.prop.pallet, roughness:0.95 });
  const matBelt     = new THREE.MeshStandardMaterial({ color:PAL.prop.belt, roughness:0.95 });

  switch (zone.kind) {
    case 'transformer': {
      // Two big transformer cabinets with vents + a metal frame between them
      [-zone.w/4, zone.w/4].forEach((ox) => {
        const cab = new THREE.Mesh(new THREE.BoxGeometry(5.5, 3.6, 5.5), matBody);
        cab.position.set(zone.x + ox, 1.8, zone.z);
        g.add(cab);
        // top plate
        const top = new THREE.Mesh(new THREE.BoxGeometry(5.8, 0.18, 5.8), matMetal);
        top.position.set(zone.x + ox, 3.7, zone.z);
        g.add(top);
        // vent stripes (three thin slats)
        [-1.4, -0.2, 1].forEach(oy => {
          const vent = new THREE.Mesh(new THREE.BoxGeometry(4.0, 0.18, 0.08), matBodyDark);
          vent.position.set(zone.x + ox, 1.6 + oy + 1.2, zone.z + 2.78);
          g.add(vent);
        });
        // small warning light on top
        const light = new THREE.Mesh(new THREE.SphereGeometry(0.22, 12, 8), matAccent);
        light.position.set(zone.x + ox + 2, 3.95, zone.z + 1.8);
        g.add(light);
      });
      // floor cable tray
      const tray = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.6, 0.08, 0.9), matMetal);
      tray.position.set(zone.x, 0.06, zone.z + zone.d * 0.3);
      g.add(tray);
      break;
    }
    case 'battery': {
      // Stacked battery cabinet wall along the back, plus inverter cabinet
      const totalW = zone.w * 0.7, back = zone.z - zone.d/2 + 1.5;
      const cabW = 1.4;
      const cabCount = Math.floor(totalW / (cabW + 0.15));
      for (let i = 0; i < cabCount; i++) {
        const x = zone.x - totalW/2 + i*(cabW + 0.15) + cabW/2;
        const cab = new THREE.Mesh(new THREE.BoxGeometry(cabW, 3.2, 1.2), matBody);
        cab.position.set(x, 1.6, back);
        g.add(cab);
        // status LED row
        const led = new THREE.Mesh(new THREE.BoxGeometry(cabW * 0.7, 0.06, 0.06), matAccent);
        led.position.set(x, 2.7, back + 0.62);
        g.add(led);
      }
      // Inverter unit
      const inv = new THREE.Mesh(new THREE.BoxGeometry(3.4, 2.6, 1.6), matBodyDark);
      inv.position.set(zone.x + zone.w*0.25, 1.3, zone.z + zone.d*0.25);
      g.add(inv);
      const invTop = new THREE.Mesh(new THREE.BoxGeometry(3.6, 0.12, 1.8), matMetal);
      invTop.position.set(zone.x + zone.w*0.25, 2.66, zone.z + zone.d*0.25);
      g.add(invTop);
      break;
    }
    case 'sensor': {
      // 2 rows of server racks
      [zone.z - 3, zone.z + 3].forEach((row, ri) => {
        for (let i = 0; i < 4; i++) {
          const x = zone.x - 6 + i*4;
          const rack = new THREE.Mesh(new THREE.BoxGeometry(2.4, 2.4, 1.4), matBody);
          rack.position.set(x, 1.2, row);
          g.add(rack);
          // front grille (4 thin lines)
          for (let j = 0; j < 4; j++) {
            const g1 = new THREE.Mesh(new THREE.BoxGeometry(2.0, 0.04, 0.04), matAccent);
            g1.material = new THREE.MeshStandardMaterial({ color:PAL.prop.accent, emissive:PAL.prop.accent, emissiveIntensity:0.25 });
            g1.position.set(x, 0.4 + j*0.5, row + (ri===0 ? 0.72 : -0.72));
            g.add(g1);
          }
        }
      });
      // central gateway pedestal
      const ped = new THREE.Mesh(new THREE.CylinderGeometry(0.7, 0.9, 0.6, 16), matMetal);
      ped.position.set(zone.x, 0.3, zone.z);
      g.add(ped);
      break;
    }
    case 'logistics': {
      // Pallet stacks scattered + simple forklift silhouette
      const palletPositions = [
        [-9, -6], [-9, -2], [-9, 2], [-9, 6],
        [-4, 6], [0, 6], [4, 6],
      ];
      palletPositions.forEach(([ox, oz]) => {
        // 3 stacked pallets
        for (let k = 0; k < 3; k++) {
          const p = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.18, 1.2), matPallet);
          p.position.set(zone.x + ox, 0.09 + k*0.22, zone.z + oz);
          g.add(p);
          // boxes on top of top pallet
          if (k === 2) {
            const box = new THREE.Mesh(new THREE.BoxGeometry(1.3, 0.9, 1.0), matBody);
            box.position.set(zone.x + ox, 0.55 + 0.45, zone.z + oz);
            g.add(box);
          }
        }
      });
      // forklift silhouette — body + mast
      const fk = new THREE.Group();
      const fkBody = new THREE.Mesh(new THREE.BoxGeometry(2.0, 1.0, 1.4), matAccent);
      fkBody.material = new THREE.MeshStandardMaterial({ color:0xE0A13B, roughness:0.6 });
      fkBody.position.set(0, 0.5, 0);
      const fkCab = new THREE.Mesh(new THREE.BoxGeometry(1.4, 1.2, 1.3), matMetal);
      fkCab.position.set(-0.4, 1.55, 0);
      const fkMast = new THREE.Mesh(new THREE.BoxGeometry(0.2, 2.6, 1.2), matMetal);
      fkMast.position.set(1.1, 1.3, 0);
      fk.add(fkBody, fkCab, fkMast);
      fk.position.set(zone.x + 6, 0, zone.z + 4);
      fk.rotation.y = Math.PI / 6;
      g.add(fk);
      break;
    }
    case 'sortation': {
      // Two conveyor belt segments running along X axis
      [-4, 4].forEach((oz) => {
        const beltLen = zone.w * 0.75;
        const belt = new THREE.Mesh(new THREE.BoxGeometry(beltLen, 0.18, 1.6), matBelt);
        belt.position.set(zone.x, 0.9, zone.z + oz);
        g.add(belt);
        // belt frame rails (sides)
        [-0.85, 0.85].forEach(or => {
          const rail = new THREE.Mesh(new THREE.BoxGeometry(beltLen, 0.6, 0.12), matMetal);
          rail.position.set(zone.x, 0.6, zone.z + oz + or);
          g.add(rail);
        });
        // legs
        [-beltLen/2 + 1, 0, beltLen/2 - 1].forEach(ox => {
          const leg = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.9, 1.6), matMetal);
          leg.position.set(zone.x + ox, 0.45, zone.z + oz);
          g.add(leg);
        });
        // boxes travelling on belt (static)
        [-beltLen/2 + 4, -2, 2, beltLen/2 - 3].forEach(ox => {
          const cube = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.9, 1.0), matBody);
          cube.position.set(zone.x + ox, 1.45, zone.z + oz);
          g.add(cube);
        });
      });
      break;
    }

    // -------- Manufacturing --------
    case 'cnc': {
      // Four CNC machine cells in a 2x2 layout behind the device row
      const cells = [[-6, -4], [-6, 2], [2, -4], [2, 2]];
      cells.forEach(([ox, oz]) => {
        const body = new THREE.Mesh(new THREE.BoxGeometry(4.0, 2.6, 3.4), matBody);
        body.position.set(zone.x + ox, 1.3, zone.z + oz);
        g.add(body);
        const top = new THREE.Mesh(new THREE.BoxGeometry(4.2, 0.18, 3.6), matMetal);
        top.position.set(zone.x + ox, 2.69, zone.z + oz);
        g.add(top);
        // Control pillar with a screen
        const pillar = new THREE.Mesh(new THREE.BoxGeometry(0.6, 1.8, 0.6), matBodyDark);
        pillar.position.set(zone.x + ox + 2.5, 0.9, zone.z + oz);
        g.add(pillar);
        const screen = new THREE.Mesh(
          new THREE.BoxGeometry(0.04, 0.7, 0.5),
          new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.7 })
        );
        screen.position.set(zone.x + ox + 2.82, 1.4, zone.z + oz);
        g.add(screen);
        // Spindle indicator on top
        const ind = new THREE.Mesh(new THREE.SphereGeometry(0.18, 10, 8), matAccent);
        ind.position.set(zone.x + ox - 1.5, 2.95, zone.z + oz - 1.2);
        g.add(ind);
      });
      break;
    }
    case 'assembly': {
      // Long central conveyor with three robotic arm stations + workstation boxes
      const beltLen = zone.w * 0.78;
      const belt = new THREE.Mesh(new THREE.BoxGeometry(beltLen, 0.18, 1.4), matBelt);
      belt.position.set(zone.x, 0.85, zone.z + 1.5);
      g.add(belt);
      [-0.75, 0.75].forEach(or => {
        const rail = new THREE.Mesh(new THREE.BoxGeometry(beltLen, 0.6, 0.12), matMetal);
        rail.position.set(zone.x, 0.55, zone.z + 1.5 + or);
        g.add(rail);
      });
      // legs
      for (let i = -beltLen/2 + 1; i <= beltLen/2 - 1; i += 4) {
        const leg = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.85, 1.4), matMetal);
        leg.position.set(zone.x + i, 0.42, zone.z + 1.5);
        g.add(leg);
      }
      // robotic arm stations along the back
      [-7, 0, 7].forEach(ox => {
        const base = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.7, 0.5, 12), matMetal);
        base.position.set(zone.x + ox, 0.25, zone.z - 2);
        g.add(base);
        // shoulder block
        const shoulder = new THREE.Mesh(new THREE.BoxGeometry(0.7, 1.0, 0.7), matBody);
        shoulder.position.set(zone.x + ox, 0.95, zone.z - 2);
        g.add(shoulder);
        // upper arm (tilted)
        const arm = new THREE.Mesh(new THREE.BoxGeometry(0.4, 2.0, 0.4), matBody);
        arm.position.set(zone.x + ox + 0.4, 2.0, zone.z - 1.4);
        arm.rotation.x = -Math.PI / 4;
        g.add(arm);
        // tool head
        const tool = new THREE.Mesh(new THREE.SphereGeometry(0.22, 10, 8), matAccent);
        tool.position.set(zone.x + ox + 0.9, 2.6, zone.z - 0.4);
        g.add(tool);
        // small parts bin near the station
        const bin = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.4, 0.9), matPallet);
        bin.position.set(zone.x + ox - 1.8, 0.2, zone.z - 3.4);
        g.add(bin);
      });
      // parts on belt
      [-beltLen/2 + 4, -2, 2, beltLen/2 - 3].forEach(ox => {
        const part = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.5, 0.8), matBody);
        part.position.set(zone.x + ox, 1.18, zone.z + 1.5);
        g.add(part);
      });
      break;
    }
    case 'paint': {
      // Enclosed booth on the right side of the zone, open toward the device row
      const bx = zone.x + 1, bz = zone.z - 1.5;
      const bw = 12, bh = 3.6, bd = 7;
      // back + side walls (3 sides only — front open)
      const wMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.9 });
      const back = new THREE.Mesh(new THREE.BoxGeometry(bw, bh, 0.25), wMat);
      back.position.set(bx, bh/2, bz - bd/2);
      const left = new THREE.Mesh(new THREE.BoxGeometry(0.25, bh, bd), wMat);
      left.position.set(bx - bw/2, bh/2, bz);
      const right = new THREE.Mesh(new THREE.BoxGeometry(0.25, bh, bd), wMat);
      right.position.set(bx + bw/2, bh/2, bz);
      // roof of booth
      const roofMesh = new THREE.Mesh(new THREE.BoxGeometry(bw + 0.3, 0.2, bd + 0.3), matBodyDark);
      roofMesh.position.set(bx, bh + 0.1, bz);
      g.add(back, left, right, roofMesh);
      // paint heads (two robotic sprayers on the ceiling)
      [-2, 2].forEach(ox => {
        const arm = new THREE.Mesh(new THREE.BoxGeometry(0.25, 1.6, 0.25), matMetal);
        arm.position.set(bx + ox, bh - 0.8, bz);
        g.add(arm);
        const nozzle = new THREE.Mesh(new THREE.ConeGeometry(0.18, 0.45, 8), matAccent);
        nozzle.position.set(bx + ox, bh - 1.8, bz);
        nozzle.rotation.x = Math.PI;
        g.add(nozzle);
      });
      // Car body silhouette being painted (a stretched box for theatre)
      const carBody = new THREE.Mesh(new THREE.BoxGeometry(4.4, 1.2, 1.8), matBody);
      carBody.position.set(bx, 0.8, bz);
      g.add(carBody);
      // Exhaust duct going up out of the roof
      const duct = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.5, 1.4, 12), matMetal);
      duct.position.set(bx - 3.8, bh + 0.9, bz - 1.6);
      g.add(duct);
      // Warning beacon at booth entrance
      const beacon = new THREE.Mesh(
        new THREE.SphereGeometry(0.25, 12, 8),
        new THREE.MeshStandardMaterial({ color:0xE0A13B, emissive:0xE0A13B, emissiveIntensity:0.7 })
      );
      beacon.position.set(bx + bw/2 - 0.3, bh - 0.3, bz + bd/2 - 0.3);
      g.add(beacon);
      break;
    }
    case 'plant': {
      // Compressor with horizontal receiver tank + AHU box + ductwork rack
      // Air receiver tank (horizontal cylinder)
      const tank = new THREE.Mesh(new THREE.CylinderGeometry(0.9, 0.9, 5.0, 16), matBody);
      tank.rotation.z = Math.PI / 2;
      tank.position.set(zone.x - 6, 1.0, zone.z - 4);
      g.add(tank);
      // tank end caps
      [-2.6, 2.6].forEach(ox => {
        const cap = new THREE.Mesh(new THREE.CylinderGeometry(0.92, 0.92, 0.12, 16), matMetal);
        cap.rotation.z = Math.PI / 2;
        cap.position.set(zone.x - 6 + ox, 1.0, zone.z - 4);
        g.add(cap);
      });
      // Compressor head
      const head = new THREE.Mesh(new THREE.BoxGeometry(2.4, 2.0, 1.4), matBodyDark);
      head.position.set(zone.x - 2.8, 1.0, zone.z - 4);
      g.add(head);
      // AHU box
      const ahu = new THREE.Mesh(new THREE.BoxGeometry(7.0, 2.6, 3.2), matBody);
      ahu.position.set(zone.x + 3, 1.3, zone.z - 3.5);
      g.add(ahu);
      // AHU vents (slats facing forward)
      for (let i = 0; i < 5; i++) {
        const v = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.16, 0.06), matBodyDark);
        v.position.set(zone.x + 0.5 + i * 1.2, 1.2 + (i%2)*0.4, zone.z - 1.91);
        g.add(v);
      }
      // overhead ductwork running across zone
      const ductMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.6, metalness:0.4 });
      const ductMain = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.85, 0.7, 0.9), ductMat);
      ductMain.position.set(zone.x, 3.6, zone.z + 4);
      g.add(ductMain);
      [-7, -2, 3, 7].forEach(ox => {
        const drop = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.7, 0.6), ductMat);
        drop.position.set(zone.x + ox, 3.0, zone.z + 4);
        g.add(drop);
      });
      break;
    }

    // -------- HVAC / hotel --------
    case 'vrf-outdoor': {
      // Three stacked Samsung-style VRF condenser units along the back, refrigerant pipe rack
      const yBase = 0.4;
      [-7, 0, 7].forEach(ox => {
        // Lower body (two-fan unit)
        const cab = new THREE.Mesh(new THREE.BoxGeometry(4.0, 2.4, 1.8), matBody);
        cab.position.set(zone.x + ox, yBase + 1.2, zone.z - 4);
        g.add(cab);
        // Top plate
        const top = new THREE.Mesh(new THREE.BoxGeometry(4.2, 0.15, 2.0), matBodyDark);
        top.position.set(zone.x + ox, yBase + 2.48, zone.z - 4);
        g.add(top);
        // Two fan rings on top
        [-1.0, 1.0].forEach(fx => {
          const fanHole = new THREE.Mesh(
            new THREE.CylinderGeometry(0.7, 0.7, 0.12, 16),
            new THREE.MeshStandardMaterial({ color:0x0E1426, roughness:0.95 })
          );
          fanHole.position.set(zone.x + ox + fx, yBase + 2.55, zone.z - 4);
          g.add(fanHole);
          // fan blade silhouette
          for (let b = 0; b < 3; b++) {
            const blade = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.04, 1.0), matMetal);
            blade.position.set(zone.x + ox + fx, yBase + 2.61, zone.z - 4);
            blade.rotation.y = (b * Math.PI * 2) / 3;
            g.add(blade);
          }
        });
        // Front grille slats
        for (let s = 0; s < 6; s++) {
          const slat = new THREE.Mesh(new THREE.BoxGeometry(3.6, 0.08, 0.05), matBodyDark);
          slat.position.set(zone.x + ox, yBase + 0.4 + s * 0.32, zone.z - 3.11);
          g.add(slat);
        }
        // Concrete plinth
        const plinth = new THREE.Mesh(new THREE.BoxGeometry(4.4, 0.4, 2.4), matBodyDark);
        plinth.position.set(zone.x + ox, 0.2, zone.z - 4);
        g.add(plinth);
      });
      // Refrigerant pipe rack running across the back
      const pipeMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.45, metalness:0.55 });
      [-0.15, 0.15].forEach(oy => {
        const pipe = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, zone.w * 0.88, 12), pipeMat);
        pipe.rotation.z = Math.PI / 2;
        pipe.position.set(zone.x, 3.6 + oy, zone.z - 2);
        g.add(pipe);
      });
      // Pipe stands
      [-9, -3, 3, 9].forEach(ox => {
        const stand = new THREE.Mesh(new THREE.BoxGeometry(0.18, 1.4, 0.18), pipeMat);
        stand.position.set(zone.x + ox, 2.9, zone.z - 2);
        g.add(stand);
      });
      break;
    }
    case 'chiller': {
      // Two chiller bodies + buffer tank + pumps
      [-5, 5].forEach(ox => {
        const body = new THREE.Mesh(new THREE.BoxGeometry(7.0, 2.6, 2.6), matBody);
        body.position.set(zone.x + ox, 1.3, zone.z - 2);
        g.add(body);
        // top heat-exchanger fins (vertical slats)
        for (let i = 0; i < 9; i++) {
          const fin = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.7, 2.4), matMetal);
          fin.position.set(zone.x + ox - 3.0 + i * 0.75, 2.95, zone.z - 2);
          g.add(fin);
        }
        // status display on side
        const disp = new THREE.Mesh(
          new THREE.BoxGeometry(0.04, 0.5, 0.8),
          new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.7 })
        );
        disp.position.set(zone.x + ox - 3.52, 1.8, zone.z - 2);
        g.add(disp);
      });
      // Buffer tank (tall cylinder)
      const tank = new THREE.Mesh(new THREE.CylinderGeometry(1.0, 1.0, 3.0, 16), matBody);
      tank.position.set(zone.x, 1.5, zone.z + 2.5);
      g.add(tank);
      const tankTop = new THREE.Mesh(new THREE.CylinderGeometry(1.05, 1.05, 0.12, 16), matMetal);
      tankTop.position.set(zone.x, 3.06, zone.z + 2.5);
      g.add(tankTop);
      // Two pumps
      [-3, 3].forEach(ox => {
        const pumpBase = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.4, 1.0), matBodyDark);
        pumpBase.position.set(zone.x + ox, 0.2, zone.z + 3);
        const pumpBody = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.5, 1.0, 12), matMetal);
        pumpBody.rotation.z = Math.PI / 2;
        pumpBody.position.set(zone.x + ox, 0.8, zone.z + 3);
        g.add(pumpBase, pumpBody);
      });
      break;
    }
    case 'guest-rooms': {
      // Show a strip of 5 hotel guest rooms with internal partitions, beds, and ceiling AC units
      const rooms = 5;
      const roomW = (zone.w - 2) / rooms;
      const roomD = 8;
      const startX = zone.x - zone.w/2 + 1 + roomW/2;
      const rowZ = zone.z - 4;
      // Corridor floor strip
      const corridor = new THREE.Mesh(new THREE.PlaneGeometry(zone.w - 2, 4), new THREE.MeshStandardMaterial({ color:0x1F2640, roughness:0.95 }));
      corridor.rotation.x = -Math.PI/2;
      corridor.position.set(zone.x, 0.06, zone.z + 2);
      g.add(corridor);
      // Partitions between rooms
      const partMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.9 });
      for (let i = 0; i <= rooms; i++) {
        const px = zone.x - zone.w/2 + 1 + i * roomW;
        const partWall = new THREE.Mesh(new THREE.BoxGeometry(0.15, 2.6, roomD), partMat);
        partWall.position.set(px, 1.3, rowZ);
        g.add(partWall);
      }
      // Back wall of guest rooms
      const backWall = new THREE.Mesh(new THREE.BoxGeometry(zone.w - 2, 2.6, 0.15), partMat);
      backWall.position.set(zone.x, 1.3, rowZ - roomD/2);
      g.add(backWall);
      // Per-room: bed + ceiling AC unit
      for (let i = 0; i < rooms; i++) {
        const rx = startX + i * roomW;
        // bed
        const bed = new THREE.Mesh(new THREE.BoxGeometry(roomW * 0.7, 0.4, 3.0), new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 }));
        bed.position.set(rx, 0.25, rowZ - 2.2);
        g.add(bed);
        // pillow
        const pillow = new THREE.Mesh(new THREE.BoxGeometry(roomW * 0.6, 0.12, 0.5), new THREE.MeshStandardMaterial({ color:0xE6E8EE, roughness:1 }));
        pillow.position.set(rx, 0.52, rowZ - 3.55);
        g.add(pillow);
        // nightstand
        const ns = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.5, 0.6), matBodyDark);
        ns.position.set(rx + roomW * 0.4, 0.3, rowZ - 3.5);
        g.add(ns);
        // Ceiling-mounted Samsung WindFree 4-way unit (small flat box)
        const acBox = new THREE.Mesh(
          new THREE.BoxGeometry(1.8, 0.25, 1.8),
          new THREE.MeshStandardMaterial({ color:0xF6F7F9, roughness:0.6 })
        );
        acBox.position.set(rx, 2.4, rowZ - 0.5);
        g.add(acBox);
        // Vents on the AC unit
        const acVent = new THREE.Mesh(
          new THREE.BoxGeometry(1.5, 0.05, 0.04),
          new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.5 })
        );
        acVent.position.set(rx, 2.3, rowZ - 0.5);
        g.add(acVent);
      }
      break;
    }
    case 'ahu-room': {
      // Two large AHU boxes + ductwork manifold
      [-6, 6].forEach(ox => {
        const ahu = new THREE.Mesh(new THREE.BoxGeometry(8.0, 2.6, 3.0), matBody);
        ahu.position.set(zone.x + ox, 1.3, zone.z - 4);
        g.add(ahu);
        // Front face dot-grid (filter pattern)
        for (let r = 0; r < 3; r++) {
          for (let c = 0; c < 8; c++) {
            const dot = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 6), matBodyDark);
            dot.position.set(zone.x + ox - 3.2 + c * 0.9, 0.5 + r * 0.9, zone.z - 2.51);
            g.add(dot);
          }
        }
        // Label panel (emissive strip)
        const label = new THREE.Mesh(
          new THREE.BoxGeometry(2.0, 0.3, 0.05),
          new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.6 })
        );
        label.position.set(zone.x + ox, 2.4, zone.z - 2.46);
        g.add(label);
      });
      // Ductwork manifold above
      const ductMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.5, metalness:0.5 });
      const mainDuct = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.85, 0.9, 1.2), ductMat);
      mainDuct.position.set(zone.x, 3.6, zone.z + 0.5);
      g.add(mainDuct);
      [-9, -4, 1, 6, 9].forEach(ox => {
        const drop = new THREE.Mesh(new THREE.BoxGeometry(0.5, 1.0, 0.7), ductMat);
        drop.position.set(zone.x + ox, 3.0, zone.z + 0.5);
        g.add(drop);
      });
      // Decorative seating cluster (atrium)
      const seatMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      [[-3, 4], [3, 4]].forEach(([ox, oz]) => {
        const seat = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.5, 0.9), seatMat);
        seat.position.set(zone.x + ox, 0.3, zone.z + oz);
        g.add(seat);
        const back = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.7, 0.2), seatMat);
        back.position.set(zone.x + ox, 0.8, zone.z + oz + 0.4);
        g.add(back);
      });
      break;
    }

    // -------- Campus --------
    case 'lecture': {
      // Tiered seating: 5 rows stepping back and up
      const benchMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const tierMat  = new THREE.MeshStandardMaterial({ color:0x232A48, roughness:1 });
      for (let r = 0; r < 5; r++) {
        const y = 0.15 + r * 0.25;
        const z = zone.z - 4 + r * 1.6;
        // tier platform
        const tier = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.8, 0.3, 1.5), tierMat);
        tier.position.set(zone.x, y, z);
        g.add(tier);
        // bench (seat) along this tier
        const bench = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.75, 0.4, 0.6), benchMat);
        bench.position.set(zone.x, y + 0.35, z - 0.2);
        g.add(bench);
        // backrest for previous tier's bench (visual cue)
        if (r > 0) {
          const back = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.75, 0.5, 0.1), benchMat);
          back.position.set(zone.x, y + 0.55, z - 0.55);
          g.add(back);
        }
      }
      // Lectern at front
      const lectern = new THREE.Mesh(new THREE.BoxGeometry(1.4, 1.2, 0.8), matBody);
      lectern.position.set(zone.x - 4, 0.6, zone.z + 7);
      g.add(lectern);
      // Top tilted panel
      const top = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.08, 0.9), matMetal);
      top.position.set(zone.x - 4, 1.22, zone.z + 7);
      top.rotation.x = -Math.PI / 16;
      g.add(top);
      // Projector screen at back wall
      const screen = new THREE.Mesh(
        new THREE.BoxGeometry(zone.w * 0.55, 2.2, 0.08),
        new THREE.MeshStandardMaterial({ color:0xF6F7F9, roughness:0.7 })
      );
      screen.position.set(zone.x, 2.4, zone.z + 8.5);
      g.add(screen);
      // Screen content (faint accent rectangle)
      const slide = new THREE.Mesh(
        new THREE.BoxGeometry(zone.w * 0.55 - 0.3, 2.0, 0.02),
        new THREE.MeshStandardMaterial({ color:0x3A6FF8, emissive:0x3A6FF8, emissiveIntensity:0.25 })
      );
      slide.position.set(zone.x, 2.4, zone.z + 8.46);
      g.add(slide);
      break;
    }
    case 'classroom': {
      // 4 columns × 3 rows of desks (12 desks) with chairs and a teacher's desk + whiteboard
      const deskMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const chairMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const colsX = [-7.5, -2.5, 2.5, 7.5];
      const rowsZ = [-2, 1, 4];
      colsX.forEach(ox => {
        rowsZ.forEach(oz => {
          const desk = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.08, 1.2), deskMat);
          desk.position.set(zone.x + ox, 0.75, zone.z + oz);
          g.add(desk);
          [-0.8, 0.8].forEach(lx => {
            const leg = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.7, 0.08), matMetal);
            leg.position.set(zone.x + ox + lx, 0.35, zone.z + oz);
            g.add(leg);
          });
          const chair = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.5, 1.0), chairMat);
          chair.position.set(zone.x + ox, 0.25, zone.z + oz + 1.1);
          g.add(chair);
          const back = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.7, 0.1), chairMat);
          back.position.set(zone.x + ox, 0.6, zone.z + oz + 1.55);
          g.add(back);
        });
      });
      // Teacher's desk
      const tDesk = new THREE.Mesh(new THREE.BoxGeometry(3.4, 0.1, 1.2), matBody);
      tDesk.position.set(zone.x, 0.8, zone.z - 6);
      g.add(tDesk);
      // Whiteboard
      const board = new THREE.Mesh(
        new THREE.BoxGeometry(zone.w * 0.55, 1.8, 0.08),
        new THREE.MeshStandardMaterial({ color:0xFFFFFF, roughness:0.9 })
      );
      board.position.set(zone.x, 2.1, zone.z - 9.5);
      g.add(board);
      // Whiteboard accent stripe
      const stripe = new THREE.Mesh(
        new THREE.BoxGeometry(zone.w * 0.55 + 0.05, 0.05, 0.085),
        new THREE.MeshStandardMaterial({ color:0x3A6FF8 })
      );
      stripe.position.set(zone.x, 1.05, zone.z - 9.5);
      g.add(stripe);
      break;
    }
    case 'study': {
      // Six privacy booths (cylinder shells) arranged in 2 rows + central long table
      const podMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const tablemat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const positions = [
        [-7, -3], [-7, 3], [-2, -3], [-2, 3], [3, -3], [3, 3],
      ];
      positions.forEach(([ox, oz]) => {
        // Outer pod shell — partial cylinder (we fake with a 16-sided cylinder)
        const shell = new THREE.Mesh(
          new THREE.CylinderGeometry(1.4, 1.4, 1.8, 16, 1, true),
          new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95, side:THREE.DoubleSide })
        );
        shell.position.set(zone.x + ox, 0.9, zone.z + oz);
        g.add(shell);
        // Floor inside pod (a small disc)
        const padding = new THREE.Mesh(
          new THREE.CylinderGeometry(1.35, 1.35, 0.05, 16),
          new THREE.MeshStandardMaterial({ color:0x232A48, roughness:1 })
        );
        padding.position.set(zone.x + ox, 0.04, zone.z + oz);
        g.add(padding);
        // Desktop inside pod
        const desktop = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.06, 0.6), tablemat);
        desktop.position.set(zone.x + ox, 0.7, zone.z + oz - 0.4);
        g.add(desktop);
        // Stool
        const stool = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.3, 0.45, 12), podMat);
        stool.position.set(zone.x + ox, 0.22, zone.z + oz + 0.3);
        g.add(stool);
        // Reading lamp (small emissive sphere on top)
        const lamp = new THREE.Mesh(
          new THREE.SphereGeometry(0.14, 10, 8),
          new THREE.MeshStandardMaterial({ color:0xE0A13B, emissive:0xE0A13B, emissiveIntensity:0.5 })
        );
        lamp.position.set(zone.x + ox + 0.6, 1.4, zone.z + oz - 0.5);
        g.add(lamp);
      });
      // Central long table (collaborative area)
      const longTable = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.6, 0.1, 1.2), tablemat);
      longTable.position.set(zone.x, 0.75, zone.z + 7);
      g.add(longTable);
      // Bench seats either side
      [-0.9, 0.9].forEach(oz => {
        const bench = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.55, 0.5, 0.55), podMat);
        bench.position.set(zone.x, 0.25, zone.z + 7 + oz);
        g.add(bench);
      });
      break;
    }
    // -------- Workspace (Form Property) --------
    case 'reception': {
      // Reception desk (curved) + waiting seats + signage panel
      const deskMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const topMat  = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.4 });
      // Curved reception (cylinder segment approx)
      const desk = new THREE.Mesh(new THREE.CylinderGeometry(2.6, 2.6, 1.1, 16, 1, false, -Math.PI/2, Math.PI), deskMat);
      desk.position.set(zone.x, 0.55, zone.z - 1);
      g.add(desk);
      const deskTop = new THREE.Mesh(new THREE.CylinderGeometry(2.85, 2.85, 0.1, 16, 1, false, -Math.PI/2, Math.PI), topMat);
      deskTop.position.set(zone.x, 1.13, zone.z - 1);
      g.add(deskTop);
      // Backdrop signage panel (TEO accent strip)
      const panel = new THREE.Mesh(new THREE.BoxGeometry(zone.w*0.65, 2.6, 0.12), deskMat);
      panel.position.set(zone.x, 1.6, zone.z - zone.d/2 + 0.6);
      g.add(panel);
      const strip = new THREE.Mesh(new THREE.BoxGeometry(zone.w*0.4, 0.18, 0.14), accentMat);
      strip.position.set(zone.x, 2.0, zone.z - zone.d/2 + 0.55);
      g.add(strip);
      // Waiting bench
      const bench = new THREE.Mesh(new THREE.BoxGeometry(4.0, 0.5, 1.0), new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 }));
      bench.position.set(zone.x, 0.25, zone.z + 3);
      g.add(bench);
      // Two pot plants (cylinders w/ green sphere)
      [-2.4, 2.4].forEach(ox => {
        const pot = new THREE.Mesh(new THREE.CylinderGeometry(0.35, 0.3, 0.6, 12), deskMat);
        pot.position.set(zone.x + ox, 0.3, zone.z + 3);
        const foliage = new THREE.Mesh(new THREE.SphereGeometry(0.5, 10, 8), new THREE.MeshStandardMaterial({ color:0x4A6650, roughness:1 }));
        foliage.position.set(zone.x + ox, 1.0, zone.z + 3);
        g.add(pot, foliage);
      });
      break;
    }

    case 'meeting-rooms': {
      // Row of meeting/private rooms with partitions, each room has a table + chairs
      const partMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const tableMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const chairMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.4 });
      const rooms = Math.max(2, Math.floor(zone.w / 6));
      const roomW = (zone.w - 1) / rooms;
      const startX = zone.x - zone.w/2 + 0.5 + roomW/2;
      const rowZ = zone.z;
      // Partitions
      for (let i = 0; i <= rooms; i++) {
        const px = zone.x - zone.w/2 + 0.5 + i * roomW;
        const part = new THREE.Mesh(new THREE.BoxGeometry(0.12, 2.4, zone.d * 0.85), partMat);
        part.position.set(px, 1.2, rowZ);
        g.add(part);
      }
      // Per-room: small table + 2 chairs + status light strip
      for (let i = 0; i < rooms; i++) {
        const rx = startX + i * roomW;
        const table = new THREE.Mesh(new THREE.BoxGeometry(Math.min(roomW * 0.5, 2.2), 0.1, 1.0), tableMat);
        table.position.set(rx, 0.75, rowZ);
        g.add(table);
        [-0.7, 0.7].forEach(oz => {
          const ch = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.7, 0.6), chairMat);
          ch.position.set(rx, 0.35, rowZ + oz);
          g.add(ch);
        });
        // status light strip above the door
        const strip = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.06, 0.06), accentMat);
        strip.position.set(rx, 2.2, rowZ + zone.d * 0.42);
        g.add(strip);
      }
      break;
    }

    case 'phone-booths': {
      // Row of single-seat enclosed booths
      const shellMat = new THREE.MeshStandardMaterial({ color:0x2A335A, roughness:0.95 });
      const seatMat  = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.45 });
      const count = Math.max(3, Math.floor(zone.w / 2.5));
      const w = (zone.w - 1) / count;
      const startX = zone.x - zone.w/2 + 0.5 + w/2;
      for (let i = 0; i < count; i++) {
        const bx = startX + i * w;
        // booth shell (open front)
        const back = new THREE.Mesh(new THREE.BoxGeometry(w - 0.2, 2.2, 0.12), shellMat);
        back.position.set(bx, 1.1, zone.z - 1.5);
        const left = new THREE.Mesh(new THREE.BoxGeometry(0.12, 2.2, 2.8), shellMat);
        left.position.set(bx - (w-0.2)/2, 1.1, zone.z - 0.2);
        const right = new THREE.Mesh(new THREE.BoxGeometry(0.12, 2.2, 2.8), shellMat);
        right.position.set(bx + (w-0.2)/2, 1.1, zone.z - 0.2);
        const top = new THREE.Mesh(new THREE.BoxGeometry(w - 0.2, 0.1, 2.8), shellMat);
        top.position.set(bx, 2.2, zone.z - 0.2);
        g.add(back, left, right, top);
        // small stool
        const stool = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.3, 0.45, 10), seatMat);
        stool.position.set(bx, 0.22, zone.z - 0.4);
        g.add(stool);
        // status light strip above booth
        const strip = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.05, 0.05), accentMat);
        strip.position.set(bx, 2.32, zone.z + 1.2);
        g.add(strip);
      }
      break;
    }

    case 'hot-desks': {
      // Bays of bench-style desks with monitors. Two parallel rows.
      const benchMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const monMat   = new THREE.MeshStandardMaterial({ color:0x0A0F1F, roughness:0.8 });
      const screenMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.35 });
      const chairMat = new THREE.MeshStandardMaterial({ color:0x232A48, roughness:1 });
      [-3.5, 3.5].forEach(oz => {
        // Long bench
        const bench = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.8, 0.08, 1.4), benchMat);
        bench.position.set(zone.x, 0.78, zone.z + oz);
        g.add(bench);
        // Monitor cluster every 2 units
        const seats = Math.floor((zone.w * 0.8) / 2.1);
        const startX = zone.x - (zone.w * 0.8)/2 + 1.05;
        for (let i = 0; i < seats; i++) {
          const mx = startX + i * 2.1;
          // Monitor
          const mon = new THREE.Mesh(new THREE.BoxGeometry(1.1, 0.7, 0.06), monMat);
          mon.position.set(mx, 1.3, zone.z + oz - 0.4);
          g.add(mon);
          const face = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.6, 0.005), screenMat);
          face.position.set(mx, 1.3, zone.z + oz - 0.37);
          g.add(face);
          // Stand
          const stand = new THREE.Mesh(new THREE.BoxGeometry(0.18, 0.3, 0.18), new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.5, metalness:0.4 }));
          stand.position.set(mx, 0.95, zone.z + oz - 0.4);
          g.add(stand);
          // Chair on the open side
          const chair = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.32, 0.1, 12), chairMat);
          chair.position.set(mx, 0.55, zone.z + oz + (oz < 0 ? 1.4 : -1.4));
          g.add(chair);
          const chairBack = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.7, 0.1), chairMat);
          chairBack.position.set(mx, 0.95, zone.z + oz + (oz < 0 ? 1.85 : -1.85));
          g.add(chairBack);
        }
      });
      break;
    }

    case 'atrium-f1': {
      // Open atrium with curved staircase at the centre + structural columns
      const stairMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.85 });
      const railMat  = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.5 });
      // Curved staircase — approximate as 8 box steps in a spiral
      const stairCount = 9;
      const radius = 3.2;
      for (let i = 0; i < stairCount; i++) {
        const angle = -Math.PI/2 + (i / stairCount) * Math.PI;
        const x = zone.x + Math.cos(angle) * radius;
        const z = zone.z + 0.5 + Math.sin(angle) * radius;
        const step = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.18, 0.7), stairMat);
        step.position.set(x, 0.09 + i * 0.18, z);
        step.rotation.y = angle + Math.PI/2;
        g.add(step);
      }
      // Landing at top of stair
      const landing = new THREE.Mesh(new THREE.BoxGeometry(4.5, 0.22, 4.5), stairMat);
      landing.position.set(zone.x, 0.09 + stairCount * 0.18, zone.z - 2.4);
      g.add(landing);
      // Hand-rail (curved approximation with a thin torus segment)
      const rail = new THREE.Mesh(
        new THREE.TorusGeometry(radius, 0.06, 8, 22, Math.PI),
        railMat
      );
      rail.rotation.x = -Math.PI/2;
      rail.rotation.z = -Math.PI/2;
      rail.position.set(zone.x, 1.3, zone.z + 0.5);
      g.add(rail);
      // Four structural columns at corners of atrium
      [[-zone.w/2+1, -zone.d/2+1],[zone.w/2-1, -zone.d/2+1],[-zone.w/2+1, zone.d/2-1],[zone.w/2-1, zone.d/2-1]].forEach(([ox,oz]) => {
        const col = new THREE.Mesh(new THREE.BoxGeometry(0.5, 4.2, 0.5), new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.85 }));
        col.position.set(zone.x + ox, 2.1, zone.z + oz);
        g.add(col);
      });
      // Feature pendant lights — accent dots overhead
      [[-3,-3],[3,-3],[-3,3],[3,3]].forEach(([ox,oz]) => {
        const stem = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 1.6, 6), railMat);
        stem.position.set(zone.x + ox, 3.4, zone.z + oz);
        g.add(stem);
        const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.18, 10, 8), new THREE.MeshStandardMaterial({ color:0xE0A13B, emissive:0xE0A13B, emissiveIntensity:0.6 }));
        bulb.position.set(zone.x + ox, 2.6, zone.z + oz);
        g.add(bulb);
      });
      break;
    }

    case 'atrium-f2': {
      // Void looking down to floor 1 — a darker tile + balustrade around the perimeter
      // Dark inner panel (the void)
      const void_ = new THREE.Mesh(
        new THREE.PlaneGeometry(zone.w * 0.65, zone.d * 0.65),
        new THREE.MeshStandardMaterial({ color:0x05080F, roughness:1 })
      );
      void_.rotation.x = -Math.PI/2;
      void_.position.set(zone.x, 0.07, zone.z);
      g.add(void_);
      // Balustrade — thin walls around the void with a top rail
      const balMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.9 });
      const railMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.5 });
      const inW = zone.w * 0.65, inD = zone.d * 0.65;
      const bH = 1.1, bT = 0.12;
      // four sides
      const sides = [
        { x:zone.x, z:zone.z - inD/2, w:inW + bT, d:bT },
        { x:zone.x, z:zone.z + inD/2, w:inW + bT, d:bT },
        { x:zone.x - inW/2, z:zone.z, w:bT, d:inD + bT },
        { x:zone.x + inW/2, z:zone.z, w:bT, d:inD + bT },
      ];
      sides.forEach(s => {
        const bal = new THREE.Mesh(new THREE.BoxGeometry(s.w, bH, s.d), balMat);
        bal.position.set(s.x, bH/2, s.z);
        g.add(bal);
        const rail = new THREE.Mesh(new THREE.BoxGeometry(s.w, 0.06, s.d), railMat);
        rail.position.set(s.x, bH + 0.03, s.z);
        g.add(rail);
      });
      // Pendant lights hanging into the void
      [[-3,-3],[3,3]].forEach(([ox,oz]) => {
        const stem = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 1.6, 6), railMat);
        stem.position.set(zone.x + ox, 3.4, zone.z + oz);
        g.add(stem);
        const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.18, 10, 8), new THREE.MeshStandardMaterial({ color:0xE0A13B, emissive:0xE0A13B, emissiveIntensity:0.6 }));
        bulb.position.set(zone.x + ox, 2.6, zone.z + oz);
        g.add(bulb);
      });
      break;
    }

    case 'private-offices': {
      // Grid of individual office rooms with partitions + desk + chair
      const wallMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.95 });
      const deskMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const chairMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const cols = Math.max(3, Math.floor(zone.w / 6));
      const rows = 1;
      const cellW = (zone.w - 1) / cols;
      const startX = zone.x - zone.w/2 + 0.5 + cellW/2;
      // Partition walls
      for (let i = 0; i <= cols; i++) {
        const px = zone.x - zone.w/2 + 0.5 + i * cellW;
        const wall = new THREE.Mesh(new THREE.BoxGeometry(0.14, 2.4, zone.d * 0.85), wallMat);
        wall.position.set(px, 1.2, zone.z);
        g.add(wall);
      }
      // Back wall
      const back = new THREE.Mesh(new THREE.BoxGeometry(zone.w - 0.6, 2.4, 0.14), wallMat);
      back.position.set(zone.x, 1.2, zone.z - zone.d * 0.42);
      g.add(back);
      // Office contents
      for (let i = 0; i < cols; i++) {
        const ox = startX + i * cellW;
        const desk = new THREE.Mesh(new THREE.BoxGeometry(Math.min(cellW * 0.7, 2.4), 0.08, 1.2), deskMat);
        desk.position.set(ox, 0.78, zone.z - 1);
        g.add(desk);
        const monitor = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.6, 0.05), new THREE.MeshStandardMaterial({ color:0x0A0F1F, roughness:0.8 }));
        monitor.position.set(ox, 1.18, zone.z - 1.4);
        g.add(monitor);
        const chair = new THREE.Mesh(new THREE.CylinderGeometry(0.38, 0.3, 0.1, 12), chairMat);
        chair.position.set(ox, 0.5, zone.z + 0.2);
        g.add(chair);
        const chairBack = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.7, 0.1), chairMat);
        chairBack.position.set(ox, 0.9, zone.z + 0.65);
        g.add(chairBack);
      }
      break;
    }

    case 'meeting-pods': {
      // Small round meeting pods (cylinder shells with internal table)
      const shellMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95, side:THREE.DoubleSide });
      const tableMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const seatMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const positions = [[-4, -3], [4, -3], [-4, 3], [4, 3]];
      positions.forEach(([ox, oz]) => {
        const shell = new THREE.Mesh(new THREE.CylinderGeometry(1.6, 1.6, 2.0, 16, 1, true), shellMat);
        shell.position.set(zone.x + ox, 1.0, zone.z + oz);
        g.add(shell);
        const table = new THREE.Mesh(new THREE.CylinderGeometry(0.9, 0.9, 0.08, 16), tableMat);
        table.position.set(zone.x + ox, 0.8, zone.z + oz);
        g.add(table);
        const tableLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.18, 0.74, 8), new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.5, metalness:0.5 }));
        tableLeg.position.set(zone.x + ox, 0.4, zone.z + oz);
        g.add(tableLeg);
        for (let a = 0; a < 3; a++) {
          const ang = (a / 3) * Math.PI * 2;
          const cx = zone.x + ox + Math.cos(ang) * 1.05;
          const cz = zone.z + oz + Math.sin(ang) * 1.05;
          const c = new THREE.Mesh(new THREE.BoxGeometry(0.55, 0.8, 0.55), seatMat);
          c.position.set(cx, 0.4, cz);
          g.add(c);
        }
      });
      break;
    }

    case 'wellness': {
      // Yoga / wellness studio: floor mats laid out, a small mirror wall, plants
      const matMat = new THREE.MeshStandardMaterial({ color:0x5A7560, roughness:1 });
      const mirrorMat = new THREE.MeshStandardMaterial({ color:0xE0E4ED, roughness:0.3, metalness:0.4 });
      // Mirror wall along the back
      const mirror = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.75, 2.4, 0.08), mirrorMat);
      mirror.position.set(zone.x, 1.2, zone.z - zone.d/2 + 0.2);
      g.add(mirror);
      // 6 yoga mats arranged in 2 rows
      for (let r = 0; r < 2; r++) {
        for (let c = 0; c < 3; c++) {
          const mat = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.05, 0.7), matMat);
          mat.position.set(zone.x - 3 + c * 3, 0.04, zone.z - 1 + r * 1.7);
          g.add(mat);
        }
      }
      // Two pot plants in the corners
      [-zone.w/2+1.5, zone.w/2-1.5].forEach(ox => {
        const pot = new THREE.Mesh(new THREE.CylinderGeometry(0.35, 0.3, 0.7, 12), new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.9 }));
        pot.position.set(zone.x + ox, 0.35, zone.z + zone.d/2 - 1.5);
        const foliage = new THREE.Mesh(new THREE.SphereGeometry(0.55, 10, 8), new THREE.MeshStandardMaterial({ color:0x4A6650, roughness:1 }));
        foliage.position.set(zone.x + ox, 1.1, zone.z + zone.d/2 - 1.5);
        g.add(pot, foliage);
      });
      break;
    }

    case 'open-coworking': {
      // Large open floor with multiple desk clusters and a central feature seat
      const deskMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const chairMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      // 6 clusters of desks
      const clusters = [
        [-9, -5], [-9, 5], [0, -5], [0, 5], [9, -5], [9, 5],
      ];
      clusters.forEach(([cx, cz]) => {
        // 4-desk cluster (2x2)
        for (let r = 0; r < 2; r++) {
          for (let c = 0; c < 2; c++) {
            const dx = zone.x + cx + (c === 0 ? -1.2 : 1.2);
            const dz = zone.z + cz + (r === 0 ? -0.8 : 0.8);
            const desk = new THREE.Mesh(new THREE.BoxGeometry(2.0, 0.08, 1.2), deskMat);
            desk.position.set(dx, 0.78, dz);
            g.add(desk);
            const monitor = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.55, 0.06), new THREE.MeshStandardMaterial({ color:0x0A0F1F, roughness:0.8 }));
            monitor.position.set(dx, 1.18, dz + (r === 0 ? 0.4 : -0.4));
            g.add(monitor);
            const chair = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.7, 0.6), chairMat);
            chair.position.set(dx, 0.4, dz + (r === 0 ? -0.8 : 0.8));
            g.add(chair);
          }
        }
      });
      // Central feature seating (long communal table)
      const longTable = new THREE.Mesh(new THREE.BoxGeometry(8, 0.1, 1.6), deskMat);
      longTable.position.set(zone.x, 0.78, zone.z);
      g.add(longTable);
      [-0.95, 0.95].forEach(oz => {
        const bench = new THREE.Mesh(new THREE.BoxGeometry(8, 0.5, 0.5), chairMat);
        bench.position.set(zone.x, 0.25, zone.z + oz);
        g.add(bench);
      });
      // Heritage / industrial columns (Bonded Warehouse vibe — extra brick-tinted)
      [[-zone.w/2+3, 0], [zone.w/2-3, 0]].forEach(([ox, oz]) => {
        const col = new THREE.Mesh(new THREE.BoxGeometry(0.6, 4.2, 0.6), new THREE.MeshStandardMaterial({ color:0x6A4A3A, roughness:1 }));
        col.position.set(zone.x + ox, 2.1, zone.z + oz);
        g.add(col);
      });
      break;
    }

    case 'kitchen-cafe': {
      // L-shaped counter + barstools + coffee machine
      const counterMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.85 });
      const topMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.9 });
      const stoolMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0xE0A13B, emissive:0xE0A13B, emissiveIntensity:0.4 });
      // Back counter (L-shape: two segments)
      const c1 = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.7, 0.9, 0.7), counterMat);
      c1.position.set(zone.x, 0.45, zone.z - zone.d/2 + 0.5);
      g.add(c1);
      const t1 = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.7 + 0.1, 0.06, 0.8), topMat);
      t1.position.set(zone.x, 0.92, zone.z - zone.d/2 + 0.5);
      g.add(t1);
      const c2 = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.9, zone.d * 0.4), counterMat);
      c2.position.set(zone.x - zone.w/2 + 0.5, 0.45, zone.z - 0.5);
      g.add(c2);
      const t2 = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.06, zone.d * 0.4 + 0.1), topMat);
      t2.position.set(zone.x - zone.w/2 + 0.5, 0.92, zone.z - 0.5);
      g.add(t2);
      // Coffee machine (box with accent light)
      const cm = new THREE.Mesh(new THREE.BoxGeometry(1.1, 0.7, 0.6), counterMat);
      cm.position.set(zone.x + 0.5, 1.3, zone.z - zone.d/2 + 0.5);
      g.add(cm);
      const cmL = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.08, 0.05), accentMat);
      cmL.position.set(zone.x + 0.5, 1.55, zone.z - zone.d/2 + 0.78);
      g.add(cmL);
      // Bar stools at the counter
      for (let i = 0; i < 4; i++) {
        const sx = zone.x - 2 + i * 1.4;
        const stool = new THREE.Mesh(new THREE.CylinderGeometry(0.28, 0.28, 0.7, 10), stoolMat);
        stool.position.set(sx, 0.35, zone.z - zone.d/2 + 1.8);
        g.add(stool);
      }
      // Small dining table + 2 chairs in the open area
      const table = new THREE.Mesh(new THREE.CylinderGeometry(0.7, 0.7, 0.08, 16), topMat);
      table.position.set(zone.x + 1.5, 0.78, zone.z + 1.5);
      g.add(table);
      const tableLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.18, 0.74, 8), new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.5, metalness:0.5 }));
      tableLeg.position.set(zone.x + 1.5, 0.4, zone.z + 1.5);
      g.add(tableLeg);
      [-1.0, 1.0].forEach(ox => {
        const ch = new THREE.Mesh(new THREE.BoxGeometry(0.55, 0.8, 0.55), stoolMat);
        ch.position.set(zone.x + 1.5 + ox, 0.4, zone.z + 1.5);
        g.add(ch);
      });
      break;
    }

    case 'event-space': {
      // Open floor with rows of stacked chairs against the back wall + a projector screen
      const chairMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const screenMat = new THREE.MeshStandardMaterial({ color:0xF6F7F9, roughness:0.7 });
      const slideMat = new THREE.MeshStandardMaterial({ color:0x3A6FF8, emissive:0x3A6FF8, emissiveIntensity:0.2 });
      // Projector screen at one end
      const screen = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.55, 1.8, 0.06), screenMat);
      screen.position.set(zone.x, 2.0, zone.z - zone.d/2 + 0.3);
      g.add(screen);
      const slide = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.55 - 0.2, 1.6, 0.02), slideMat);
      slide.position.set(zone.x, 2.0, zone.z - zone.d/2 + 0.34);
      g.add(slide);
      // Stacks of chairs against the back wall
      for (let i = 0; i < 4; i++) {
        const stack = new THREE.Mesh(new THREE.BoxGeometry(0.55, 1.6, 0.55), chairMat);
        stack.position.set(zone.x - zone.w/2 + 1 + i * 0.75, 0.8, zone.z + zone.d/2 - 0.8);
        g.add(stack);
      }
      // Rolling AV cart
      const cart = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.9, 0.5), new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.9 }));
      cart.position.set(zone.x + zone.w/2 - 1, 0.45, zone.z + 1);
      g.add(cart);
      break;
    }

    case 'core': {
      // Lift core + stair core
      const wallMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.95 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.5 });
      // Lift shaft box
      const lift = new THREE.Mesh(new THREE.BoxGeometry(2.2, 3.6, 2.2), wallMat);
      lift.position.set(zone.x - 1.5, 1.8, zone.z);
      g.add(lift);
      // Call button strip
      const call = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.5, 0.2), accentMat);
      call.position.set(zone.x - 0.38, 1.3, zone.z);
      g.add(call);
      // Stair core
      for (let i = 0; i < 6; i++) {
        const step = new THREE.Mesh(new THREE.BoxGeometry(2.0, 0.18, 0.5), new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.85 }));
        step.position.set(zone.x + 2.5, 0.09 + i * 0.18, zone.z - 1.5 + i * 0.5);
        g.add(step);
      }
      break;
    }

    case 'office': {
      // 2 rows of 3 desks each, with monitors + chairs, plus a round meeting table
      const deskMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const monitorMat = new THREE.MeshStandardMaterial({ color:0x0A0F1F, roughness:0.8 });
      const monitorScreenMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.4 });
      const chairMat = new THREE.MeshStandardMaterial({ color:0x232A48, roughness:1 });
      const desksX = [-7, -2, 3];
      const rowsZ = [-3, 2];
      desksX.forEach(ox => {
        rowsZ.forEach((oz, ri) => {
          const desk = new THREE.Mesh(new THREE.BoxGeometry(3.0, 0.08, 1.4), deskMat);
          desk.position.set(zone.x + ox, 0.78, zone.z + oz);
          g.add(desk);
          // Legs
          [[-1.3,-0.55],[1.3,-0.55],[-1.3,0.55],[1.3,0.55]].forEach(([lx,lz]) => {
            const leg = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.74, 0.08), matMetal);
            leg.position.set(zone.x + ox + lx, 0.41, zone.z + oz + lz);
            g.add(leg);
          });
          // Monitor stand + screen
          const stand = new THREE.Mesh(new THREE.BoxGeometry(0.18, 0.32, 0.18), matMetal);
          stand.position.set(zone.x + ox, 0.98, zone.z + oz - 0.3);
          g.add(stand);
          const screen = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.85, 0.06), monitorMat);
          screen.position.set(zone.x + ox, 1.45, zone.z + oz - 0.3);
          g.add(screen);
          const screenFace = new THREE.Mesh(new THREE.BoxGeometry(1.3, 0.75, 0.005), monitorScreenMat);
          screenFace.position.set(zone.x + ox, 1.45, zone.z + oz - 0.27);
          g.add(screenFace);
          // Office chair
          const chairSeat = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.4, 0.12, 12), chairMat);
          chairSeat.position.set(zone.x + ox, 0.55, zone.z + oz + (ri===0 ? 1.4 : 1.4));
          g.add(chairSeat);
          const chairBack = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.9, 0.12), chairMat);
          chairBack.position.set(zone.x + ox, 1.05, zone.z + oz + (ri===0 ? 1.85 : 1.85));
          g.add(chairBack);
          const chairLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 0.4, 8), matMetal);
          chairLeg.position.set(zone.x + ox, 0.3, zone.z + oz + 1.4);
          g.add(chairLeg);
        });
      });
      // Round meeting table
      const mtg = new THREE.Mesh(new THREE.CylinderGeometry(1.4, 1.4, 0.08, 24), deskMat);
      mtg.position.set(zone.x + 8, 0.78, zone.z + 5);
      g.add(mtg);
      const mtgLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.3, 0.74, 12), matMetal);
      mtgLeg.position.set(zone.x + 8, 0.37, zone.z + 5);
      g.add(mtgLeg);
      // Meeting chairs around it
      for (let a = 0; a < 4; a++) {
        const ang = (a / 4) * Math.PI * 2;
        const cx = zone.x + 8 + Math.cos(ang) * 2.2;
        const cz = zone.z + 5 + Math.sin(ang) * 2.2;
        const c = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.8, 0.7), chairMat);
        c.position.set(cx, 0.4, cz);
        g.add(c);
      }
      break;
    }

    // ===========================================================
    // Form Property — estate, retail, public realm, housing
    // ===========================================================
    case 'public-realm': {
      // Outdoor plaza with trees, benches, signage totem
      const greenMat = new THREE.MeshStandardMaterial({ color:0x4A6650, roughness:1 });
      const benchMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const totemMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.9 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.5 });
      // Tree trunks + foliage
      [[-3, -5], [3, -5], [-3, 5], [3, 5]].forEach(([ox, oz]) => {
        const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.15, 1.4, 8), new THREE.MeshStandardMaterial({ color:0x6A4A3A, roughness:1 }));
        trunk.position.set(zone.x + ox, 0.7, zone.z + oz);
        g.add(trunk);
        const foliage = new THREE.Mesh(new THREE.SphereGeometry(0.9, 12, 10), greenMat);
        foliage.position.set(zone.x + ox, 2.0, zone.z + oz);
        g.add(foliage);
      });
      // Benches
      [[-0, -3], [0, 3]].forEach(([ox, oz]) => {
        const bench = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.4, 0.6), benchMat);
        bench.position.set(zone.x + ox, 0.2, zone.z + oz);
        g.add(bench);
      });
      // Signage totem
      const totem = new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.4, 0.5), totemMat);
      totem.position.set(zone.x, 1.2, zone.z);
      g.add(totem);
      const sign = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.8, 0.4), accentMat);
      sign.position.set(zone.x + 0.27, 1.6, zone.z);
      g.add(sign);
      break;
    }

    case 'estate-block': {
      // Multi-storey building footprint with windows
      const bldMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.92 });
      const winMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.25 });
      const roofMat = new THREE.MeshStandardMaterial({ color:0x1A2240, roughness:0.95 });
      const building = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.7, 4.0, zone.d * 0.6), bldMat);
      building.position.set(zone.x, 2.0, zone.z);
      g.add(building);
      const top = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.72, 0.18, zone.d * 0.62), roofMat);
      top.position.set(zone.x, 4.09, zone.z);
      g.add(top);
      // Window grid on front face
      const cols = 6, rows = 3;
      for (let c = 0; c < cols; c++) {
        for (let r = 0; r < rows; r++) {
          const win = new THREE.Mesh(new THREE.BoxGeometry((zone.w * 0.7)/cols - 0.4, 0.6, 0.02), winMat);
          win.position.set(
            zone.x - zone.w*0.35 + ((zone.w*0.7)/cols)*(c + 0.5),
            0.9 + r * 1.1,
            zone.z + zone.d * 0.3 + 0.01
          );
          g.add(win);
        }
      }
      break;
    }

    case 'retail': {
      // Storefronts along the front face + small shop interiors
      const shopMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.95 });
      const glassMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.3, transparent:true, opacity:0.85 });
      const shops = 4;
      const shopW = (zone.w - 2) / shops;
      for (let i = 0; i < shops; i++) {
        const sx = zone.x - zone.w/2 + 1 + shopW * (i + 0.5);
        // Shop back wall
        const back = new THREE.Mesh(new THREE.BoxGeometry(shopW - 0.4, 3.0, 0.2), shopMat);
        back.position.set(sx, 1.5, zone.z - zone.d * 0.35);
        g.add(back);
        // Storefront glass
        const front = new THREE.Mesh(new THREE.BoxGeometry(shopW - 0.6, 2.4, 0.08), glassMat);
        front.position.set(sx, 1.2, zone.z + zone.d * 0.35);
        g.add(front);
        // Display fixture
        const display = new THREE.Mesh(new THREE.BoxGeometry(shopW * 0.4, 0.8, 0.8), shopMat);
        display.position.set(sx, 0.4, zone.z);
        g.add(display);
      }
      break;
    }

    case 'car-park': {
      // Painted parking bays + a few cars + EV charger pillars
      const lineMat = new THREE.MeshStandardMaterial({ color:0xFAFAF5, roughness:0.7 });
      const carMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:0.7 });
      const evMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.45 });
      const cols = 8;
      const bayW = zone.w * 0.85 / cols;
      for (let c = 0; c <= cols; c++) {
        const lx = zone.x - zone.w * 0.425 + c * bayW;
        const line = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.04, zone.d * 0.6), lineMat);
        line.position.set(lx, 0.06, zone.z);
        g.add(line);
      }
      // Cars in alternating bays
      for (let i = 0; i < cols; i++) {
        if (i % 2 === 0) continue;
        const cx_ = zone.x - zone.w * 0.425 + bayW * (i + 0.5);
        const car = new THREE.Mesh(new THREE.BoxGeometry(bayW - 0.6, 0.7, zone.d * 0.45), carMat);
        car.position.set(cx_, 0.35, zone.z);
        g.add(car);
      }
      // EV pillars
      [[-zone.w/2+1, -zone.d*0.35], [-zone.w/2+1, zone.d*0.35]].forEach(([ox, oz]) => {
        const p = new THREE.Mesh(new THREE.BoxGeometry(0.3, 1.6, 0.3), new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.9 }));
        p.position.set(zone.x + ox, 0.8, zone.z + oz);
        const led = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.4, 0.18), evMat);
        led.position.set(zone.x + ox + 0.17, 1.2, zone.z + oz);
        g.add(p, led);
      });
      break;
    }

    case 'energy-centre': {
      // Large heat pump arrays + buffer tanks
      const arrayMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.9 });
      const tankMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:0.6, metalness:0.3 });
      const railMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.5 });
      // Two heat pump banks
      [-5, 5].forEach((oz) => {
        const block = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.65, 2.6, 2.2), arrayMat);
        block.position.set(zone.x, 1.3, zone.z + oz);
        g.add(block);
        // Fan circles on top
        for (let i = 0; i < 4; i++) {
          const fx = zone.x - zone.w * 0.25 + (zone.w * 0.5)/3 * i;
          const fan = new THREE.Mesh(new THREE.CylinderGeometry(0.45, 0.45, 0.1, 16), new THREE.MeshStandardMaterial({ color:0x0E1426, roughness:0.95 }));
          fan.position.set(fx, 2.65, zone.z + oz);
          g.add(fan);
        }
      });
      // Tall buffer tanks
      [[-zone.w/2+2, 0], [zone.w/2-2, 0]].forEach(([ox, oz]) => {
        const tank = new THREE.Mesh(new THREE.CylinderGeometry(0.9, 0.9, 3.6, 16), tankMat);
        tank.position.set(zone.x + ox, 1.8, zone.z + oz);
        g.add(tank);
      });
      // Overhead pipe rack
      const pipe = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, zone.w * 0.8, 12), railMat);
      pipe.rotation.z = Math.PI / 2;
      pipe.position.set(zone.x, 3.8, zone.z);
      g.add(pipe);
      break;
    }

    case 'roof-terrace': {
      // Outdoor terrace: paving + planters + outdoor seating
      const paveMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const greenMat = new THREE.MeshStandardMaterial({ color:0x4A6650, roughness:1 });
      const seatMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      // Stepped paving feature
      const step = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.7, 0.12, zone.d * 0.4), paveMat);
      step.position.set(zone.x, 0.06, zone.z);
      g.add(step);
      // Planters along the back
      [[-4, -zone.d/2 + 1.5], [0, -zone.d/2 + 1.5], [4, -zone.d/2 + 1.5]].forEach(([ox, oz]) => {
        const planter = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.6, 0.8), paveMat);
        planter.position.set(zone.x + ox, 0.3, zone.z + oz);
        const greenery = new THREE.Mesh(new THREE.SphereGeometry(0.6, 10, 8), greenMat);
        greenery.position.set(zone.x + ox, 0.9, zone.z + oz);
        g.add(planter, greenery);
      });
      // Bench seating cluster
      [-2, 2].forEach((ox) => {
        const bench = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.4, 0.6), seatMat);
        bench.position.set(zone.x + ox, 0.22, zone.z + zone.d * 0.2);
        g.add(bench);
      });
      // Fire pit feature
      const pit = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.8, 0.3, 16), paveMat);
      pit.position.set(zone.x, 0.15, zone.z + zone.d * 0.2);
      const flame = new THREE.Mesh(new THREE.SphereGeometry(0.35, 10, 8), new THREE.MeshStandardMaterial({ color:0xE0A13B, emissive:0xE0A13B, emissiveIntensity:0.8 }));
      flame.position.set(zone.x, 0.45, zone.z + zone.d * 0.2);
      g.add(pit, flame);
      break;
    }

    case 'living': {
      // Sofa + coffee table + TV unit
      const sofaMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const tableMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:1 });
      const tvMat = new THREE.MeshStandardMaterial({ color:0x0A0F1F, roughness:0.8 });
      const screenMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.3 });
      const rugMat = new THREE.MeshStandardMaterial({ color:0x6A4A3A, roughness:1 });
      // Rug
      const rug = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.55, 0.04, zone.d * 0.55), rugMat);
      rug.position.set(zone.x, 0.04, zone.z);
      g.add(rug);
      // Sofa (long L-shape)
      const sofaSeat = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.55, 0.5, 0.9), sofaMat);
      sofaSeat.position.set(zone.x, 0.3, zone.z - zone.d * 0.25);
      const sofaBack = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.55, 0.8, 0.2), sofaMat);
      sofaBack.position.set(zone.x, 0.7, zone.z - zone.d * 0.25 - 0.55);
      g.add(sofaSeat, sofaBack);
      // Coffee table
      const ct = new THREE.Mesh(new THREE.BoxGeometry(2.0, 0.08, 1.0), tableMat);
      ct.position.set(zone.x, 0.4, zone.z);
      g.add(ct);
      // TV unit
      const tvUnit = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.4, 0.5), sofaMat);
      tvUnit.position.set(zone.x, 0.2, zone.z + zone.d * 0.35);
      const tv = new THREE.Mesh(new THREE.BoxGeometry(2.0, 1.1, 0.06), tvMat);
      tv.position.set(zone.x, 1.0, zone.z + zone.d * 0.35);
      const tvScreen = new THREE.Mesh(new THREE.BoxGeometry(1.9, 1.0, 0.01), screenMat);
      tvScreen.position.set(zone.x, 1.0, zone.z + zone.d * 0.35 + 0.04);
      g.add(tvUnit, tv, tvScreen);
      // Floor lamp
      const lampPole = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 1.8, 8), new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.5 }));
      lampPole.position.set(zone.x - zone.w*0.35, 0.9, zone.z - zone.d * 0.2);
      const lampShade = new THREE.Mesh(new THREE.ConeGeometry(0.3, 0.4, 12), new THREE.MeshStandardMaterial({ color:0xF1ECDD, emissive:0xE0A13B, emissiveIntensity:0.3 }));
      lampShade.position.set(zone.x - zone.w*0.35, 2.0, zone.z - zone.d * 0.2);
      g.add(lampPole, lampShade);
      break;
    }

    case 'eco-kitchen': {
      // L-shaped kitchen with cabinets, hob, sink, fridge, and an island
      const cabMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const topMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.9 });
      const metalMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.5 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.4 });
      // Counter along the back wall
      const cabBack = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.75, 0.9, 0.7), cabMat);
      cabBack.position.set(zone.x, 0.45, zone.z - zone.d * 0.35);
      const cabBackTop = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.75 + 0.04, 0.06, 0.74), topMat);
      cabBackTop.position.set(zone.x, 0.93, zone.z - zone.d * 0.35);
      g.add(cabBack, cabBackTop);
      // Hob on the counter
      const hob = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.04, 0.5), new THREE.MeshStandardMaterial({ color:0x0A0F1F, roughness:0.9 }));
      hob.position.set(zone.x - 1.5, 0.98, zone.z - zone.d * 0.35);
      const burner1 = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.02, 12), accentMat);
      burner1.position.set(zone.x - 1.7, 1.0, zone.z - zone.d * 0.35);
      const burner2 = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.02, 12), metalMat);
      burner2.position.set(zone.x - 1.3, 1.0, zone.z - zone.d * 0.35);
      g.add(hob, burner1, burner2);
      // Sink
      const sink = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.04, 0.5), metalMat);
      sink.position.set(zone.x + 1.0, 0.97, zone.z - zone.d * 0.35);
      g.add(sink);
      // Fridge (tall)
      const fridge = new THREE.Mesh(new THREE.BoxGeometry(0.8, 1.9, 0.7), new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.85 }));
      fridge.position.set(zone.x + zone.w*0.32, 0.95, zone.z - zone.d * 0.35);
      g.add(fridge);
      // Island
      const island = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.9, 1.2), cabMat);
      island.position.set(zone.x, 0.45, zone.z + zone.d * 0.1);
      const islandTop = new THREE.Mesh(new THREE.BoxGeometry(2.64, 0.06, 1.24), topMat);
      islandTop.position.set(zone.x, 0.93, zone.z + zone.d * 0.1);
      g.add(island, islandTop);
      // Pendant lights over island
      [-0.8, 0.8].forEach((ox) => {
        const cord = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 1.6, 6), metalMat);
        cord.position.set(zone.x + ox, 2.0, zone.z + zone.d * 0.1);
        const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.16, 10, 8), new THREE.MeshStandardMaterial({ color:0xE0A13B, emissive:0xE0A13B, emissiveIntensity:0.7 }));
        bulb.position.set(zone.x + ox, 1.2, zone.z + zone.d * 0.1);
        g.add(cord, bulb);
      });
      break;
    }

    case 'bedroom': {
      // Double bed, bedside tables, wardrobe, lamp
      const bedMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const sheetMat = new THREE.MeshStandardMaterial({ color:0xE6E8EE, roughness:1 });
      const cabMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const wardrobeMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      // Bed base
      const bedBase = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.4, 3.2), bedMat);
      bedBase.position.set(zone.x, 0.2, zone.z);
      g.add(bedBase);
      // Mattress / duvet
      const duvet = new THREE.Mesh(new THREE.BoxGeometry(2.3, 0.18, 3.0), sheetMat);
      duvet.position.set(zone.x, 0.52, zone.z);
      g.add(duvet);
      // Pillows
      [-0.5, 0.5].forEach((ox) => {
        const pillow = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.15, 0.5), sheetMat);
        pillow.position.set(zone.x + ox, 0.7, zone.z - zone.d * 0.27);
        g.add(pillow);
      });
      // Bedside tables
      [-1.6, 1.6].forEach((ox) => {
        const bs = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.6, 0.6), cabMat);
        bs.position.set(zone.x + ox, 0.3, zone.z - zone.d * 0.3);
        g.add(bs);
      });
      // Wardrobe
      const wardrobe = new THREE.Mesh(new THREE.BoxGeometry(2.4, 2.4, 0.7), wardrobeMat);
      wardrobe.position.set(zone.x, 1.2, zone.z + zone.d * 0.35);
      g.add(wardrobe);
      // Wardrobe handles
      [-0.4, 0.4].forEach((ox) => {
        const handle = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.4, 0.04), new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.6 }));
        handle.position.set(zone.x + ox, 1.2, zone.z + zone.d * 0.35 - 0.36);
        g.add(handle);
      });
      break;
    }

    case 'bathroom': {
      // Bath, sink, toilet, shower screen, towel rail
      const tileMat = new THREE.MeshStandardMaterial({ color:0xE0E4ED, roughness:0.6 });
      const fixtureMat = new THREE.MeshStandardMaterial({ color:0xF6F7F9, roughness:0.4 });
      const metalMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.5 });
      // Bath (large rectangle)
      const bath = new THREE.Mesh(new THREE.BoxGeometry(1.7, 0.5, 0.8), fixtureMat);
      bath.position.set(zone.x - zone.w*0.25, 0.25, zone.z);
      g.add(bath);
      // Sink
      const sinkBase = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.85, 0.5), tileMat);
      sinkBase.position.set(zone.x + zone.w*0.25, 0.42, zone.z - zone.d*0.3);
      const sinkBowl = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.1, 0.4), fixtureMat);
      sinkBowl.position.set(zone.x + zone.w*0.25, 0.9, zone.z - zone.d*0.3);
      g.add(sinkBase, sinkBowl);
      // Toilet
      const toilet = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.4, 0.7), fixtureMat);
      toilet.position.set(zone.x + zone.w*0.25, 0.2, zone.z + zone.d*0.1);
      const tank = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.6, 0.2), fixtureMat);
      tank.position.set(zone.x + zone.w*0.25, 0.7, zone.z + zone.d*0.1 + 0.25);
      g.add(toilet, tank);
      // Mirror over sink
      const mirror = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.7, 0.04), new THREE.MeshStandardMaterial({ color:0xE0E4ED, roughness:0.3, metalness:0.4 }));
      mirror.position.set(zone.x + zone.w*0.25, 1.6, zone.z - zone.d*0.35);
      g.add(mirror);
      // Towel rail
      const rail = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 1.2, 8), metalMat);
      rail.rotation.z = Math.PI / 2;
      rail.position.set(zone.x - zone.w*0.25, 1.4, zone.z - zone.d*0.4);
      g.add(rail);
      break;
    }

    case 'eco-plant': {
      // Heat pump indoor unit + buffer tank + solar inverter — TEO-blue accent
      const bodyMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.92 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.5 });
      const metalMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.5, metalness:0.4 });
      // Heat pump indoor unit (slim white cabinet)
      const hp = new THREE.Mesh(new THREE.BoxGeometry(1.6, 2.0, 0.7), new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.85 }));
      hp.position.set(zone.x - zone.w * 0.25, 1.0, zone.z - zone.d * 0.25);
      g.add(hp);
      const hpLed = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.04, 0.02), accentMat);
      hpLed.position.set(zone.x - zone.w * 0.25, 1.85, zone.z - zone.d * 0.25 + 0.36);
      g.add(hpLed);
      // Buffer tank (cylinder)
      const tank = new THREE.Mesh(new THREE.CylinderGeometry(0.55, 0.55, 2.0, 16), bodyMat);
      tank.position.set(zone.x, 1.0, zone.z - zone.d * 0.25);
      g.add(tank);
      // Solar inverter
      const inv = new THREE.Mesh(new THREE.BoxGeometry(0.7, 1.0, 0.3), bodyMat);
      inv.position.set(zone.x + zone.w * 0.25, 0.8, zone.z - zone.d * 0.25);
      const invLed = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.06, 0.02), accentMat);
      invLed.position.set(zone.x + zone.w * 0.25, 1.2, zone.z - zone.d * 0.25 + 0.16);
      g.add(inv, invLed);
      // Pipework
      [-0.15, 0.15].forEach((oy) => {
        const pipe = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, zone.w * 0.55, 8), metalMat);
        pipe.rotation.z = Math.PI / 2;
        pipe.position.set(zone.x, 2.3 + oy, zone.z - zone.d * 0.25);
        g.add(pipe);
      });
      break;
    }

    case 'solar-roof': {
      // Array of tilted solar panels — TEO accent grid
      const panelMat = new THREE.MeshStandardMaterial({ color:0x0E1426, roughness:0.5, metalness:0.6 });
      const frameMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.6 });
      const lineMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.25 });
      const rows = Math.max(2, Math.floor(zone.d / 4));
      const cols = Math.max(3, Math.floor(zone.w / 4));
      const panelW = (zone.w - 1) / cols - 0.3;
      const panelD = (zone.d - 1) / rows - 0.5;
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const px = zone.x - zone.w/2 + 0.5 + ((zone.w - 1)/cols) * (c + 0.5);
          const pz = zone.z - zone.d/2 + 0.5 + ((zone.d - 1)/rows) * (r + 0.5);
          // Tilted panel
          const panel = new THREE.Mesh(new THREE.BoxGeometry(panelW, 0.06, panelD), panelMat);
          panel.position.set(px, 0.4, pz);
          panel.rotation.x = -Math.PI / 9;
          g.add(panel);
          // Frame
          const frame = new THREE.Mesh(new THREE.BoxGeometry(panelW + 0.04, 0.08, panelD + 0.04), frameMat);
          frame.position.set(px, 0.39, pz);
          frame.rotation.x = -Math.PI / 9;
          g.add(frame);
          // Cell-grid suggestion (4 thin lines)
          for (let i = 0; i < 3; i++) {
            const line = new THREE.Mesh(new THREE.BoxGeometry(panelW * 0.95, 0.005, 0.02), lineMat);
            line.position.set(px, 0.43, pz - panelD/2 + ((i + 1) * panelD)/4);
            line.rotation.x = -Math.PI / 9;
            g.add(line);
          }
          // Support legs
          [-panelW/2 + 0.2, panelW/2 - 0.2].forEach((ox) => {
            const leg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.4, 0.06), frameMat);
            leg.position.set(px + ox, 0.2, pz + panelD/2 - 0.2);
            g.add(leg);
          });
        }
      }
      break;
    }

    case 'apartment-row': {
      // Strip of apartments with internal partitions, bed + small kitchen each
      const wallMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.95 });
      const bedMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const sheetMat = new THREE.MeshStandardMaterial({ color:0xE6E8EE, roughness:1 });
      const cabMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const topMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.9 });
      const apts = 4;
      const aptW = (zone.w - 0.6) / apts;
      // Partitions between apartments
      for (let i = 0; i <= apts; i++) {
        const px = zone.x - zone.w/2 + 0.3 + i * aptW;
        const wall = new THREE.Mesh(new THREE.BoxGeometry(0.14, 2.4, zone.d * 0.85), wallMat);
        wall.position.set(px, 1.2, zone.z);
        g.add(wall);
      }
      // Per-apartment content
      for (let i = 0; i < apts; i++) {
        const ax = zone.x - zone.w/2 + 0.3 + aptW * (i + 0.5);
        // Bed
        const bed = new THREE.Mesh(new THREE.BoxGeometry(aptW * 0.45, 0.4, 1.6), bedMat);
        bed.position.set(ax, 0.2, zone.z - zone.d * 0.2);
        const duvet = new THREE.Mesh(new THREE.BoxGeometry(aptW * 0.42, 0.16, 1.5), sheetMat);
        duvet.position.set(ax, 0.5, zone.z - zone.d * 0.2);
        g.add(bed, duvet);
        // Mini kitchen counter
        const k = new THREE.Mesh(new THREE.BoxGeometry(aptW * 0.6, 0.8, 0.5), cabMat);
        k.position.set(ax, 0.4, zone.z + zone.d * 0.25);
        const kt = new THREE.Mesh(new THREE.BoxGeometry(aptW * 0.6 + 0.04, 0.05, 0.54), topMat);
        kt.position.set(ax, 0.83, zone.z + zone.d * 0.25);
        g.add(k, kt);
        // Apartment number indicator (small accent panel above the door side)
        const indicator = new THREE.Mesh(
          new THREE.BoxGeometry(aptW * 0.3, 0.05, 0.03),
          new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.4 })
        );
        indicator.position.set(ax, 2.2, zone.z + zone.d * 0.42);
        g.add(indicator);
      }
      break;
    }

    case 'communal-lobby': {
      // Lobby with reception desk, seating, mailboxes
      const deskMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.92 });
      const topMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.9 });
      const seatMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const accentMat = new THREE.MeshStandardMaterial({ color:0x7A9CFF, emissive:0x3A6FF8, emissiveIntensity:0.4 });
      // Reception desk
      const desk = new THREE.Mesh(new THREE.BoxGeometry(3.4, 1.1, 0.9), deskMat);
      desk.position.set(zone.x - zone.w * 0.18, 0.55, zone.z - zone.d * 0.3);
      const deskTop = new THREE.Mesh(new THREE.BoxGeometry(3.6, 0.06, 1.0), topMat);
      deskTop.position.set(zone.x - zone.w * 0.18, 1.13, zone.z - zone.d * 0.3);
      g.add(desk, deskTop);
      // Mailbox wall
      const mailbox = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.35, 2.0, 0.3), deskMat);
      mailbox.position.set(zone.x + zone.w * 0.25, 1.0, zone.z - zone.d * 0.4);
      g.add(mailbox);
      for (let r = 0; r < 4; r++) {
        for (let c = 0; c < 5; c++) {
          const slot = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.06, 0.3, 0.02), topMat);
          slot.position.set(
            zone.x + zone.w * 0.25 - zone.w * 0.13 + c * (zone.w * 0.065),
            0.4 + r * 0.45,
            zone.z - zone.d * 0.4 + 0.16
          );
          g.add(slot);
        }
      }
      // Sofa
      const sofa = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.5, 0.9), seatMat);
      sofa.position.set(zone.x - zone.w * 0.2, 0.25, zone.z + zone.d * 0.3);
      g.add(sofa);
      // Coffee table
      const ct = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.6, 0.08, 16), topMat);
      ct.position.set(zone.x - zone.w * 0.2, 0.4, zone.z + 0.4);
      g.add(ct);
      // Accent feature wall strip
      const strip = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.5, 0.08, 0.04), accentMat);
      strip.position.set(zone.x - zone.w * 0.18, 1.8, zone.z - zone.d * 0.42);
      g.add(strip);
      break;
    }

    case 'communal-room': {
      // Long communal table + chairs + small kitchenette
      const tableMat = new THREE.MeshStandardMaterial({ color:0xF1ECDD, roughness:0.9 });
      const chairMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:1 });
      const cabMat = new THREE.MeshStandardMaterial({ color:0x2E3658, roughness:0.95 });
      const table = new THREE.Mesh(new THREE.BoxGeometry(zone.w * 0.6, 0.1, 1.4), tableMat);
      table.position.set(zone.x - zone.w * 0.05, 0.78, zone.z);
      g.add(table);
      // Chairs along both sides
      for (let i = 0; i < 6; i++) {
        const x = zone.x - zone.w * 0.05 - zone.w * 0.27 + i * (zone.w * 0.1);
        [-0.95, 0.95].forEach((oz) => {
          const ch = new THREE.Mesh(new THREE.BoxGeometry(0.55, 0.8, 0.55), chairMat);
          ch.position.set(x, 0.4, zone.z + oz);
          g.add(ch);
        });
      }
      // Mini kitchenette at the end
      const k = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.9, 0.7), cabMat);
      k.position.set(zone.x + zone.w * 0.35, 0.45, zone.z - zone.d * 0.3);
      const kt = new THREE.Mesh(new THREE.BoxGeometry(2.24, 0.06, 0.74), tableMat);
      kt.position.set(zone.x + zone.w * 0.35, 0.93, zone.z - zone.d * 0.3);
      g.add(k, kt);
      break;
    }

    case 'utility': {
      // Bike racks + storage cages
      const rackMat = new THREE.MeshStandardMaterial({ color:PAL.prop.metal, roughness:0.4, metalness:0.5 });
      const bikeMat = new THREE.MeshStandardMaterial({ color:0x3A435E, roughness:0.6 });
      const cageMat = new THREE.MeshStandardMaterial({ color:0x2A325A, roughness:0.95 });
      // Bike racks
      for (let i = 0; i < 8; i++) {
        const bx = zone.x - zone.w * 0.4 + i * (zone.w * 0.7) / 7;
        // Vertical hook
        const hook = new THREE.Mesh(new THREE.BoxGeometry(0.05, 1.2, 0.1), rackMat);
        hook.position.set(bx, 0.6, zone.z - zone.d * 0.2);
        g.add(hook);
        // Bike silhouette (simple)
        if (i % 2 === 0) {
          const frame = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.6, 1.0), bikeMat);
          frame.position.set(bx, 0.6, zone.z - zone.d * 0.2 + 0.55);
          g.add(frame);
        }
      }
      // Storage cages
      [-zone.w*0.25, 0, zone.w*0.25].forEach((ox) => {
        const cage = new THREE.Mesh(new THREE.BoxGeometry(2.4, 2.0, 1.4), cageMat);
        cage.position.set(zone.x + ox, 1.0, zone.z + zone.d * 0.25);
        g.add(cage);
      });
      break;
    }
  }
  return g;
}

// =============================================================
// Component
// =============================================================
const Twin3D = ({ devices, site, selectedId, onSelect, floor }) => {
  const containerRef = React.useRef(null);
  const overlayRef   = React.useRef(null);
  const stateRef     = React.useRef({});
  const [hovered, setHovered] = React.useState(null);
  const [roof, setRoof] = React.useState(false);
  const selectedRef = React.useRef(selectedId);
  React.useEffect(() => { selectedRef.current = selectedId; }, [selectedId]);

  // Init scene once
  React.useEffect(() => {
    const container = containerRef.current;
    if (!container || !window.THREE) return;
    const THREE = window.THREE;
    const W = container.clientWidth || 900;
    const H = container.clientHeight || 600;

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x0A0F1F);
    scene.fog = new THREE.Fog(0x0A0F1F, 90, 240);

    const camera = new THREE.PerspectiveCamera(36, W/H, 0.1, 500);
    camera.position.set(58, 48, 78);

    const renderer = new THREE.WebGLRenderer({ antialias:true, alpha:false });
    renderer.setSize(W, H);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    container.appendChild(renderer.domElement);

    // Lighting — soft, slightly directional, low-poly friendly
    scene.add(new THREE.AmbientLight(0xB4C0DA, 0.55));
    const key = new THREE.DirectionalLight(0xFFFFFF, 0.5);
    key.position.set(50, 90, 30);
    scene.add(key);
    const fill = new THREE.DirectionalLight(0x7A9CFF, 0.3);
    fill.position.set(-40, 50, -30);
    scene.add(fill);
    const rim = new THREE.DirectionalLight(0x3A6FF8, 0.18);
    rim.position.set(0, 20, -80);
    scene.add(rim);

    // Ground (outside the building)
    const ground = new THREE.Mesh(
      new THREE.PlaneGeometry(260, 260),
      new THREE.MeshStandardMaterial({ color:PAL.ground, roughness:1, metalness:0 })
    );
    ground.rotation.x = -Math.PI/2;
    ground.position.y = -0.02;
    scene.add(ground);

    // Subtle grid hint
    const grid = new THREE.GridHelper(260, 52, 0x162038, 0x0E1224);
    grid.material.transparent = true;
    grid.material.opacity = 0.5;
    scene.add(grid);

    // Orbit controls
    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.target.set(0, 1, 0);
    controls.enableDamping = true;
    controls.dampingFactor = 0.08;
    controls.maxPolarAngle = Math.PI / 2.05;
    controls.minDistance = 30;
    controls.maxDistance = 180;
    controls.enablePan = true;

    // Raycaster
    const raycaster = new THREE.Raycaster();
    const pointer = new THREE.Vector2();

    const onPointerMove = (e) => {
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
      raycaster.setFromCamera(pointer, camera);
      const meshes = (stateRef.current.deviceMeshes || []).map(d => d.mesh);
      const hit = raycaster.intersectObjects(meshes, false)[0];
      const id = hit ? hit.object.userData.deviceId : null;
      if (id !== stateRef.current.hoveredId) {
        stateRef.current.hoveredId = id;
        setHovered(id);
        renderer.domElement.style.cursor = id ? 'pointer' : 'grab';
      }
    };
    const onClick = (e) => {
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
      raycaster.setFromCamera(pointer, camera);
      const meshes = (stateRef.current.deviceMeshes || []).map(d => d.mesh);
      const hit = raycaster.intersectObjects(meshes, false)[0];
      if (hit && stateRef.current.onSelect) {
        stateRef.current.onSelect(hit.object.userData.device);
      }
    };
    renderer.domElement.addEventListener('pointermove', onPointerMove);
    renderer.domElement.addEventListener('click', onClick);

    // Resize
    const onResize = () => {
      const w = container.clientWidth, h = container.clientHeight;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    };
    window.addEventListener('resize', onResize);
    const ro = new ResizeObserver(onResize);
    ro.observe(container);

    // Render loop
    const clock = new THREE.Clock();
    let raf;
    const tick = () => {
      const t = clock.getElapsedTime();
      (stateRef.current.deviceMeshes || []).forEach(({ mesh, state, ring }) => {
        if (state === 'down' || state === 'idle') {
          mesh.material.emissiveIntensity = 0.25;
        } else {
          const k = 0.5 + 0.5 * Math.sin(t * 2.6);
          mesh.material.emissiveIntensity = 0.45 + k * 0.45;
        }
        if (ring && mesh.userData.deviceId === selectedRef.current) {
          ring.visible = true;
          ring.rotation.z = t * 0.6;
        } else if (ring) {
          ring.visible = false;
        }
      });
      controls.update();
      renderer.render(scene, camera);
      updateOverlay();
      raf = requestAnimationFrame(tick);
    };

    const updateOverlay = () => {
      const overlay = overlayRef.current;
      if (!overlay) return;
      const ws = stateRef.current;
      (ws.roomLabels || []).forEach(({ el, position }) => {
        const v = position.clone().project(camera);
        const W = renderer.domElement.clientWidth;
        const H = renderer.domElement.clientHeight;
        const x = (v.x * 0.5 + 0.5) * W;
        const y = (-v.y * 0.5 + 0.5) * H;
        // Hide labels behind the camera OR too close to a canvas edge
        // (so a label that would clip half-off the canvas just disappears).
        const margin = 60;
        const offscreen = v.z > 1 || x < margin || x > W - margin || y < 12 || y > H - 12;
        if (offscreen) { el.style.display = 'none'; }
        else {
          el.style.display = 'block';
          el.style.transform = `translate(-50%, -50%) translate(${x.toFixed(1)}px, ${y.toFixed(1)}px)`;
        }
      });
      if (ws.selectedLabel) {
        const dm = (ws.deviceMeshes || []).find(d => d.mesh.userData.deviceId === selectedRef.current);
        if (dm) {
          const pos = dm.mesh.position.clone();
          pos.y += 1.6;
          const v = pos.project(camera);
          const W = renderer.domElement.clientWidth;
          const H = renderer.domElement.clientHeight;
          const x = (v.x * 0.5 + 0.5) * W;
          const y = (-v.y * 0.5 + 0.5) * H;
          const margin = 50;
          const offscreen = v.z > 1 || x < margin || x > W - margin || y < 20 || y > H - 10;
          ws.selectedLabel.style.display = offscreen ? 'none' : 'block';
          ws.selectedLabel.style.transform = `translate(-50%, -100%) translate(${x.toFixed(1)}px, ${y.toFixed(1)}px)`;
          ws.selectedLabel.textContent = dm.device.id;
        } else {
          ws.selectedLabel.style.display = 'none';
        }
      }
    };

    stateRef.current = { THREE, scene, camera, renderer, controls, deviceMeshes:[], roomLabels:[], onSelect:null };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', onResize);
      ro.disconnect();
      renderer.domElement.removeEventListener('pointermove', onPointerMove);
      renderer.domElement.removeEventListener('click', onClick);
      controls.dispose();
      renderer.dispose();
      if (renderer.domElement.parentNode === container) container.removeChild(renderer.domElement);
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  React.useEffect(() => { stateRef.current.onSelect = onSelect; }, [onSelect]);

  // Rebuild rooms + devices when site or devices change, or roof toggles
  React.useEffect(() => {
    const ws = stateRef.current;
    if (!ws.scene) return;
    const THREE = ws.THREE;

    ['roomsGroup','devicesGroup','meshGroup','roofGroup'].forEach(k => {
      if (ws[k]) { ws.scene.remove(ws[k]); disposeGroup(ws[k]); ws[k] = null; }
    });
    if (overlayRef.current) {
      ws.roomLabels?.forEach(({ el }) => el.remove());
    }
    ws.roomLabels = [];
    ws.deviceMeshes = [];

    // Filter zones by floor if multi-floor site
    const allZones = ZONE_LAYOUTS_3D[site.id] || [];
    const zones = floor != null
      ? allZones.filter(z => z.floor === floor)
      : allZones;
    const roomsGroup = new THREE.Group();
    const devicesGroup = new THREE.Group();
    const meshGroup = new THREE.Group();
    const roofGroup = new THREE.Group();

    // Materials
    const matExterior = new THREE.MeshStandardMaterial({ color:PAL.exterior, roughness:0.85 });
    const matInterior = new THREE.MeshStandardMaterial({ color:PAL.interior, roughness:0.85 });

    // Building bounds (outer perimeter wrapping all zones)
    if (zones.length) {
      const minX = Math.min(...zones.map(z => z.x - z.w/2)) - 0.5;
      const maxX = Math.max(...zones.map(z => z.x + z.w/2)) + 0.5;
      const minZ = Math.min(...zones.map(z => z.z - z.d/2)) - 0.5;
      const maxZ = Math.max(...zones.map(z => z.z + z.d/2)) + 0.5;
      const totW = maxX - minX, totD = maxZ - minZ;
      const cx = (minX + maxX) / 2, cz = (minZ + maxZ) / 2;
      const wallH = 4.2, wallT = 0.4;

      // Building floor base (just under all zones, so seams are hidden)
      const base = new THREE.Mesh(
        new THREE.BoxGeometry(totW + 0.8, 0.3, totD + 0.8),
        new THREE.MeshStandardMaterial({ color:0x0E1426, roughness:1 })
      );
      base.position.set(cx, -0.15, cz);
      roomsGroup.add(base);

      // Exterior walls — 4 sides
      // N wall
      roomsGroup.add(makeWallSegments(THREE, 'N', cx, minZ, totW, wallH, wallT, false, matExterior));
      // S wall
      roomsGroup.add(makeWallSegments(THREE, 'S', cx, maxZ, totW, wallH, wallT, false, matExterior));
      // W wall
      roomsGroup.add(makeWallSegments(THREE, 'W', minX, cz, totD, wallH, wallT, false, matExterior));
      // E wall
      roomsGroup.add(makeWallSegments(THREE, 'E', maxX, cz, totD, wallH, wallT, false, matExterior));

      // Optional roof (translucent, toggled)
      if (roof) {
        const roofMesh = new THREE.Mesh(
          new THREE.BoxGeometry(totW + 0.4, 0.2, totD + 0.4),
          new THREE.MeshStandardMaterial({ color:PAL.exterior, transparent:true, opacity:0.6, roughness:0.9 })
        );
        roofMesh.position.set(cx, wallH + 0.15, cz);
        roofGroup.add(roofMesh);
      }
    }

    // Per-zone: floor, interior partitions, label
    zones.forEach((z, zi) => {
      // Tinted floor
      const floorColor = PAL.floor[z.kind] || PAL.floor.default;
      const floor = new THREE.Mesh(
        new THREE.PlaneGeometry(z.w, z.d),
        new THREE.MeshStandardMaterial({ color:floorColor, roughness:0.95 })
      );
      floor.rotation.x = -Math.PI/2;
      floor.position.set(z.x, 0.05, z.z);
      roomsGroup.add(floor);

      // Floor tile lines — subtle grid within zone (line segments at 4-unit spacing)
      const gridGeom = new THREE.BufferGeometry();
      const verts = [];
      for (let gx = -z.w/2 + 4; gx < z.w/2; gx += 4) {
        verts.push(z.x + gx, 0.055, z.z - z.d/2);
        verts.push(z.x + gx, 0.055, z.z + z.d/2);
      }
      for (let gz = -z.d/2 + 4; gz < z.d/2; gz += 4) {
        verts.push(z.x - z.w/2, 0.055, z.z + gz);
        verts.push(z.x + z.w/2, 0.055, z.z + gz);
      }
      gridGeom.setAttribute('position', new THREE.Float32BufferAttribute(verts, 3));
      const tileGrid = new THREE.LineSegments(
        gridGeom,
        new THREE.LineBasicMaterial({ color:0x2A325A, transparent:true, opacity:0.35 })
      );
      roomsGroup.add(tileGrid);

      // Interior partition walls — only between zones (not on the building exterior).
      // A partition exists between zone i and zone i+1 along the shared edge.
      const wallH = 4.2, wallT = 0.3;
      // E side wall: only if there's another zone to the east (zi+1)
      const east = zones[zi + 1];
      if (east) {
        // partition runs along Z (vertical wall) at zone's east edge
        const partX = z.x + z.w / 2;
        const partZ = z.z;
        const open = z.doors?.includes('E') || east.doors?.includes('W');
        roomsGroup.add(makeWallSegments(THREE, 'E', partX, partZ, z.d, wallH, wallT, open, matInterior));
      }

      // Interior props
      roomsGroup.add(makeZoneProps(THREE, z));

      // Room label (HTML overlay)
      if (overlayRef.current) {
        const el = document.createElement('div');
        el.innerHTML = `<span style="opacity:.5; font-family:'IBM Plex Mono', monospace; margin-right:8px">${String(zi+1).padStart(2,'0')}</span>${z.name.toUpperCase()}`;
        el.style.cssText = `
          position:absolute; top:0; left:0; pointer-events:none;
          font: 500 10px/1.2 'IBM Plex Sans', sans-serif;
          letter-spacing:0.16em; color:rgba(255,255,255,.75);
          background:rgba(10,15,31,.7); border:1px solid rgba(58,111,248,.4);
          padding:5px 10px; white-space:nowrap;
        `;
        overlayRef.current.appendChild(el);
        ws.roomLabels.push({ el, position: new THREE.Vector3(z.x, 4.6, z.z) });
      }
    });

    // -------- Devices --------
    let siteDevices = devices.filter(d => d.site === site.id);
    if (floor != null) siteDevices = siteDevices.filter(d => (d.floor || 1) === floor);
    const byZone = {};
    siteDevices.forEach(d => { (byZone[d.zone] ||= []).push(d); });

    Object.entries(byZone).forEach(([zoneName, list]) => {
      const z = zones.find(zz => zz.name === zoneName);
      if (!z) return;
      // Layout: place devices in a row along the front of the zone, in front of the props,
      // so markers don't disappear inside equipment.
      const positions = [];
      const usableW = z.w - 6;
      list.forEach((d, i) => {
        const t = list.length === 1 ? 0.5 : i / (list.length - 1);
        const x = z.x - usableW/2 + t * usableW;
        const zPos = z.z + z.d/2 - 3; // toward front of room
        positions.push({ d, x, z: zPos });
      });

      const gateway = positions.find(p => p.d.kind === 'gateway');

      positions.forEach(({ d, x, z: zPos }) => {
        const colorHex = STATE_HEX_3D[d.state] || STATE_HEX_3D.ok;
        // Stem
        const stem = new THREE.Mesh(
          new THREE.CylinderGeometry(0.05, 0.05, 2.4, 8),
          new THREE.MeshStandardMaterial({ color:0x5A5E6E, roughness:0.7 })
        );
        stem.position.set(x, 1.2, zPos);
        devicesGroup.add(stem);

        // Floor disc
        const base = new THREE.Mesh(
          new THREE.CylinderGeometry(0.55, 0.55, 0.05, 24),
          new THREE.MeshStandardMaterial({ color: colorHex, emissive: colorHex, emissiveIntensity:0.4, transparent:true, opacity:0.6 })
        );
        base.position.set(x, 0.09, zPos);
        devicesGroup.add(base);

        // Head sphere
        const mat = new THREE.MeshStandardMaterial({
          color: colorHex, emissive: colorHex,
          emissiveIntensity: 0.55, roughness:0.35, metalness:0.2,
        });
        const head = new THREE.Mesh(new THREE.SphereGeometry(0.5, 24, 16), mat);
        head.position.set(x, 2.55, zPos);
        head.userData = { device:d, deviceId:d.id };
        devicesGroup.add(head);

        // Selection ring
        const ring = new THREE.Mesh(
          new THREE.RingGeometry(0.85, 1.0, 32),
          new THREE.MeshBasicMaterial({ color:0x7A9CFF, side:THREE.DoubleSide, transparent:true, opacity:0.9 })
        );
        ring.position.set(x, 2.55, zPos);
        ring.rotation.x = Math.PI/2;
        ring.visible = false;
        devicesGroup.add(ring);

        ws.deviceMeshes.push({ mesh: head, ring, device: d, state: d.state });

        // Mesh line for sensors → gateway
        if (gateway && d.kind === 'sensor') {
          const points = [
            new THREE.Vector3(x, 2.55, zPos),
            new THREE.Vector3(gateway.x, 2.55, gateway.z),
          ];
          const line = new THREE.Line(
            new THREE.BufferGeometry().setFromPoints(points),
            new THREE.LineBasicMaterial({ color:0x3A6FF8, transparent:true, opacity:0.4 })
          );
          meshGroup.add(line);
        }
      });
    });

    ws.scene.add(roomsGroup);
    ws.scene.add(meshGroup);
    ws.scene.add(devicesGroup);
    ws.scene.add(roofGroup);
    ws.roomsGroup = roomsGroup;
    ws.meshGroup = meshGroup;
    ws.devicesGroup = devicesGroup;
    ws.roofGroup = roofGroup;

    // Camera target = site centre
    if (zones.length) {
      const cx = zones.reduce((s, z) => s + z.x, 0) / zones.length;
      const cz = zones.reduce((s, z) => s + z.z, 0) / zones.length;
      ws.controls.target.set(cx, 1.5, cz);
      ws.controls.update();
    }
  }, [devices, site, roof, floor]);

  // Selected-label HTML element
  React.useEffect(() => {
    if (!overlayRef.current) return;
    const el = document.createElement('div');
    el.style.cssText = `
      position:absolute; top:0; left:0; pointer-events:none;
      font: 500 11px/1 'IBM Plex Mono', monospace;
      color:#FFF; background:#0A0F1F;
      border:1px solid #3A6FF8; padding:5px 10px;
      box-shadow: 0 0 0 3px rgba(58,111,248,.18);
      display:none;
    `;
    overlayRef.current.appendChild(el);
    stateRef.current.selectedLabel = el;
    return () => { el.remove(); };
  }, []);

  const hoveredDevice = hovered ? devices.find(d => d.id === hovered) : null;

  return (
    <div ref={containerRef} className="teo-twin-canvas" style={{
      position:'relative', width:'100%', height:600, background:'#0A0F1F',
      borderTop:'1px solid #EDEDED', borderBottom:'1px solid #EDEDED',
      overflow:'hidden',   // clip HTML labels so they can't bleed into the inspector panel
    }}>
      <div ref={overlayRef} style={{ position:'absolute', inset:0, pointerEvents:'none', overflow:'hidden' }} />

      {/* Top-left: navigation hint */}
      <div style={{
        position:'absolute', top:16, left:16, display:'flex', alignItems:'center', gap:12,
        background:'rgba(10,15,31,.7)', border:'1px solid rgba(255,255,255,.12)',
        padding:'8px 12px', font:"400 12px 'IBM Plex Sans'", color:'rgba(255,255,255,.85)'
      }}>
        <i data-lucide="move-3d" style={{ width:14, height:14, color:'#7A9CFF' }} />
        Drag · rotate · scroll · pan
      </div>

      {/* Top-centre: roof toggle */}
      <div style={{
        position:'absolute', top:16, left:'50%', transform:'translateX(-50%)',
        display:'flex', alignItems:'center', gap:8,
        background:'rgba(10,15,31,.7)', border:'1px solid rgba(255,255,255,.12)',
        padding:'6px 8px',
      }}>
        {['Cutaway','Roof on'].map((opt, i) => {
          const active = (i === 0 && !roof) || (i === 1 && roof);
          return (
            <button key={opt} onClick={() => setRoof(i === 1)} style={{
              padding:'5px 10px', font:"400 12px 'IBM Plex Sans'",
              background: active ? '#7A9CFF' : 'transparent',
              color: active ? '#0A0F1F' : 'rgba(255,255,255,.85)',
              border:'1px solid '+(active ? '#7A9CFF' : 'rgba(255,255,255,.18)'),
              cursor:'pointer', pointerEvents:'auto'
            }}>{opt}</button>
          );
        })}
      </div>

      {/* Top-right: legend */}
      <div style={{
        position:'absolute', top:16, right:16, display:'flex', alignItems:'center', gap:14,
        background:'rgba(10,15,31,.7)', border:'1px solid rgba(255,255,255,.12)',
        padding:'8px 12px', font:"400 11px 'IBM Plex Sans'", color:'rgba(255,255,255,.85)'
      }}>
        {[['ok','Online','#3A6FF8'],['warn','Degraded','#E0A13B'],['down','Offline','#C44A4A'],['idle','Idle','#878787']].map(([s,l,c]) => (
          <div key={s} style={{ display:'flex', alignItems:'center', gap:6 }}>
            <span style={{ width:7, height:7, borderRadius:'50%', background:c, boxShadow:`0 0 6px ${c}` }} />
            {l}
          </div>
        ))}
      </div>

      {/* Bottom-left: hover card */}
      {hoveredDevice && (
        <div style={{
          position:'absolute', bottom:16, left:16,
          background:'#0A0F1F', color:'#FFF',
          border:'1px solid rgba(255,255,255,.18)', padding:'12px 16px',
          font:"400 12px 'IBM Plex Sans'", maxWidth:280
        }}>
          <div style={{ fontFamily:'IBM Plex Mono, monospace', fontSize:11, color:'#7A9CFF' }}>{hoveredDevice.id}</div>
          <div style={{ fontSize:14, marginTop:4 }}>{hoveredDevice.name}</div>
          <div style={{ display:'flex', alignItems:'center', gap:10, marginTop:8, color:'rgba(255,255,255,.7)' }}>
            <span style={{ display:'inline-flex', alignItems:'center', gap:6 }}>
              <span style={{ width:6, height:6, borderRadius:'50%', background:`#${STATE_HEX_3D[hoveredDevice.state].toString(16).padStart(6,'0')}` }} />
              {hoveredDevice.state}
            </span>
            <span>·</span>
            <span style={{ fontFamily:'IBM Plex Mono, monospace' }}>{hoveredDevice.metric.value} {hoveredDevice.metric.unit}</span>
          </div>
        </div>
      )}
    </div>
  );
};

function disposeGroup(group) {
  group.traverse(obj => {
    if (obj.geometry) obj.geometry.dispose();
    if (obj.material) {
      if (Array.isArray(obj.material)) obj.material.forEach(m => m.dispose());
      else obj.material.dispose();
    }
  });
}

Object.assign(window, { Twin3D, ZONE_LAYOUTS_3D });
