news: the adventure page gets something that's actually happening
Every dispatch Pete publishes is an accomplishment — a death, a clear, a milestone — and an accomplishment is a newspaper clipping the moment it lands. No refresh interval fixes that. So the page never felt alive, and it never was going to. The board is the other kind of thing: state that is currently true. gogobee pushes the whole roster, we replace ours with it, and it renders above the clippings. An open tab re-polls so it keeps telling the truth. Replace, never merge: anyone gogobee omits (opted out, no character) drops off the public page. That omission IS the opt-out — a standing row showing class, level and zone names the player anyway, so "an adventurer" would have been a fig leaf. The snapshot time lives in its own row, because an empty board is ambiguous: nobody playing, or gogobee stopped talking to us. The page has to tell those apart — one is a quiet realm, the other is a board that lies confidently, which is worse than one that admits it lost the wire. Also teaches Pete "departure", so a bored adventurer letting itself out is news.
This commit is contained in:
@@ -13,6 +13,81 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{if .ShowRoster}}
|
||||
<section class="mb-10" id="roster" data-stale="{{.RosterStale}}">
|
||||
<div class="flex items-baseline justify-between mb-3">
|
||||
<h2 class="font-display text-2xl font-bold">Out there right now</h2>
|
||||
<p class="text-xs uppercase tracking-wider text-[color:var(--ink)]/50" id="roster-status">
|
||||
{{if .RosterStale}}last known — the wire's gone quiet{{else}}live{{end}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete overflow-hidden {{if .RosterStale}}opacity-60{{end}}" id="roster-card">
|
||||
<ul class="divide-y divide-[color:var(--ink)]/10" id="roster-list">
|
||||
{{range .Roster}}
|
||||
<li class="flex items-center gap-4 px-5 py-3">
|
||||
<span class="text-lg" aria-hidden="true">{{if .OnRun}}⚔{{else}}🏠{{end}}</span>
|
||||
<span class="font-semibold">{{.Name}}</span>
|
||||
<span class="text-sm text-[color:var(--ink)]/60">lv {{.Level}} {{.ClassRace}}</span>
|
||||
<span class="ml-auto text-sm {{if .OnRun}}font-semibold{{else}}text-[color:var(--ink)]/60{{end}}">
|
||||
{{.Where}}{{if .Idle}} <span class="text-[color:var(--ink)]/45">· {{.Idle}}</span>{{end}}
|
||||
</span>
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="px-5 py-8 text-center text-[color:var(--ink)]/60">Nobody's out at the moment. Quiet week in the realm.</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// The board is state, not a story: an open tab should keep telling the truth
|
||||
// without a reload. Cheap poll — a handful of rows, no auth, no cache.
|
||||
(function () {
|
||||
var list = document.getElementById('roster-list');
|
||||
var status = document.getElementById('roster-status');
|
||||
var card = document.getElementById('roster-card');
|
||||
if (!list) return;
|
||||
|
||||
function esc(s) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = s == null ? '' : String(s);
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function row(a) {
|
||||
var idle = a.Idle ? ' <span class="text-[color:var(--ink)]/45">· ' + esc(a.Idle) + '</span>' : '';
|
||||
return '<li class="flex items-center gap-4 px-5 py-3">' +
|
||||
'<span class="text-lg" aria-hidden="true">' + (a.OnRun ? '⚔' : '🏠') + '</span>' +
|
||||
'<span class="font-semibold">' + esc(a.Name) + '</span>' +
|
||||
'<span class="text-sm text-[color:var(--ink)]/60">lv ' + esc(a.Level) + ' ' + esc(a.ClassRace) + '</span>' +
|
||||
'<span class="ml-auto text-sm ' + (a.OnRun ? 'font-semibold' : 'text-[color:var(--ink)]/60') + '">' +
|
||||
esc(a.Where) + idle + '</span></li>';
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
fetch('/api/roster', { headers: { 'Accept': 'application/json' } })
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (data) {
|
||||
if (!data) return;
|
||||
var rows = data.adventurers || [];
|
||||
list.innerHTML = rows.length
|
||||
? rows.map(row).join('')
|
||||
: '<li class="px-5 py-8 text-center text-[color:var(--ink)]/60">Nobody\'s out at the moment. Quiet week in the realm.</li>';
|
||||
status.textContent = data.stale ? "last known — the wire's gone quiet" : 'live';
|
||||
card.classList.toggle('opacity-60', !!data.stale);
|
||||
})
|
||||
.catch(function () { /* transient — the next tick will do */ });
|
||||
}
|
||||
|
||||
setInterval(refresh, 60000);
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (!document.hidden) refresh();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{if .Stories}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{{range .Stories}}
|
||||
|
||||
Reference in New Issue
Block a user