The who-page poll now keeps the location line honest both ways (a mark that came back to town stops showing its old expedition) and stops polling once the adventurer leaves the board. Also collapses a redundant branch in ResolveMischiefOrder and removes the always-false whoPage.Stale.
208 lines
10 KiB
HTML
208 lines
10 KiB
HTML
{{define "title"}}{{.Mark.Name}} — {{.SiteTitle}}{{end}}
|
|
|
|
{{define "main"}}
|
|
<article class="mt-2 mb-10 max-w-3xl mx-auto" id="who" data-token="{{.Mark.Token}}">
|
|
<nav class="mb-4">
|
|
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-[color:var(--ink)]/60 hover:text-[color:var(--ink)] transition">
|
|
<span aria-hidden="true">←</span> The board
|
|
</a>
|
|
</nav>
|
|
|
|
<header class="rounded-3xl bg-theme-adventure text-white p-6 sm:p-10 shadow-pete relative overflow-hidden">
|
|
<div class="absolute -top-6 -right-6 text-[12rem] opacity-20 select-none" aria-hidden="true">{{if .Mark.OnRun}}⚔{{else}}🏠{{end}}</div>
|
|
<div class="relative">
|
|
<p class="text-sm uppercase tracking-[0.2em] opacity-80">adventurer</p>
|
|
<h1 class="font-display text-3xl sm:text-4xl font-bold mt-2 leading-tight">{{.Mark.Name}}</h1>
|
|
<p class="mt-2 opacity-90">Level {{.Mark.Level}} {{.Mark.ClassRace}}</p>
|
|
<p class="mt-4 text-xs uppercase tracking-wider opacity-75" id="who-where">
|
|
{{if .Mark.OnRun}}{{.Mark.Where}}{{else}}In town{{if .Mark.Idle}} · {{.Mark.Idle}}{{end}}{{end}}
|
|
</p>
|
|
</div>
|
|
</header>
|
|
|
|
{{if .HasDetail}}
|
|
<!-- Stats + gear: public, the same anonymity model as the board. -->
|
|
<section class="mt-8 grid gap-6 sm:grid-cols-2">
|
|
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
|
<h2 class="font-display text-xl font-bold mb-4">Vitals</h2>
|
|
<div class="flex items-baseline justify-between">
|
|
<span class="text-sm uppercase tracking-wider text-[color:var(--ink)]/50">Hit points</span>
|
|
<span class="font-semibold" id="who-hp">{{.Detail.HPCurrent}} / {{.Detail.HPMax}}{{if .Detail.TempHP}} <span class="text-theme-adventure">+{{.Detail.TempHP}}</span>{{end}}</span>
|
|
</div>
|
|
<div class="mt-2 h-2 rounded-full bg-[color:var(--ink)]/10 overflow-hidden">
|
|
<div class="h-full rounded-full bg-theme-adventure transition-all" id="who-hp-bar" style="width:0%"></div>
|
|
</div>
|
|
<div class="mt-4 flex items-baseline justify-between">
|
|
<span class="text-sm uppercase tracking-wider text-[color:var(--ink)]/50">Armor class</span>
|
|
<span class="font-semibold" id="who-ac">{{.Detail.ArmorClass}}</span>
|
|
</div>
|
|
|
|
<div class="mt-5 grid grid-cols-3 gap-2">
|
|
{{range .Abilities}}
|
|
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
|
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50">{{.Label}}</div>
|
|
<div class="font-display text-lg font-bold leading-none mt-1">{{.Score}}</div>
|
|
<div class="text-xs text-[color:var(--ink)]/60">{{.ModStr}}</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if .Mark.OnRun}}
|
|
<div class="mt-5 pt-4 border-t border-[color:var(--ink)]/10 space-y-1.5 text-sm" id="who-expedition">
|
|
{{if .Detail.Room}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Room</span><span class="font-semibold" id="who-room">{{.Detail.Room}}</span></div>{{end}}
|
|
<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Supplies</span><span class="font-semibold" id="who-supplies">{{.Detail.Supplies}}</span></div>
|
|
{{if .Detail.ThreatLevel}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Threat</span><span class="font-semibold" id="who-threat">{{.Detail.ThreatLevel}}</span></div>{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
|
<h2 class="font-display text-xl font-bold mb-4">Gear</h2>
|
|
{{if .Detail.Gear}}
|
|
<ul class="space-y-2">
|
|
{{range .Detail.Gear}}
|
|
<li class="flex items-baseline justify-between gap-3">
|
|
<span class="text-xs uppercase tracking-wider text-[color:var(--ink)]/45 w-16 shrink-0">{{.Slot}}</span>
|
|
<span class="font-semibold flex-1">{{.Name}}{{if .Masterwork}} <span title="masterwork">★</span>{{end}}</span>
|
|
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">T{{.Tier}}{{if .Condition}} · {{.Condition}}%{{end}}</span>
|
|
</li>
|
|
{{end}}
|
|
</ul>
|
|
{{else}}
|
|
<p class="text-sm text-[color:var(--ink)]/50">Traveling light — nothing equipped.</p>
|
|
{{end}}
|
|
</div>
|
|
</section>
|
|
{{else}}
|
|
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
|
<p class="text-sm text-[color:var(--ink)]/60">No detailed sheet on file for this adventurer yet — check back after the next snapshot.</p>
|
|
</section>
|
|
{{end}}
|
|
|
|
{{if .HasSelf}}
|
|
<!-- Owner-only: this is you. Inventory, vault, house, pets — private, served
|
|
only because your signed-in localpart owns this page's token. -->
|
|
<section class="mt-8">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<span class="rounded-full bg-theme-adventure text-white text-xs font-semibold px-3 py-1">Your adventurer</span>
|
|
<span class="text-xs text-[color:var(--ink)]/45">only you can see the panels below</span>
|
|
</div>
|
|
|
|
<div class="grid gap-6 sm:grid-cols-2">
|
|
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
|
<h2 class="font-display text-xl font-bold mb-4">Home</h2>
|
|
<div class="space-y-1.5 text-sm">
|
|
<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">House tier</span><span class="font-semibold">{{if .Self.House.Tier}}{{.Self.House.Tier}}{{else}}no house{{end}}</span></div>
|
|
{{if .Self.House.LoanBalance}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Loan balance</span><span class="font-semibold">{{.Self.House.LoanBalance}}</span></div>{{end}}
|
|
{{if .Self.House.Rate}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Rate</span><span class="font-semibold">{{.Self.House.Rate}}</span></div>{{end}}
|
|
{{if .Self.House.Autopay}}<div class="flex justify-between"><span class="text-[color:var(--ink)]/50">Autopay</span><span class="font-semibold">on</span></div>{{end}}
|
|
</div>
|
|
|
|
<h3 class="font-display text-lg font-bold mt-6 mb-3">Pets</h3>
|
|
{{if .Self.Pets}}
|
|
<ul class="space-y-2 text-sm">
|
|
{{range .Self.Pets}}
|
|
<li class="flex items-baseline justify-between gap-3">
|
|
<span class="font-semibold flex-1">{{if .Name}}{{.Name}}{{else}}your {{.Type}}{{end}} <span class="text-[color:var(--ink)]/45 font-normal">{{.Type}}</span></span>
|
|
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">lv {{.Level}}{{if .ArmorTier}} · barding T{{.ArmorTier}}{{end}}</span>
|
|
</li>
|
|
{{end}}
|
|
</ul>
|
|
{{else}}
|
|
<p class="text-sm text-[color:var(--ink)]/50">No pets right now.</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
|
<h2 class="font-display text-xl font-bold mb-4">Backpack</h2>
|
|
{{if .Self.Inventory}}
|
|
<ul class="space-y-1.5 text-sm max-h-72 overflow-y-auto pr-1">
|
|
{{range .Self.Inventory}}
|
|
<li class="flex items-baseline justify-between gap-3">
|
|
<span class="flex-1">{{.Name}}{{if .Temper}} <span class="text-theme-adventure">+{{.Temper}}</span>{{end}}</span>
|
|
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">T{{.Tier}}{{if .Value}} · {{.Value}}g{{end}}</span>
|
|
</li>
|
|
{{end}}
|
|
</ul>
|
|
{{else}}
|
|
<p class="text-sm text-[color:var(--ink)]/50">Backpack's empty.</p>
|
|
{{end}}
|
|
|
|
{{if .Self.Vault}}
|
|
<h3 class="font-display text-lg font-bold mt-6 mb-3">Vault</h3>
|
|
<ul class="space-y-1.5 text-sm max-h-52 overflow-y-auto pr-1">
|
|
{{range .Self.Vault}}
|
|
<li class="flex items-baseline justify-between gap-3">
|
|
<span class="flex-1">{{.Name}}{{if .Temper}} <span class="text-theme-adventure">+{{.Temper}}</span>{{end}}</span>
|
|
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">T{{.Tier}}{{if .Value}} · {{.Value}}g{{end}}</span>
|
|
</li>
|
|
{{end}}
|
|
</ul>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{{end}}
|
|
</article>
|
|
|
|
<script>
|
|
// The sheet is state, like the board: keep an open tab honest without a reload.
|
|
// Public detail only (HP, room, supplies) — the private panels stay as rendered.
|
|
(function () {
|
|
var root = document.getElementById('who');
|
|
if (!root) return;
|
|
var token = root.getAttribute('data-token');
|
|
|
|
function setBar() {
|
|
var el = document.getElementById('who-hp-bar');
|
|
var hp = document.getElementById('who-hp');
|
|
if (!el || !hp) return;
|
|
var m = hp.textContent.match(/(-?\d+)\s*\/\s*(\d+)/);
|
|
if (!m) return;
|
|
var cur = parseInt(m[1], 10), max = parseInt(m[2], 10);
|
|
var pct = max > 0 ? Math.max(0, Math.min(100, Math.round(cur / max * 100))) : 0;
|
|
el.style.width = pct + '%';
|
|
}
|
|
setBar();
|
|
|
|
function txt(id, v) { var el = document.getElementById(id); if (el && v != null) el.textContent = v; }
|
|
|
|
var timer = null;
|
|
function refresh() {
|
|
fetch('/api/adventure/who/' + encodeURIComponent(token), { headers: { 'Accept': 'application/json' } })
|
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
.then(function (data) {
|
|
if (!data) return;
|
|
if (!data.live) {
|
|
// Off the board entirely (expedition ended, opted out): nothing more to
|
|
// poll for, so stop the timer rather than hammer a token that's gone.
|
|
txt('who-where', 'Back in town');
|
|
if (timer) clearInterval(timer);
|
|
return;
|
|
}
|
|
var d = data.detail || {};
|
|
var mark = data.mark || {};
|
|
if (data.has_detail) {
|
|
var temp = d.temp_hp ? ' +' + d.temp_hp : '';
|
|
txt('who-hp', d.hp_current + ' / ' + d.hp_max + temp);
|
|
txt('who-ac', d.armor_class);
|
|
txt('who-room', d.room);
|
|
txt('who-supplies', d.supplies);
|
|
txt('who-threat', d.threat_level);
|
|
setBar();
|
|
}
|
|
// Keep the location line honest in both directions: a mark that came back
|
|
// to town while still on the board must stop showing its old expedition.
|
|
if (mark.OnRun) {
|
|
txt('who-where', mark.Where);
|
|
} else {
|
|
txt('who-where', 'In town' + (mark.Idle ? ' · ' + mark.Idle : ''));
|
|
}
|
|
})
|
|
.catch(function () { /* transient — next tick will do */ });
|
|
}
|
|
timer = setInterval(refresh, 60000);
|
|
})();
|
|
</script>
|
|
{{end}}
|