<!-- SOLUV · part 484 -->
ormance.now(), ms: 34000, from: { ...st } }; setOn(); }
  function risePose(t) {
    const e = t * t * (3 - 2 * t), y = 2 + (topY + 60) * e, out = Math.sin(Math.PI * Math.min(1, t * 1.15));
    const eye = [-8 - 26 * out, y, 10 + 42 * out]; const look = t < 0.88 ? [0, y + 160 + 240 * (1 - t), 0] : [0, y + 400, -60];
    return aim(eye, look);
  }
  const setOn = () => wrap.querySelector('.sp-rise').classList.toggle('sp-on', !!rise);

  /* ---------- touch: one finger looks around; two fingers: pinch flies forward/back, drag up/down rides the tower ---------- */
  const ptr = new Map(); let last = null;
  const speed = () => Math.max(0.04, (Math.max(0, st.y) + 20) * 0.0035, Math.min(40, wall.wall.distanceTo(camera.position) * 0.004));
  canvas.addEventListener('pointerdown', e => { userMoved = true; fling = 0; canvas.setPointerCapture(e.pointerId); ptr.set(e.pointerId, { x: e.clientX, y: e.clientY }); stopMotion(); last = gesture(); });
  canvas.addEventListener('pointermove', e => { if (!ptr.has(e.pointerId)) return; ptr.set(e.pointerId, { x: e.clientX, y: e.clientY }); const g = gesture(); if (last && g && g.n === last.n) move(last, g); last = g; });
  const up = e => { ptr.delete(e.pointerId); last = gesture(); };
  function scrollClamp() { const t = wall.wall.textRect(), half = distForPpe(READ_PPE) * tanH(); st.y = Math.max(t.y0 + half * 0.6, Math.min(t.y1 + half * 0.2, st.y)); st.x = 0; st.yaw = 0; st.pitch = 0; }
  canvas.addEventListener('pointerup', up); canvas.addEventListener('pointercancel', up);
  // wheel / two-finger scroll = up and down the tower (Read: the column, scroll down = newer); Ctrl/Alt+wheel or pinch = fly forward/back
  canvas.addEventListener('wheel', e => {
    e.preventDefault(); haltAuto();
    const px = e.deltaY * (e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? cssH() : 1);
    if (e.ctrlKey || e.altKey) { rideGoal = null; const k = -px * speed() * 0.05; st.x += fwd.x * k; st.y += fwd.y * k; st.z += fwd.z * k; applyCamera(); return; }
    rideBy(-px * pxWorld());
  }, { passive: false });

  /* KEYBOARD (ignored while typing): Read mode: Up/W older, Down/S newer, PageUp/PageDown a screen, Home top, End newest, Escape leaves Read.
     Flying: W/S forward/back, A/D slide, Up/Down/PageUp/PageDown/E/Q ride up and down, Left/Right turn. Enter = write. */
  function keyAction(key) {
    const k = key.length === 1 ? key.toLowerCase() : key;
    if (k === 'Home') { goTop(); return true; }
    if (k === 'End') { goNewest(); return true; }
    if (k === 'Enter') { if (!form.classList.contains('sp-away') && !input.disabled) input.focus(); else goNewest(); return true; }
    if (k === 'Escape') { if (!voice.panel.hidden) { voice.panel.hidden = true; return true; } if (reading) { setReading(false); return true; } return false; }
    if (reading) {
      const line = L.LINE * EM, page = 2 * distForPpe(READ_PPE) * tanH() * 0.9;
      const d = k === 'ArrowUp' || k === 'w' ? line * 3 : k === 'ArrowDown' || k === 's' ? -line * 3 : k === 'PageUp' ? page : k === 'PageDown' ? -page : null;
      if (d === null) return false; haltAuto(); rideBy(d); return true;
    }
    const s = speed() * 4, r = { x: Math.cos(st.yaw), z: Math.sin(st.yaw) }, vis = Math.max(4, 2 * Math.max(1, wall.wall.distanceTo(camera.position)) * tanH());
    if (k === 'w') { haltAuto(); st.x += fwd.x * s; st.y += fwd.y * s; st.z += fwd.z * s; applyCamera(); }
    else if (k === 's') { haltAuto(); st.x -= fwd.x * s; st.y -= fwd.y * s; st.z -= fwd.z * s; applyCamera(); }
    else if (k === 'a') { haltAuto(); st.x -= r.x * s; st.z -= r.z * s; applyCamera(); }
    else if (k === 'd') { haltAuto(); st.x += r.x * s; st.z += r.z * s; applyCamera(); }
    else if (k === 'ArrowLeft') { haltAuto(); st.yaw -= 0.06; applyCamera(); }
    else if (k === 'ArrowRight') { haltAuto(); st.yaw += 0.06; applyCamera(); }
    else if (k === 'ArrowUp' || k === 'e') { haltAuto(); rideBy(vis * 0.12); }
    else if (k === 'ArrowDown' || k === 'q') { haltAuto(); rideBy(-vis * 0.12); }
    else if (k === 'PageUp') { haltAuto(); rideBy(vis * 0.9); }
    else if (k === 'PageDown') { haltAuto(); rideBy(-vis * 0.9); }
    else return false;
    return true;
  }
  addEventListener('keydown', e => {
    if (e.metaKey || e.ctrlKey || e.altKey || e.defaultPrevented) return;
    const tgt = e.target;
    if (tgt && tgt.closest && tgt.closest('input,textarea,select,[contenteditable]')) { if (e.key === 'Escape') tgt.blur(); return; }   // typing is never steering
    if (tgt && tgt.closest && tgt.closest('button,a') && (e.key === 'Enter' || e.key === ' ')) return;                             // let focused buttons work
    if (keyAction(e.key)) e.preventDefault();
  });
  function gesture() { const p = [...ptr.values()]; if (!p.length) return null; const x = p.reduce((a, b) => a + b.x, 0) / p.length, y = p.reduce((a, b) => a + b.y, 0) / p.length; const span = p.length > 1 ? Math.hypot(p[0].x - p[1].x, p[0].y - p[1].y) : 0; return { n: p.le