<!-- SOLUV · part 464 -->
-----
    function showError(msg) {
      var box = h('div', 'sr-error');
      box.appendChild(h('p', null, msg));
      var x = h('button', 'sr-btn', 'OK'); x.type = 'button';
      x.addEventListener('click', function () { box.remove(); });
      box.appendChild(x);
      while (errors.children.length >= 3) errors.firstChild.remove();
      errors.appendChild(box);
    }
    function setStatus(state, text) {
      status.setAttribute('data-state', state);
      if (!dense) { statusText.textContent = text; return; }
      statusText.textContent = state === 'live' ? 'Live' : state === 'poll' ? '1s' : 'Off';
      status.title = text;
      if (state !== 'live') info(text); else if (read && /checking|Offline|No live/.test(read.textContent)) info(readBase);
    }

    // ---------- seal opening (once, as it scrolls into view) ----------
    var io = null;
    if ('IntersectionObserver' in window) {
      io = new IntersectionObserver(function (entries) {
        entries.forEach(function (en) { if (en.isIntersecting) { io.unobserve(en.target); breakSeal(en.target); } });
      }, { root: list, threshold: 0.35 });
    } else {
      showError('This browser has no IntersectionObserver, so letters open right away instead of as you scroll.');
    }
    function breakSeal(node) {
      if (!node.classList.contains('sr-sealed')) return;
      node.classList.remove('sr-sealed');
      if (reduceMotion) { node.classList.add('sr-opened'); return; }
      node.classList.add('sr-opening');
      setTimeout(function () { node.classList.remove('sr-opening'); node.classList.add('sr-opened'); }, 480);
    }
    function sealIt(node) {
      node.classList.add('sr-sealed');
      if (io) io.observe(node); else breakSeal(node);
    }

    // ---------- letters ----------
    var lastSpk = null;   // dense: speaker of the last line, so a run shows its tag once
    function shortName(m, king) {
      if (king) return '♛Epistolary';
      return String(m.from || 'Guest').trim().replace(/^(king|queen|the)\s+/i, '').split(/\s+/)[0].slice(0, 12) || 'Guest';
    }
    function denseLine(m, key, king, c, mine) {
      var node = h('div', 'sr-line' + (king ? ' sr-king' : '') + (mine ? ' sr-mine' : ''));
      node.dataset.spk = key;
      node.style.setProperty('--sr-ct', c.tag);
      if (key !== lastSpk) {
        node.classList.add('sr-newspk');
        var tag = h('span', 'sr-tag', shortName(m, king)); node.appendChild(tag);
      } else {
        node.setAttribute('aria-label', (king ? 'The Epistolary' : (m.from || 'Guest')) + ' continues');
      }
      lastSpk = key;
      var text = h('span', 'sr-text', m.text);
      node.appendChild(text);
      node._sr = { from: king ? 'The Epistolary' : (m.from || 'Guest'), t: m.t || Date.now(), note: null };
      node.addEventListener('click', function (ev) {
        if (ev.target.closest('button')) return;
        var prev = list.querySelector('.sr-tapped'); if (prev && prev !== node) prev.classList.remove('sr-tapped');
        node.classList.toggle('sr-tapped');
        info(node.classList.contains('sr-tapped') ? node._sr.from + ' · ' + new Date(node._sr.t).toLocaleString([], { weekday: 'short', hour: 'numeric', minute: '2-digit' }) + (node._sr.state ? ' · ' + node._sr.state : '') : readBase);
      });
      return node;
    }
    function letterNode(m, opened) {
      var key = m.user || ('guest:' + (m.from || 'Guest'));
      if (dense) {
        var dk = !!m.king || String(m.user || '').toLowerCase() === KING.toLowerCase();
        return denseLine(m, key, dk, colorsFor(key), m.mine || (me.username ? m.user === me.username : false));
      }
      var king = !!m.king || String(m.user || '').toLowerCase() === KING.toLowerCase();
      var c = colorsFor(key);
      var mine = m.mine || (me.username ? m.user === me.username : false);
      var node = h('article', 'sr-letter ' + (mine ? 'sr-mine' : 'sr-theirs') + (king ? ' sr-king' : ''));
      node.style.setProperty('--sr-c', c.face); node.style.setProperty('--sr-cn', c.name);
      var seal = h('div', 'sr-seal'); seal.setAttribute('aria-hidden', 'true'); seal.appendChild(h('span', null, initial(m.from)));
      var fold = h('div', 'sr-fold');
      var lh = h('div', 'sr-letter-head');
      var face = h('div', 'sr-face', king ? '♛' : initial(m.from)); face.setAttribute('aria-hidden', 'true');
      var name = h('div', 'sr-name', king ? 'The Epistolary' : (m.from || 'Guest'));
      var time = h('time', 'sr-time', hhmm(m.t || Date.now()));
      lh.appendChild(face); lh.appendChild(name); lh.appendChild(time);
      var text = h('p', 'sr-text', m.text);
      var note = h('div', 'sr-note', '');
      fold.appendChild(lh); fold.appendChild(text); fold.appendChild(note);
      node.appendChild(seal); node.appendChild(fold);
      node._sr = { name: name, time: time, note: note, face: face, seal: seal };
      if (opened) node.classList.add('sr-opened'); else sealIt(node);
      return node;
    }

    func