'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();