<!-- SOLID CITY · part 2179 -->
inp.addEventListener('keydown', function (e) {
  if (e.key === 'Escape') { inp.blur(); return; }
  if (e.key !== 'Enter') return;
  var v = inp.value; inp.value = ''; if (A.IS_TOUCH) inp.blur(); handleCommand(v);
});
addEventListener('keydown', function (e) {
  var tg = e.target && e.target.tagName; if (tg === 'INPUT' || tg === 'TEXTAREA') return;
  if (e.key === '/' && !hc.hidden) { e.preventDefault(); inp.focus(); }
  else if (e.key === 'Escape') { if (!card.hidden) hideCard(); else if (H.nav || H.tour || H.flyDrive || H.armed) stopAI(); }
});
var cv = $('sc-gl'); ['pointerdown', 'pointermove'].forEach(function (ev) { cv.addEventListener(ev, function (e) { if (e.type === 'pointerdown' || e.buttons) H.lastDrag = performance.now(); }); });

/* ---------------- per-frame (called by city.js) ---------------- */
function update(dt) {
  var fl = flying(), vis = S.mode === 'walk' || fl;
  if (hc.hidden === vis) hc.hidden = !vis;
  root.classList.toggle('hc-fly', fl);
  if (fl !== H.wasFly) { // walk <-> flight: switch the button set
    H.wasFly = fl; H.nav = null; H.tour = null; H.armed = false; S.ai = null; clearPath(); hideCard(); clearScan(); focusG.visible = false; H.focus = null;
    if (H.mode === 'LOOK') { S.camDist = 7.5; S.camPitch = 0.28; }
    if (!H.flyDrive) setMode(fl ? 'FLIGHT' : 'WALK');
    inp.placeholder = fl ? 'Try: "fly me to Mauna Kea"' : PH;
  }
  if (H.flyDrive && S.mode !== 'auto') { // autopilot ended without our arrival callback: user took over, or landed
    var landed = S.mode === 'boarded'; H.flyDrive = null; setMode(baseMode());
    narrate(landed ? 'Landed at the <b>Orbital Spaceport</b>. LAUNCH again, or EXIT SHIP to walk.' : 'Manual control. AI Drive stopped.', 5);
  }
  if (!vis && H.mode !== 'WALK' && S.mode === 'boarded') setMode('WALK');
  if (H.tour && !H.nav) { var I = A.input, k = I.keys; if (Math.abs(I.sx) + Math.abs(I.sy) > 0.08 || k.KeyW || k.KeyA || k.KeyS || k.KeyD || k.ArrowUp || k.ArrowDown || k.ArrowLeft || k.ArrowRight) { stopAI(true); setMode('WALK'); narrate('Manual control. Tour stopped.', 3); } }
  if (H.tour && !H.nav && H.tour.wait > 0) { H.tour.wait -= dt; if (H.tour.wait <= 0) tourNext(); }
  if (H.scanT > 0) { H.scanT -= dt; if (H.scanT <= 0 && H.mode === 'SCAN') setMode(H.prevMode === 'SCAN' ? baseMode() : H.prevMode); }
  if (H.scan) {
    H.scan.age += dt;
    if (scanRing.visible) { var r = 2 + H.scan.age / 1.6 * 600; scanRing.scale.setScalar(r); matRing.opacity = Math.max(0, 0.8 * (1 - H.scan.age / 1.6)); if (H.scan.age > 1.6) scanRing.visible = false; }
    if (H.scan.fly) { // keep labels on the beam tops despite the planet curvature
      var cx = A.camera.position.x + A.origin.x, cz = A.camera.position.z + A.origin.z;
      scanG.children.forEach(function (o) { if (o.isSprite) { var dd = Math.hypot(o.position.x - cx, o.position.z - cz); o.position.y = o.userData.top - dd * dd / (2 * R_PLANET); } });
    }
    matRed.opacity = 0.35 + 0.25 * Math.sin(S.t * 4);
    if (H.scan.age > H.scan.t) { clearScan(); scanRing.visible = false; }
  }
  if (H.beaconT > 0) { H.beaconT -= dt; fRing.material.opacity = 0.4 + 0.3 * Math.sin(S.t * 6); if (H.beaconT <= 0) focusG.visible = false; }
  if (pathG) matPath.opacity = 0.55 + 0.25 * Math.sin(S.t * 5);
  if (H.mode === 'LOOK' && S.mode === 'walk' && performance.now() - H.lastDrag > 2000) S.camYaw += dt * 0.22;
}
function radar(g, blip, fl, sc, ax, az) {
  if (H.scan && H.scan.fly === fl) {
    if (H.scan.age < 1.8) { var W = g.canvas.width; g.strokeStyle = 'rgba(255,210,122,' + (0.9 - H.scan.age / 2) + ')'; g.lineWidth = 3; g.beginPath(); g.arc(W / 2, W / 2, Math.min(W / 2, H.scan.age / 1.6 * W / 2), 0, Math.PI * 2); g.stroke(); }
    H.scan.marks.forEach(function (m) { blip(m.x, m.z, '#ffd27a', 4, fl ? m.t.split(' ')[0].slice(0, 9) : ''); });
  }
  if (H.focus) blip(H.focus.x, H.focus.z, '#ff4d5e', 5, fl ? '\u25ce' : '');
  var t = H.nav ? H.nav.p : H.flyDrive ? H.flyDrive.t : null; if (t) blip(t.x, t.z, '#ffffff', 4, '\u2605');
}

window.CityHyper = {
  regrid: function () { grid = null; }, update: update, radar: radar, state: H, cmd: handleCommand, stop: function () { stopAI(true); hideCard(); clearNarration(); setMode(baseMode()); }, pois: POIS, landmarks: FLY,
  getMode: function () { return H.mode; }, findPath: function (a, b) { return findPath(a, b); },
  // used by citizens.js (👥 CITIZENS list): same glass card, AI Drive and narration as the rest of the controller
  showCard: showCard, hideCard: hideCard, driveTo: driveTo, narrate: narrate, stopAI: stopAI, flying: flying
};
})();


===== FILE: public/city/hyper.js.bak-royal-20261012 =====
/* SOLID CITY — Hyper Controller (2026-10-09)
 * The same setup as the Solidability World Hyper Controller (public/solidability-world/js/sw-hyper.js):
 * right-edge column of 44px round emoji buttons under a mode chip, a command pill, and the
 * "🤖 AI DRIVE" narration bar — re-skinned red/black/gold and adapted to Solid City's distri