web: the muted text that was never muted, and the stylesheet fetched every time

Every text-[color:var(--ink)]/50, border-[color:var(--ink)]/10 and
bg-[color:var(--ink)]/5 in the templates compiled to nothing. Tailwind v3
cannot put an opacity on an arbitrary var() colour, so all 538 of them were
dropped from output.css without a word. Meta text rendered at full ink, card
borders fell back to Tailwind's default grey (bright white rings at night),
and the washes, backdrops and hover pills simply weren't there, on the news
site, the casino and the adventure pages alike.

The palette is now written once per phase and casino room as bare rgb
channels (--ink-rgb and friends), the plain vars are built from them, and
tailwind.config.js exposes ink, muted, accent, warn and card as real colours.
The classes are text-ink/80, border-ink/10, bg-accent/20 now, and they compile.

Restoring the intended opacities as-is would have traded flat for illegible:
ink at 50% measures 2.7 to 2.9:1 on the lit phases. So secondary text gets
its own colour, --muted, per phase and per casino room, taken as faint as it
can go while still clearing 4.5:1 on both the card and the page. Everything
that was ink at 35 to 70% is text-muted. The 20-30% ones are separators and
empty-cell dashes and stay faint.

The bg-theme-* chips carry white text everywhere they appear, and eight of
them measured 2.5 to 3.2:1. Same hues, taken just dark enough to pass (4.6 to
4.7:1). eu, lego and adventure already passed and are unchanged, and the
underline and border colours keep the old pastels since they carry no text.

Static files came out of the embedded FS with no Last-Modified, no ETag and
no Cache-Control, so every page view downloaded the stylesheet and every
script in full, and the service worker's "cheap 304" never happened. Each
file is hashed once now. Templates link through {{asset}}, which appends
?v=<hash>; a request naming the current hash is served immutable for a year,
and everything else gets the hash as its ETag and revalidates to a 304. The
service worker serves versioned URLs cache-first, drops older versions of the
same file as new ones land, and moves to cache v6.
This commit is contained in:
prosolis
2026-09-22 22:54:50 -07:00
parent 4eaf8ae366
commit a15d67e210
40 changed files with 900 additions and 676 deletions
+95 -95
View File
@@ -9,16 +9,16 @@
<li>
<div class="flex items-baseline justify-between gap-3">
<span class="flex-1 font-semibold">{{.Name}}{{if .Temper}} <span class="text-theme-adventure">+{{.Temper}}</span>{{end}}</span>
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">{{if .Tier}}T{{.Tier}}{{end}}{{if .Value}}{{if .Tier}} · {{end}}{{.Value}}g{{end}}</span>
<span class="text-xs text-muted shrink-0">{{if .Tier}}T{{.Tier}}{{end}}{{if .Value}}{{if .Tier}} · {{end}}{{.Value}}g{{end}}</span>
</div>
{{if or .Slot .SkillSource .Attunement}}
<div class="flex flex-wrap gap-1.5 mt-1">
{{if .Slot}}<span class="text-[11px] rounded-full bg-[color:var(--ink)]/5 px-2 py-0.5 text-[color:var(--ink)]/60">{{.Slot}}</span>{{end}}
{{if .SkillSource}}<span class="text-[11px] rounded-full bg-[color:var(--ink)]/5 px-2 py-0.5 text-[color:var(--ink)]/60">{{.SkillSource}}</span>{{end}}
{{if .Attunement}}{{if .Attuned}}<span class="text-[11px] rounded-full bg-theme-adventure/20 px-2 py-0.5 text-theme-adventure font-semibold">bonded</span>{{else if .Worn}}<span class="text-[11px] rounded-full bg-amber-400/20 px-2 py-0.5 text-[color:var(--warn)] font-semibold">inert · no bond free</span>{{else}}<span class="text-[11px] rounded-full bg-[color:var(--ink)]/10 px-2 py-0.5 text-[color:var(--ink)]/60">needs a bond</span>{{end}}{{end}}
{{if .Slot}}<span class="text-[11px] rounded-full bg-ink/5 px-2 py-0.5 text-muted">{{.Slot}}</span>{{end}}
{{if .SkillSource}}<span class="text-[11px] rounded-full bg-ink/5 px-2 py-0.5 text-muted">{{.SkillSource}}</span>{{end}}
{{if .Attunement}}{{if .Attuned}}<span class="text-[11px] rounded-full bg-theme-adventure/20 px-2 py-0.5 text-theme-adventure font-semibold">bonded</span>{{else if .Worn}}<span class="text-[11px] rounded-full bg-amber-400/20 px-2 py-0.5 text-warn font-semibold">inert · no bond free</span>{{else}}<span class="text-[11px] rounded-full bg-ink/10 px-2 py-0.5 text-muted">needs a bond</span>{{end}}{{end}}
</div>
{{end}}
{{if .Desc}}<p class="text-xs text-[color:var(--ink)]/55 mt-1 leading-snug">{{.Desc}}</p>{{end}}
{{if .Desc}}<p class="text-xs text-muted mt-1 leading-snug">{{.Desc}}</p>{{end}}
{{if .Effect}}<p class="text-xs text-theme-adventure/80 mt-0.5">{{.Effect}}</p>{{end}}
{{/* The compare card: how this backpack item stacks up against what's worn in
the slot it would equip into. gogobee decided the verdict and the per-stat
@@ -35,7 +35,7 @@
{{else if eq .Verdict "same"}}<span class="cmp-chip cmp-same">= no change</span>{{end}}
{{range .Deltas}}<span class="cmp-delta {{if .Better}}cmp-delta-up{{else}}cmp-delta-down{{end}}">{{.Text}}</span>{{end}}
</div>
{{if .VsName}}<p class="text-[11px] text-[color:var(--ink)]/45 mt-0.5">vs worn {{.VsName}}</p>{{end}}
{{if .VsName}}<p class="text-[11px] text-muted mt-0.5">vs worn {{.VsName}}</p>{{end}}
{{end}}
{{if .EquipAction}}
<div class="mt-1.5">
@@ -64,7 +64,7 @@
{{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">
<a href="/adventure" class="inline-flex items-center gap-1.5 text-sm font-semibold text-muted hover:text-ink transition">
<span aria-hidden="true">←</span> The board
</a>
</nav>
@@ -84,35 +84,35 @@
{{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">
<div class="rounded-3xl bg-card border-2 border-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="text-sm uppercase tracking-wider text-muted">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="mt-2 h-2 rounded-full bg-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="text-sm uppercase tracking-wider text-muted">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="rounded-2xl bg-ink/5 px-2 py-3 text-center">
<div class="text-[10px] uppercase tracking-wider text-muted">{{.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 class="text-xs text-muted">{{.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 class="mt-5 pt-4 border-t border-ink/10 space-y-1.5 text-sm" id="who-expedition">
{{if .Detail.Room}}<div class="flex justify-between"><span class="text-muted">Room</span><span class="font-semibold" id="who-room">{{.Detail.Room}}</span></div>{{end}}
<div class="flex justify-between"><span class="text-muted">Supplies</span><span class="font-semibold" id="who-supplies">{{.Detail.Supplies}}</span></div>
{{if .Detail.ThreatLevel}}<div class="flex justify-between"><span class="text-muted">Threat</span><span class="font-semibold" id="who-threat">{{.Detail.ThreatLevel}}</span></div>{{end}}
</div>
{{end}}
@@ -123,8 +123,8 @@
An unnamed seat is an opted-out player, kept on purpose — see
partySeat.Anonymous for why it is not simply dropped. */}}
{{if .Detail.Party}}
<div class="mt-5 pt-4 border-t border-[color:var(--ink)]/10">
<h3 class="text-sm uppercase tracking-wider text-[color:var(--ink)]/50 mb-1">Down there together</h3>
<div class="mt-5 pt-4 border-t border-ink/10">
<h3 class="text-sm uppercase tracking-wider text-muted mb-1">Down there together</h3>
<ul>
{{range .Detail.Party}}
<li class="party-seat">
@@ -136,7 +136,7 @@
{{else}}
<span class="flex-1 text-sm font-semibold">{{.Name}}</span>
{{end}}
{{if .Level}}<span class="text-xs text-[color:var(--ink)]/50 shrink-0">lv {{.Level}}</span>{{end}}
{{if .Level}}<span class="text-xs text-muted shrink-0">lv {{.Level}}</span>{{end}}
</li>
{{end}}
</ul>
@@ -148,20 +148,20 @@
actually render (it needs pushed Slots) — otherwise an owner whose detail
predates ask 7's Slots would see no standard gear at all. */}}
{{if not .Self.Slots}}
<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
<div class="rounded-3xl bg-card border-2 border-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="text-xs uppercase tracking-wider text-muted 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>
<span class="text-xs text-muted 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>
<p class="text-sm text-muted">Traveling light — nothing equipped.</p>
{{end}}
</div>
{{end}}
@@ -172,15 +172,15 @@
full, the doors leading out of them, and the unopened rooms behind those
doors as question marks. Pete lays it out; it is never told what a room
holds, only that a door is there. -->
<section class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
<section class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
<div class="flex items-baseline justify-between mb-4">
<h2 class="font-display text-xl font-bold">The map</h2>
{{if .Detail.Room}}<span class="text-sm text-[color:var(--ink)]/50">Room <span class="font-semibold text-[color:var(--ink)]/70">{{.Detail.Room}}</span></span>{{end}}
{{if .Detail.Room}}<span class="text-sm text-muted">Room <span class="font-semibold text-muted">{{.Detail.Room}}</span></span>{{end}}
</div>
<div class="overflow-x-auto">
{{template "dungeonmap" .MapView}}
</div>
<div class="mt-4 flex flex-wrap gap-x-4 gap-y-1.5 text-xs text-[color:var(--ink)]/55">
<div class="mt-4 flex flex-wrap gap-x-4 gap-y-1.5 text-xs text-muted">
<span class="inline-flex items-center gap-1.5"><span class="map-dot map-node-entry"></span>entrance</span>
<span class="inline-flex items-center gap-1.5"><span class="map-dot map-node-boss"></span>boss</span>
<span class="inline-flex items-center gap-1.5"><span class="map-dot map-node-unknown"></span>unexplored</span>
@@ -189,8 +189,8 @@
</section>
{{end}}
{{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 class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
<p class="text-sm text-muted">No detailed sheet on file for this adventurer yet — check back after the next snapshot.</p>
</section>
{{end}}
@@ -199,10 +199,10 @@
own channel and can outlive the snapshot that drew the map — a run that
just ended still has a story after the board has moved the mark back to
town. Rebuilt in place by the same poll that moves the HP bar. -->
<section id="who-runlog-section" class="mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete{{if not .RunLog.Has}} hidden{{end}}">
<section id="who-runlog-section" class="mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete{{if not .RunLog.Has}} hidden{{end}}">
<div class="flex items-baseline justify-between mb-4 gap-3">
<h2 class="font-display text-xl font-bold">The run</h2>
<span id="who-runlog-status" class="text-sm text-[color:var(--ink)]/50">{{if .RunLog.Live}}under way{{else if .RunLog.Outcome}}{{.RunLog.Outcome}}{{else}}finished{{end}}{{if .RunLog.Rooms}} · room {{.RunLog.Rooms}}{{end}}</span>
<span id="who-runlog-status" class="text-sm text-muted">{{if .RunLog.Live}}under way{{else if .RunLog.Outcome}}{{.RunLog.Outcome}}{{else}}finished{{end}}{{if .RunLog.Rooms}} · room {{.RunLog.Rooms}}{{end}}</span>
</div>
<ol id="who-runlog" class="runlog">
{{range .RunLog.Lines}}
@@ -230,10 +230,10 @@
would be the page refusing an action the game would have allowed. gogobee
answers rejected_not_running if it really has ended, and that answer shows
up in the strip below. -->
<section id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete"
<section id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete"
data-balance="{{.Self.Balance}}">
<h2 class="font-display text-xl font-bold mb-1">Your call</h2>
<p class="text-sm text-[color:var(--ink)]/60 mb-4">
<p class="text-sm text-muted mb-4">
Asked for here, done on the game box. It picks these up within a few seconds.
</p>
{{/* data-offer marks what stops being true once the extraction lands: there
@@ -271,7 +271,7 @@
{{if .CanAbandon}}
<div data-offer class="mt-3">
<button type="button"
class="adv-action-btn rounded-full border border-[color:var(--warn)]/40 text-[color:var(--warn)] hover:bg-[color:var(--warn)]/10 px-4 py-1.5 text-sm font-semibold transition-colors"
class="adv-action-btn rounded-full border border-warn/40 text-warn hover:bg-warn/10 px-4 py-1.5 text-sm font-semibold transition-colors"
data-action="expedition_abandon"
data-label="Call the whole thing off"
data-confirm-tone="warn"
@@ -283,7 +283,7 @@
{{if .CanLeave}}
<div data-offer class="mt-3">
<button type="button"
class="adv-action-btn rounded-full border border-[color:var(--ink)]/25 text-[color:var(--ink)]/70 hover:bg-[color:var(--ink)]/10 px-4 py-1.5 text-sm font-semibold transition-colors"
class="adv-action-btn rounded-full border border-ink/25 text-muted hover:bg-ink/10 px-4 py-1.5 text-sm font-semibold transition-colors"
data-action="expedition_leave"
data-label="Turn back alone"
data-confirm-label="Yes, turn back"
@@ -301,11 +301,11 @@
{{/* The way back in, first: it is the only offer here with a deadline on it,
and it is the answer to the verdict "Pull out" leaves behind, which until
now told a web player to go and type !resume in Matrix. */}}
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
<div data-offer class="mt-5 pt-5 border-t border-ink/10">
<h3 class="font-display text-base font-bold">Go back in</h3>
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
<p class="text-sm text-muted mt-0.5 mb-2.5">
{{.Self.Resume.Display}}, day {{.Self.Resume.Day}} — waiting where you left it.
{{with untilUnix .Self.Resume.ExpiresAt}}<span class="text-[color:var(--warn)] font-semibold">{{.}}</span>.{{end}}
{{with untilUnix .Self.Resume.ExpiresAt}}<span class="text-warn font-semibold">{{.}}</span>.{{end}}
You re-stock before you go, so pick a pack.
</p>
<div class="flex flex-wrap gap-1.5">
@@ -328,15 +328,15 @@
{{/* Set out. The zone list is already level-gated and postgame-gated by
gogobee, and it arrives EMPTY while this adventurer is already out —
which is why there is no "you're on an expedition" branch here. */}}
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
<div data-offer class="mt-5 pt-5 border-t border-ink/10">
<h3 class="font-display text-base font-bold">Set out</h3>
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
<p class="text-sm text-muted mt-0.5 mb-2.5">
Pick where, then pick how much to carry. The pack is what you pay for.
</p>
<label class="block">
<span class="sr-only">Zone</span>
<select id="adv-zone-pick"
class="w-full rounded-xl bg-[color:var(--ink)]/5 border border-[color:var(--ink)]/15 px-3 py-2 text-sm font-semibold">
class="w-full rounded-xl bg-ink/5 border border-ink/15 px-3 py-2 text-sm font-semibold">
{{range .Self.Zones}}
<option value="{{.ID}}">{{.Display}} · T{{.Tier}}{{if .Postgame}} · mythic{{end}}</option>
{{end}}
@@ -348,7 +348,7 @@
{{range .Self.Zones}}
{{$z := .}}
<div class="adv-zone-loadouts mt-2.5 hidden" data-zone="{{$z.ID}}">
{{with $z.Hook}}<p class="text-xs italic text-[color:var(--ink)]/50 mb-2">{{.}}</p>{{end}}
{{with $z.Hook}}<p class="text-xs italic text-muted mb-2">{{.}}</p>{{end}}
<div class="flex flex-wrap gap-1.5">
{{range $z.Loadouts}}
<button type="button"
@@ -368,20 +368,20 @@
{{end}}
{{if .Self.Babysit}}
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
<div data-offer class="mt-5 pt-5 border-t border-ink/10">
<h3 class="font-display text-base font-bold">The sitter</h3>
{{if .Self.Babysit.Active}}
{{/* Engaged: say so, and offer only the way back out. Buying a second one
is refused by the game anyway, and a buy button under "already engaged"
reads as a page that has not noticed. */}}
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5">
<p class="text-sm text-muted mt-0.5">
Somebody is looking after the camp{{with untilUnix .Self.Babysit.ExpiresAt}} — {{.}}{{end}}.
Your pet is being tended daily and standard camps rest like fortified ones.
</p>
{{if .CanCancelSitter}}
<div class="flex flex-wrap gap-1.5 mt-2.5">
<button type="button"
class="adv-action-btn rounded-full border border-[color:var(--ink)]/25 text-[color:var(--ink)]/70 hover:bg-[color:var(--ink)]/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
class="adv-action-btn rounded-full border border-ink/25 text-muted hover:bg-ink/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
data-action="babysit_cancel"
data-label="Send them home"
data-confirm-label="Yes, send them home"
@@ -389,7 +389,7 @@
</div>
{{end}}
{{else}}
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
<p class="text-sm text-muted mt-0.5 mb-2.5">
Pet tended daily, standard camps rest like fortified ones, rival duels declined for you.
</p>
<div class="flex flex-wrap gap-1.5">
@@ -425,29 +425,29 @@
same information the /adventure feed already printed, only as numbers
instead of as forty separate sentences. Not a gogobee snapshot: Pete
counted these itself out of the facts it kept. -->
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
<section class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
<h2 class="font-display text-xl font-bold mb-4">The record</h2>
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.BossKills}}</div>
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">bosses down</div>
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">bosses down</div>
</div>
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.ZoneClears}}</div>
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">zones cleared</div>
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">zones cleared</div>
</div>
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.TreasuresFound}}</div>
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">treasure{{if ne .Trophies.TreasuresFound 1}}s{{end}}</div>
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">treasure{{if ne .Trophies.TreasuresFound 1}}s{{end}}</div>
</div>
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.Deaths}}</div>
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">death{{if ne .Trophies.Deaths 1}}s{{end}}</div>
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">death{{if ne .Trophies.Deaths 1}}s{{end}}</div>
</div>
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
<div class="rounded-2xl bg-ink/5 px-2 py-3 text-center">
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.Retreats}}</div>
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">walked out</div>
<div class="text-[10px] uppercase tracking-wider text-muted mt-1.5">walked out</div>
</div>
</div>
@@ -465,7 +465,7 @@
{{range .Trophies.Bosses}}
<li class="flex items-baseline justify-between gap-3">
<span class="flex-1">{{.Boss}}{{if .First}} <span class="text-theme-adventure" title="first in the realm to clear it">★</span>{{end}}</span>
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">×{{.Kills}}</span>
<span class="text-xs text-muted shrink-0">×{{.Kills}}</span>
</li>
{{end}}
</ul>
@@ -478,8 +478,8 @@
<ul class="space-y-1.5 text-sm max-h-56 overflow-y-auto pr-1">
{{range .Trophies.Zones}}
<li class="flex items-baseline justify-between gap-3">
<span class="flex-1">{{.Zone}}{{if .First}} <span class="text-theme-adventure" title="first clear in the realm">★</span>{{end}}{{if .Region}} <span class="text-[color:var(--ink)]/45">{{.Region}}</span>{{end}}</span>
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">×{{.Clears}}</span>
<span class="flex-1">{{.Zone}}{{if .First}} <span class="text-theme-adventure" title="first clear in the realm">★</span>{{end}}{{if .Region}} <span class="text-muted">{{.Region}}</span>{{end}}</span>
<span class="text-xs text-muted shrink-0">×{{.Clears}}</span>
</li>
{{end}}
</ul>
@@ -488,13 +488,13 @@
</div>
{{if .Trophies.Treasures}}
<div class="mt-6 pt-4 border-t border-[color:var(--ink)]/10">
<div class="mt-6 pt-4 border-t border-ink/10">
<h3 class="font-display text-lg font-bold mb-3">Treasures found</h3>
<ul class="space-y-1.5 text-sm max-h-56 overflow-y-auto pr-1">
{{range .Trophies.Treasures}}
<li class="flex items-baseline justify-between gap-3">
<span class="flex-1">💎 {{.Item}}{{if .First}} <span class="text-theme-adventure" title="first in the realm to pull this hoard">★</span>{{end}}</span>
{{if .Zone}}<span class="text-xs text-[color:var(--ink)]/45 shrink-0">{{.Zone}}</span>{{end}}
{{if .Zone}}<span class="text-xs text-muted shrink-0">{{.Zone}}</span>{{end}}
</li>
{{end}}
</ul>
@@ -502,7 +502,7 @@
{{end}}
{{if .Trophies.Milestones}}
<div class="mt-6 pt-4 border-t border-[color:var(--ink)]/10">
<div class="mt-6 pt-4 border-t border-ink/10">
<h3 class="font-display text-lg font-bold mb-3">Milestones</h3>
<div class="flex flex-wrap gap-2">
{{range .Trophies.Milestones}}
@@ -515,24 +515,24 @@
<!-- The trail: every fact we have, newest first, each linking to the dispatch
that told it. -->
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
<section class="mt-8 rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
<h2 class="font-display text-xl font-bold mb-4">The trail</h2>
<ol class="relative border-l-2 border-[color:var(--ink)]/10 ml-3 space-y-4">
<ol class="relative border-l-2 border-ink/10 ml-3 space-y-4">
{{range .Timeline}}
<li class="relative pl-6">
<span class="absolute -left-[13px] top-0.5 flex h-6 w-6 items-center justify-center rounded-full bg-[color:var(--card)] border-2 {{if .Notable}}border-theme-adventure{{else}}border-[color:var(--ink)]/10{{end}} text-xs" aria-hidden="true">{{.Emoji}}</span>
<span class="absolute -left-[13px] top-0.5 flex h-6 w-6 items-center justify-center rounded-full bg-card border-2 {{if .Notable}}border-theme-adventure{{else}}border-ink/10{{end}} text-xs" aria-hidden="true">{{.Emoji}}</span>
<a href="{{.Permalink}}" class="group block">
<div class="flex items-baseline justify-between gap-3">
<span class="font-semibold group-hover:text-theme-adventure transition {{if .Notable}}text-theme-adventure{{end}}">{{.Label}}</span>
<span class="text-xs text-[color:var(--ink)]/45 shrink-0">{{.When}}</span>
<span class="text-xs text-muted shrink-0">{{.When}}</span>
</div>
{{if .Line}}<p class="text-sm text-[color:var(--ink)]/60 mt-0.5">{{.Line}}</p>{{end}}
{{if .Line}}<p class="text-sm text-muted mt-0.5">{{.Line}}</p>{{end}}
</a>
</li>
{{end}}
</ol>
{{if .MoreHistory}}
<p class="mt-5 pt-4 border-t border-[color:var(--ink)]/10 text-xs text-[color:var(--ink)]/45">
<p class="mt-5 pt-4 border-t border-ink/10 text-xs text-muted">
Showing the most recent {{len .Timeline}}. The counts above cover everything.
</p>
{{end}}
@@ -545,7 +545,7 @@
<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>
<span class="text-xs text-muted">only you can see the panels below</span>
</div>
{{/* Equipment: the 5 standard slots, owner-only management. Take off round-trips
@@ -555,17 +555,17 @@
resolved server-side — the buttons only carry what was pushed. */}}
{{if .Self.Slots}}
<div id="equip-panel" data-token="{{.Mark.Token}}" data-balance="{{printf "%.2f" .Self.Balance}}"
class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete mb-6">
class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete mb-6">
<div class="flex items-baseline justify-between mb-4">
<h2 class="font-display text-xl font-bold">Equipment</h2>
<span class="text-xs text-[color:var(--ink)]/50">Balance <span class="font-semibold text-[color:var(--ink)]/70">€{{printf "%.2f" .Self.Balance}}</span></span>
<span class="text-xs text-muted">Balance <span class="font-semibold text-muted">€{{printf "%.2f" .Self.Balance}}</span></span>
</div>
<ul class="grid gap-3 sm:grid-cols-2">
{{range .Self.Slots}}
<li class="slot-card rounded-2xl bg-[color:var(--ink)]/5 p-4">
<li class="slot-card rounded-2xl bg-ink/5 p-4">
<div class="flex items-baseline justify-between gap-2">
<span class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/45">{{.Slot}}</span>
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">T{{.Tier}}{{if .Condition}} · {{.Condition}}%{{end}}</span>
<span class="text-[10px] uppercase tracking-wider text-muted">{{.Slot}}</span>
<span class="text-xs text-muted shrink-0">T{{.Tier}}{{if .Condition}} · {{.Condition}}%{{end}}</span>
</div>
<div class="font-semibold mt-0.5 flex items-center flex-wrap gap-1.5">
<span>{{.Name}}</span>
@@ -578,7 +578,7 @@
data-action="unequip" data-slot="{{.Slot}}" data-item-name="{{.Name}}">Take off</button>{{end}}
{{if .NextTier}}<button type="button" class="equip-btn text-[11px] rounded-full border border-theme-adventure/40 text-theme-adventure hover:bg-theme-adventure/10 px-2.5 py-0.5 font-semibold transition-colors"
data-action="upgrade" data-slot="{{.Slot}}" data-tier="{{.NextTier}}" data-cost="{{printf "%.2f" .NextPrice}}" data-item-name="{{.NextName}}">Upgrade to {{.NextName}} · €{{printf "%.0f" .NextPrice}}</button>{{end}}
{{if .RepairCost}}<button type="button" class="equip-btn text-[11px] rounded-full border border-[color:var(--warn)]/40 text-[color:var(--warn)] hover:bg-amber-400/10 px-2.5 py-0.5 font-semibold transition-colors"
{{if .RepairCost}}<button type="button" class="equip-btn text-[11px] rounded-full border border-warn/40 text-warn hover:bg-amber-400/10 px-2.5 py-0.5 font-semibold transition-colors"
data-action="repair" data-slot="{{.Slot}}" data-cost="{{.RepairCost}}" data-item-name="{{.Name}}">Repair · €{{.RepairCost}}</button>{{end}}
</div>
{{end}}
@@ -589,13 +589,13 @@
{{end}}
<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">
<div class="rounded-3xl bg-card border-2 border-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 class="flex justify-between"><span class="text-muted">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-muted">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-muted">Rate</span><span class="font-semibold">{{.Self.House.Rate}}</span></div>{{end}}
{{if .Self.House.Autopay}}<div class="flex justify-between"><span class="text-muted">Autopay</span><span class="font-semibold">on</span></div>{{end}}
</div>
{{/* Pets, with their levelling shown. They have been earning XP from every
@@ -610,27 +610,27 @@
{{range .PetRows}}
<li>
<div 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>
<span class="font-semibold flex-1">{{if .Name}}{{.Name}}{{else}}your {{.Type}}{{end}} <span class="text-muted font-normal">{{.Type}}</span></span>
<span class="text-xs text-muted shrink-0">lv {{.Level}}{{if .ArmorTier}} · barding T{{.ArmorTier}}{{end}}</span>
</div>
<div class="pet-xp-track mt-1.5">
<div class="pet-xp-fill{{if .Capped}} pet-xp-capped{{end}}" style="width: {{if .Capped}}100{{else}}{{.Percent}}{{end}}%"></div>
</div>
<p class="mt-1 text-xs text-[color:var(--ink)]/45">
<p class="mt-1 text-xs text-muted">
{{if .Capped}}fully grown{{else}}{{.Progress}} to level {{.NextLevel}}{{end}}
</p>
</li>
{{end}}
</ul>
{{else}}
<p class="text-sm text-[color:var(--ink)]/50">No pets right now.</p>
<p class="text-sm text-muted">No pets right now.</p>
{{end}}
</div>
<div id="gear-panel" data-token="{{.Mark.Token}}" class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
<div id="gear-panel" data-token="{{.Mark.Token}}" class="rounded-3xl bg-card border-2 border-ink/10 p-6 shadow-pete">
{{if .Worn}}
<h2 class="font-display text-xl font-bold mb-1">Worn</h2>
<p class="text-xs text-[color:var(--ink)]/45 mb-3">{{.BondsUsed}} of 3 bonds in use</p>
<p class="text-xs text-muted mb-3">{{.BondsUsed}} of 3 bonds in use</p>
<ul class="space-y-2.5 text-sm">
{{range .Worn}}{{template "itemrow" .}}{{end}}
</ul>
@@ -643,7 +643,7 @@
{{range .Backpack}}{{template "itemrow" .}}{{end}}
</ul>
{{else}}
<p class="text-sm text-[color:var(--ink)]/50">Backpack's empty.</p>
<p class="text-sm text-muted">Backpack's empty.</p>
{{end}}
{{if .VaultRows}}
@@ -842,8 +842,8 @@
left.textContent = verb + ' ' + (o.item_name || o.slot || 'item');
var right = document.createElement('span');
right.className = 'shrink-0 ' + (o.status === 'pending'
? 'text-[color:var(--ink)]/45'
: (o.status === 'applied' ? 'text-theme-adventure font-semibold' : 'text-[color:var(--warn)]'));
? 'text-muted'
: (o.status === 'applied' ? 'text-theme-adventure font-semibold' : 'text-warn'));
right.textContent = o.detail || STATUS[o.status] || o.status;
li.appendChild(left); li.appendChild(right);
list.appendChild(li);
@@ -913,9 +913,9 @@
var head = action === 'upgrade' ? ('Upgrade to ' + name) : ('Repair ' + name);
var boxEl = document.createElement('div');
boxEl.className = 'equip-confirm mt-2 rounded-xl bg-[color:var(--ink)]/5 p-2.5 text-xs';
boxEl.className = 'equip-confirm mt-2 rounded-xl bg-ink/5 p-2.5 text-xs';
var p = document.createElement('p');
p.className = 'text-[color:var(--ink)]/70';
p.className = 'text-muted';
p.textContent = head + ' for €' + euroFmt(cost) + '? Balance €' + euroFmt(balance) + ' → €' + euroFmt(balance - cost) + '.';
var row = document.createElement('div');
row.className = 'mt-2 flex gap-1.5';
@@ -926,7 +926,7 @@
yes.addEventListener('click', function () { boxEl.remove(); placeOrder(btn); });
var no = document.createElement('button');
no.type = 'button';
no.className = 'rounded-full border border-[color:var(--ink)]/20 text-[color:var(--ink)]/60 px-2.5 py-0.5';
no.className = 'rounded-full border border-ink/20 text-muted px-2.5 py-0.5';
no.textContent = 'Cancel';
no.addEventListener('click', function () { boxEl.remove(); });
row.appendChild(yes);
@@ -954,4 +954,4 @@
</script>
{{end}}
{{define "scripts"}}<script src="/static/js/adventure-actions.js" defer></script>{{end}}
{{define "scripts"}}<script src="{{asset "/static/js/adventure-actions.js"}}" defer></script>{{end}}