<!-- SOLID CITY · part 99 -->
   var cut = Date.now() - 32000;
    var parts = CAP.chunks.filter(function (ch) { return ch.t >= cut; }).map(function (ch) { return ch.blob; });
    if (!parts.length) { A.help('No moment captured yet — give it a few seconds of living.', 4); return; }
    var blob = new Blob(parts, { type: 'video/webm' });
    var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'solid-city-moment.webm'; a.click();
    setTimeout(function () { URL.revokeObjectURL(a.href); }, 4000);
  });
}

/* ---------- bubbles ---------- */
var bubbleEls = {};
function tickBubbles() {
  var nowMs = performance.now();
  for (var i = 0; i < M.citizens.length; i++) {
    var c = M.citizens[i];
    var el = bubbleEls[c.id];
    if (c.bubbleText && nowMs < c.bubbleUntil) {
      if (!el) {
        el = bubbleEls[c.id] = document.createElement('div');
        el.style.cssText = 'position:fixed;z-index:55;pointer-events:none;max-width:260px;background:rgba(13,18,28,.94);border:1px solid #d8a833;border-radius:12px;color:#e8edf5;font:13px/1.4 system-ui;padding:8px 11px;transform:translate(-50%,-100%)';
        document.body.appendChild(el);
      }
      el.textContent = c.name + ': ' + c.bubbleText; el.style.display = '';
      var v = c.fig.position.clone(); v.y += 2.45; v.project(A.camera);
      el.style.left = ((v.x * 0.5 + 0.5) * innerWidth) + 'px';
      el.style.top = ((-v.y * 0.5 + 0.5) * innerHeight - 10) + 'px';
      el.style.opacity = v.z < 1 ? '1' : '0';
    } else if (el) el.style.display = 'none';
  }
}

/* ---------- build ---------- */
function load() {
  M.state = 'loading';
  Promise.all([
    import('/city/avatar-factory.js?v=20261013a'),
    import('/city/hero-motion.js?v=20261013a'),
    import('/city/descendant-motions.js?v=20261013a'),
    api('/api/citizens'),
    api('/api/city/me'),
    fetch('/city/voices/manifest.json', { credentials: 'same-origin' }).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; })
  ]).then(function (mods) {
    VOICE_FILES = mods[5] || null;
    var fac = mods[0].createAvatarFactory(T);
    var HERO = mods[1], DESC = mods[2], data = mods[3], me = mods[4];
    M.me = me && (me.display_name || me.name || me.username) || null;
    var liveById = {};
    if (data && data.citizens) { REACT.online = true; for (var i = 0; i < data.citizens.length; i++) liveById[data.citizens[i].id] = data.citizens[i]; if (data.reactions) { REACT.scripts = data.reactions.scripts; REACT.interpretations = data.reactions.interpretations; } }
    var group = new T.Group(); A.scene.add(group);
    ROSTER.forEach(function (rec, idx) {
      var live = liveById[rec.id] || {};
      var model = fac.buildAvatar(fac.validate(rec.appearance), { preview: false });
      var parts = fac.collectParts(model);
      var y = surfY(rec.post.x, rec.post.z);
      model.position.set(rec.post.x, y, rec.post.z);
      group.add(model);
      var holder = { c: null };
      var motion = HERO.createHeroMotion(parts, { gestures: DESC.DESCENDANT_GESTURES, lambda: 10, onEvent: function (phase, name) {
        if (holder.c && phase === 'started' && name) broadcastGesture(holder.c, name);
      } });
      var plateMat = new T.SpriteMaterial({ map: null, transparent: true, depthWrite: false });
      var plate = new T.Sprite(plateMat); plate.scale.set(2.6, 0.65, 1); plate.position.set(rec.post.x, y + 2.35, rec.post.z); group.add(plate);
      var solid = { x0: rec.post.x - 0.45, x1: rec.post.x + 0.45, z0: rec.post.z - 0.45, z1: rec.post.z + 0.45, y0: y, y1: y + 1.8, citizenFigure: true };
      A.solids.push(solid);
      var c = { id: rec.id, name: rec.name, rec: rec, fig: model, parts: parts, motion: motion, plate: plate, solid: solid,
                pos: { x: rec.post.x, z: rec.post.z }, face: Math.atan2(-rec.post.x, -rec.post.z), faceTarget: null, moveTarget: null,
                op: live.operator || { type: 'ai', epoch: 1 }, stopped: !!live.stopped, session: null, lastReactAt: 0, phase: idx * 1.7 };
      plate.material.map = plateTexture(c); plate.material.needsUpdate = true;
      holder.c = c;
      M.citizens.push(c); M.byId[c.id] = c;
      if (A.POIS) A.POIS.push({ id: 'citizen-' + rec.id, name: rec.name.toUpperCase() + ' — ' + rec.craft.split(',')[0], icon: rec.icon, x: rec.post.x, z: rec.post.z });
    });
    buildPanel();
    buildCapture();
    document.addEventListener('pointerdown', function () { M.interacted = true; }, { once: true });
    setInterval(refreshLive, 25000);
    window.CityCitizens = { citizens: M.citizens, byId: M.byId, state: M };
    M.state = 'live';
    A.logBuild({ t: Date.now(), site: 'THE SEVEN CITIZENS', what: 'The descendants stand at their posts as inhabitable citizens: Forge bodies, Hero Motion, Living Reactions, server minds — Nalu (Berth 1), Lehua (Hall of Becoming), Lani (Threshold), Hōkū (The Gap), Nīnau (Starwell), Koa (Eighth Gate), Pō (South Lot gate).' });
    A.help('👥 The seven citizens s