<!-- SOLID CITY · part 1593 -->
tion (m) {
    return fetch('/api/soluv-room', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': (m && m.csrf) || '' }, body: JSON.stringify({ msg: text, localKey: 'city' + Date.now().toString(36) }) });
  }).then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { return { s: r.status, j: j }; }); })
    .then(function (o) {
      if (o.j && o.j.ok) { $('sp-wmsg').value = ''; if (o.j.msg) wallAdd(o.j.msg); $('sp-wall').scrollTop = 1e9; }
      else { var e = document.createElement('div'); e.className = 'sys'; e.textContent = o.s === 403 ? 'Session check failed. Reload and try again.' : o.s === 429 ? 'Slow down a little.' : 'Post failed (' + ((o.j && o.j.error) || o.s) + ').'; $('sp-wall').appendChild(e); }
    }).catch(function () {}).then(function () { wall.busy = false; $('sp-wsend').disabled = false; });
}
$('sp-tab-talk').addEventListener('click', function () { spTab('talk'); });
$('sp-tab-wall').addEventListener('click', function () { spTab('wall'); });
$('sp-close2').addEventListener('click', function () { $('sp-close').click(); });
$('sp-wform').addEventListener('submit', function (e) { e.preventDefault(); wallPost($('sp-wmsg').value); });
$('sp-close').addEventListener('click', function () { clearTimeout(wall.timer); });
(function () { // push-to-talk via the browser's speech recognition
  var SR = window.SpeechRecognition || window.webkitSpeechRecognition, rec = null, btn = $('t-ptt'), heard = '';
  if (!SR) { btn.addEventListener('click', function () { logLine('sys', 'Voice input isn\u2019t supported in this browser — type instead.'); }); return; }
  function start(e) { e.preventDefault(); if (rec) return; heard = ''; rec = new SR(); rec.lang = 'en-US'; rec.interimResults = true; rec.continuous = true;
    rec.onresult = function (ev) { var s = ''; for (var i = 0; i < ev.results.length; i++) s += ev.results[i][0].transcript; heard = s; $('t-msg').value = s; };
    rec.onerror = function (ev) { logLine('sys', 'Mic: ' + ev.error); };
    rec.onend = function () { btn.classList.remove('is-on'); var h = heard; rec = null; if (h.trim()) sendTalk(h); };
    try { speechSynthesis.cancel(); } catch (x) {} rec.start(); btn.classList.add('is-on'); }
  function stop() { if (rec) try { rec.stop(); } catch (x) {} }
  btn.addEventListener('click', function (e) { e.preventDefault(); if (typeof micLockStart !== 'function') return; if (micLock.on) micLockStop(); else micLockStart(); });
})();
/* Mic lock — tap the avatar locks the mic on; 20s of quiet releases it. Plain text only. */
var micLock = { on: false, rec: null, lastVoice: 0, timer: 0 };
function micLockStart() {
  var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
  if (!SR) { logLine('sys', 'Voice input isn\u2019t supported in this browser.'); return; }
  if (micLock.on) return;
  micLock.on = true; micLock.lastVoice = Date.now(); window.__sableSpeaking = false;
  try { var CPR = window.CityPlayer; micLock.radioWas = !!(CPR && CPR.state && CPR.state().playing); if (micLock.radioWas && CPR.toggle) CPR.toggle(); } catch (e6) {}
  $('t-form').style.display = 'none';
  $('sc-talk').classList.add('behind');
  logLine('sys', 'Mic locked on — speak. Quiet 20s releases it.');
  micRecStart();
  micLock.timer = setInterval(function () {
    if (!micLock.on || document.hidden) return;
    if (!window.__sableSpeaking && Date.now() - micLock.lastVoice > 20000) micLockStop();
  }, 1000);
}
function micLockStop(quiet) {
  if (!micLock.on) return;
  micLock.on = false; micLock.aborts = 0;
  try { var CPR2 = window.CityPlayer; if (micLock.radioWas && CPR2 && CPR2.toggle && CPR2.state && !CPR2.state().playing) CPR2.toggle(); } catch (e7) {}
  micLock.radioWas = false;
  try { clearInterval(micLock.timer); } catch (e) {}
  try { if (micLock.rec) { micLock.rec.onend = null; micLock.rec.onerror = null; micLock.rec.stop(); } } catch (e2) {}
  micLock.rec = null;
  $('t-form').style.display = '';
  $('sc-talk').classList.remove('behind');
  if (!quiet) logLine('sys', 'Mic off — quiet 20s.');
}
function micRecStart() {
  var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
  if (!micLock.on || micLock.rec || !SR) return;
  var rec = null;
  try { rec = new SR(); } catch (e) { return; }
  micLock.rec = rec;
  rec.lang = 'en-US'; rec.interimResults = true; rec.continuous = true;
  var finalSent = '';
  rec.onresult = function (ev) {
    micLock.aborts = 0;
    var s = '', fin = false, i;
    for (i = 0; i < ev.results.length; i++) { s += ev.results[i][0].transcript; if (ev.results[i].isFinal) fin = true; }
    if (window.__sableSpeaking) { if (s.trim()) micLock.lastVoice = Date.now(); return; }
    if (s.trim()) micLock.lastVoice = Date.now();
    if (fin && s.trim() && s.trim() !== finalSent) { finalSent = s.trim(); sendTalk(s.trim()); }
  };
  rec.onerror = function (ev) {
    if (ev.error === 'not-allowed' || ev.error