<!-- SOLID CITY · part 843 -->
;
  var wait = document.createElement('div'); wait.className = 'sys'; wait.textContent = 'Soul Print is listening…'; $('sp-log').appendChild(wait);
  getMe().then(function (m) {
    function call() { return fetch('/api/city/soulprint', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': (m && m.csrf) || '' }, body: JSON.stringify({ message: text, history: soulHist.slice(-6) }) }); }
    // one quiet retry on 502/503 (e.g. the few seconds of a server restart) before showing an error
    return call().then(function (r) { return r.status === 502 || r.status === 503 ? new Promise(function (ok) { setTimeout(ok, 1500); }).then(call) : r; });
  }).then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { return { s: r.status, j: j }; }); })
    .then(function (o) {
      wait.remove();
      if (o.s === 401) { me = null; spLine('sys', 'Log in to talk with Soul Print.'); $('sp-login').hidden = false; $('sp-form').style.display = 'none'; return; }
      if (o.s === 403) { if (o.j && o.j.error === 'csrf') { me = null; spLine('sys', 'Your session check failed. Reload the page and try again.'); } else spLine('sys', 'The door won\u2019t open right now (' + ((o.j && o.j.error) || 'forbidden') + ').'); return; }
      if (o.s === 429) { spLine('sys', 'Soul Print needs a quiet moment. Too many messages; try again in a little while.'); return; }
      var reply = o.j && typeof o.j.reply === 'string' ? o.j.reply : '';
      if (!reply) { spLine('sys', 'Soul Print didn\u2019t answer (' + ((o.j && o.j.error) || o.s) + ').'); return; }
      soulHist.push({ role: 'user', text: text }, { role: 'her', text: reply.slice(0, 300) });
      spLine('her', reply); speak(reply, 1.08);
    })
    .catch(function () { wait.remove(); spLine('sys', 'Connection lost.'); })
    .then(function () { soulBusy = false; $('sp-send').disabled = false; });
}
/* ---------------- CHECK-INS tab: the SHARED Soluv room (= /soluv wall 2, /api/soluv-room) ----------------
   Not the private AI talk: everyone logged in sees the same feed (oldest at top, newest at the bottom).
   Posts go only to /api/soluv-room (name from the login, CSRF header); nothing here calls an AI. Polls every 4 s while open. */
var wall = { tab: 'talk', last: 0, timer: 0, busy: false, n: 0 };
function spTab(t) {
  wall.tab = t; var w = t === 'wall';
  $('sp-tab-talk').classList.toggle('on', !w); $('sp-tab-wall').classList.toggle('on', w);
  $('sp-tab-talk').setAttribute('aria-selected', String(!w)); $('sp-tab-wall').setAttribute('aria-selected', String(w));
  $('sp-log').hidden = w; $('sp-wall').hidden = !w;
  getMe().then(function (j) {
    var ok = j && j.loggedIn;
    if (w) { $('sp-login').hidden = true; $('sp-form').style.display = 'none'; $('sp-wlogin').hidden = !!ok; $('sp-wform').hidden = !ok; if (ok) { wallPoll(); if (!IS_TOUCH) $('sp-wmsg').focus(); } else $('sp-wall').textContent = ''; }
    else { $('sp-wlogin').hidden = true; $('sp-wform').hidden = true; $('sp-login').hidden = !!ok; $('sp-form').style.display = ok ? '' : 'none'; }
  });
}
function wallFmt(t) { try { return new Date(t).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return ''; } }
function wallAdd(m) {
  if (!m || m.id <= wall.last) return; wall.last = m.id;
  var box = $('sp-wall'), atEnd = box.scrollHeight - box.scrollTop - box.clientHeight < 60;
  var d = document.createElement('div'), mine = me && me.name && m.from === me.name; d.className = 'wl' + (mine ? ' me' : '') + (m.king ? ' king' : '');
  var h = document.createElement('b'); h.textContent = (m.king ? '\u265B ' : '') + m.from; d.appendChild(h);
  var tm = document.createElement('small'); tm.textContent = ' ' + wallFmt(m.t); d.appendChild(tm);
  var p = document.createElement('p'); p.textContent = m.text; d.appendChild(p);
  var em = $('sp-wempty'); if (em) em.remove();
  box.appendChild(d); wall.n++;
  spStatus();
  if (atEnd || wall.n < 3) box.scrollTop = 1e9;
}
function spStatus() {   // header status line: 'Soul Print Tower · LIVE · N check-ins' (display only)
  var s = $('sp-status'); if (!s) return;
  s.textContent = 'Soul Print Tower \u00b7 '; var i = document.createElement('i'); i.className = 'sp-live'; i.textContent = 'LIVE'; s.appendChild(i);
  if (wall.n) s.appendChild(document.createTextNode(' \u00b7 ' + wall.n + ' check-in' + (wall.n === 1 ? '' : 's')));
}
function wallPoll() {
  clearTimeout(wall.timer);
  if ($('sp-talk').hidden || wall.tab !== 'wall') return;
  if (!document.hidden && !wall.inflight) { wall.inflight = true; fetch('/api/soluv-room?since=' + wall.last, { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (j) {
    var first = !wall.n; (j.msgs || []).forEach(wallAdd);
    if (first && !wall.n && !$('sp-wempty')) { var e = document.createElement('div'); e.className = 'sys'; e.id = 'sp-wem