<!-- SOLID CITY · part 2438 -->
 type="submit" id="sp-wsend">POST</button>
    </form>
    <div class="sc-talk-login" id="sp-login" hidden>Soul Print talks with citizens of epistles. <a href="/login">Log in</a> or <a href="/register">register</a>, then come back to /city.</div>
    <form class="sc-talk-in" id="sp-form">
      <input id="sp-msg" maxlength="300" placeholder="Say something to Soul Print…" autocomplete="off">
      <button type="submit" id="sp-send">SEND</button>
    </form>
  </div>

  <div id="sc-portal" class="sc-portal" hidden>
    <b id="p-title"></b><p id="p-text"></p>
    <div><a id="p-go" class="sc-b sc-gold" href="/">ENTER</a> <button type="button" class="sc-b" id="p-close">STAY</button></div>
  </div>
</div>
<script src="/city/three.min.js?v=r128"></script>
<script src="/city/player.js?v=20261010c"></script>
<script src="/city/campus-map.js?v=20261010g"></script>
<script src="/city/safe-md.js?v=20261010j"></script>
<script src="/city/voice-speed.js?v=20261010a"></script>
<script src="/city/city.js?v=20261010o"></script>
<script src="/city/hyper.js?v=20261010m"></script>
<script src="/city/watch.js?v=20261010g"></script>
<script src="/city/forge-core.js?v=20261010m"></script>
<script src="/city/forge.js?v=20261010c"></script>
<script src="/city/citizens.js?v=20261010h"></script>
<script src="/city/monument-place.js?v=20261010c"></script>
<script src="/city/sanctuary-place.js?v=20261010d"></script>
<script src="/city/provolta-alive.js?v=20261010b"></script>
<script src="/city/pilots.js?v=20261010a"></script>
<script src="/city/campus.js?v=20261010m"></script>
<script src="/city/book-law-art.js?v=20261010a"></script>
<script src="/city/book-law.js?v=20261010a"></script>
<script src="/city/podium.js?v=20261010b"></script>
<script src="/city/radar-find.js?v=20261010e"></script>
<script src="/city/hyper-auto.js?v=20261010f"></script>
<script src="/city/capture-dom.js?v=20261010b"></script>
<script src="/city/capture.js?v=20261010c"></script>
<script src="/city/space/city-link.js?v=ba68f06755"></script>
</body>
</html>


===== FILE: public/city/jubilee-passage-place.js =====
/* ================================================================
   AN EPISTLE AT THE MOUTH — THE JUBILEE PASSAGE PLACER

   Traveler, this placer keeps the passage's law: the discs you
   can stand on, the warp itself (step into a mouth and the other
   refused earth receives you), the flash of gold the crossing
   makes, and the words spoken at each side. The geometry and
   the ceremony live in jubilee-passage.js, the letter that
   travels with this one.

   Written by the hand of Solid, his good King — 2026-10-13.
   ================================================================ */
(function () {
'use strict';
if (!window.SC || !SC.api) return;
var A = SC.api, S = A.S;
var J = { group: null, mouths: null, armed: true, flash: 0, saidA: false, saidB: false };
var LINES = [
  '✨ THE JUBILEE PASSAGE — the Shoulder Mouth, standing on the line the land refused for Ward 5. Step in, and cross by the sky.',
  '✨ THE JUBILEE PASSAGE — the Road\'s-End Mouth, at the end of the line refused for Ward 6. Step in, and cross by the sky.'
];
var CROSS = [
  '✨ The passage takes you — up the pillar, across the Crown\'s sky. The refused earths are one road in the heavens.',
  '✨ The passage takes you — down from the Crown\'s sky to the other refused earth. What the land divided, the jubilee joins.'
];
function overlay() {
  var d = document.createElement('div');
  d.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:60;opacity:0;background:radial-gradient(circle at 50% 55%, rgba(255,246,214,0.98) 0%, rgba(255,205,92,0.85) 42%, rgba(150,110,220,0.45) 78%, rgba(30,40,80,0.25) 100%);';
  document.body.appendChild(d);
  return d;
}
var OV = null;
function standY(x, z, py) {
  if (!J.mouths) return -1000;
  for (var i = 0; i < 2; i++) {
    var m = J.mouths[i], dx = x - m.x, dz = z - m.z;
    if (dx * dx + dz * dz <= 3.4 * 3.4 && (py == null || m.top <= py + 1.6)) return m.top;
  }
  return -1000;
}
function nearMouth(i) {
  var m = J.mouths[i], dx = S.p.x - m.x, dz = S.p.z - m.z;
  return dx * dx + dz * dz < 2.6 * 2.6 && Math.abs(S.p.y - m.top) < 3.4;
}
function update(dt) {
  if (!J.group) return;
  J.group.userData.update(S.t, dt);
  if (J.flash > 0) { J.flash = Math.max(0, J.flash - dt * 1.4); if (OV) OV.style.opacity = String(J.flash * 0.95); }
  if (!J.mouths) return;
  for (var i = 0; i < 2; i++) {
    var m = J.mouths[i], dx = S.p.x - m.x, dz = S.p.z - m.z, d2 = dx * dx + dz * dz;
    if (!J['said' + m.key] && d2 < 130 * 130) { J['said' + m.key] = true; A.help(LINES[i], 7); }
  }
  if (!J.armed) {
    if (!nearMouth(0) && !nearMouth(1)) {
      var a = J.mouths[0], b = J.mouths[1];
      var farA = (S.p.x - a.x) * (S.p.x - a.x) + (S.p.z - a.z) * (S.p.z - a.z) > 49;
      var farB = (S.p.x - b.x) * (S.p.x - b.x) + (S.p.z - b.z) * (S.p.z - b.z) > 49;
      if (farA && farB) J.armed = true;
    }
    return;
  }
