Add isolated read-only spectator monitor for public sharing

This commit is contained in:
Codex
2026-09-22 19:50:05 -04:00
parent 8e96c7ff47
commit 7e19d82a02
7 changed files with 379 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
'use strict';
const $ = id => document.getElementById(id);
let realm;
function el(tag, text, cls) { const node = document.createElement(tag); if (text !== undefined) node.textContent = String(text); if (cls) node.className = cls; return node; }
function empty(node, text) { node.replaceChildren(el('p', text, 'empty')); }
function renderRoster() {
if (!realm) return;
const term = $('search').value.trim().toLowerCase(), faction = $('faction').value;
const bots = realm.bots.filter(b => (faction === 'all' || b.faction === faction) && [b.name,b.class,b.race,b.zone,b.guild].join(' ').toLowerCase().includes(term));
$('roster').replaceChildren(...bots.map(b => {
const row = el('tr'); row.append(el('td', b.name));
const team = el('td'); team.append(el('span', b.faction, 'tag ' + b.faction.toLowerCase())); row.append(team);
const role = el('td', b.class); role.append(el('small', b.race)); row.append(role, el('td', b.level), el('td', b.zone || 'Unknown'), el('td', b.guild || '—')); return row;
}));
$('roster-empty').hidden = bots.length > 0;
$('roster-empty').textContent = realm.bots.length ? 'No residents match this search.' : 'No residents are currently reported online.';
}
function renderFeed() {
if (!realm) return;
const entries = realm.feed.filter(e => $('activity-filter').value === 'all' || e.kind === $('activity-filter').value);
if (!entries.length) return empty($('feed'), 'No confirmed activity of this type yet. New encounters will appear here.');
$('feed').replaceChildren(...entries.map(e => {
const node = el('article', undefined, 'entry');
node.append(el('span', e.kind + ' · ' + new Date(e.at).toLocaleString(), 'meta'), el('h3', e.names.join(' ↔ ')), el('p', e.text)); return node;
}));
}
function render() {
$('total').textContent = realm.bots.length;
$('alliance').textContent = realm.bots.filter(b => b.faction === 'Alliance').length;
$('horde').textContent = realm.bots.filter(b => b.faction === 'Horde').length;
$('parties-count').textContent = realm.parties.length;
$('status').className = 'status' + (realm.stale ? ' stale' : '');
$('status').textContent = (realm.stale ? 'Updates delayed · Showing last known roster' : 'Live realm observations') + (realm.rosterAt ? ' · Observed ' + new Date(realm.rosterAt * 1000).toLocaleTimeString() : '');
renderRoster(); renderFeed();
$('parties').replaceChildren(...realm.parties.map(p => { const n=el('article',undefined,'group');n.append(el('strong',p.members.join(' · ')),el('p',p.faction));return n; }));
if (!realm.parties.length) empty($('parties'), 'No bot parties observed yet.');
const goals={quest:'Complete quests together',support:'Help guildmates in combat',explore:'Explore other maps'};
$('guilds').replaceChildren(...realm.guilds.map(g => { const n=el('article',undefined,'group');n.append(el('strong',g.name),el('p',goals[g.goal]||'Shared adventures'),el('p',g.progress+' contributions · milestone '+g.target));const p=el('progress');p.max=Math.max(1,g.target);p.value=g.progress;p.setAttribute('aria-label',g.name+' goal progress');n.append(p);return n; }));
if (!realm.guilds.length) empty($('guilds'), 'Waiting for guild observations.');
}
async function refresh() {
try { const response=await fetch('/api/realm',{cache:'no-store',signal:AbortSignal.timeout(10000)});if(!response.ok)throw new Error();realm=await response.json();render(); }
catch { $('status').className='status stale';$('status').textContent=realm?'Connection interrupted · Showing the last observation. Retrying automatically.':'Realm temporarily unavailable. Retrying automatically.'; }
finally { setTimeout(refresh,15000); }
}
$('search').addEventListener('input',renderRoster);$('faction').addEventListener('change',renderRoster);$('activity-filter').addEventListener('change',renderFeed);refresh();