The help becomes a reference desk: searchable, linkable, and it remembers its place

The rules tab is one body — the condensed rules, the rulebook, the
expansion, and the FAQ — with a contents list, a search that labels
each hit by source, and a link on every section. The rulebook's
emphasis renders, and the (6E: …) notes are set apart from the original
words. The card library gains filters for set, type, and target, ranks
an exact name first, counts its results, and says so when there are
none; a card opens as an entry: the card itself beside reading-size
text with the printed wording, deck counts, the official FAQ, this
table's rulings, and links to the cards it names. The house rulings are
organized by card, in the present tense, with the revision history kept
off the page. Every page and card has a link (/?help=rules/…, /?card=…)
that opens the desk to it, and the desk reopens on the tab, search, and
scroll position it was closed at.

About leads with the worn box and the friends, shows the workshops as a
small gallery with a direct link to the demo reel, and says plainly
what an online ledger keeps and what a passed-around device keeps to
itself. The tally leads with finished games, treasure and combat wins,
and hours played, and explains that wizards are counted by name and
table time by the clock between moves.

Escape closes only the foremost layer and returns focus to the card
that opened it; the tabs wrap on a phone instead of clipping.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-16 22:13:44 -04:00
co-authored by Claude Fable 5.1
parent 4c2a253723
commit 9dee01d393
4 changed files with 816 additions and 225 deletions
+37 -3
View File
@@ -9,6 +9,7 @@
import Faq from "./Faq.svelte";
import Card from "./Card.svelte";
import Help from "./Help.svelte";
import type { HelpTab } from "./help-state";
import Replay from "./Replay.svelte";
import FxGallery from "./FxGallery.svelte";
import TokenGallery from "./TokenGallery.svelte";
@@ -257,7 +258,39 @@
let faqCardId = $state<string | null>(null);
/** A discard-pile card enlarged above the pile. */
let discardPeek = $state<CardInstance | null>(null);
let helpTab = $state<"play" | "rules" | "cards" | "rulings" | "about" | "tally">("play");
/** The help's opening tab; null reopens it where the reader left off. */
let helpTab = $state<HelpTab | null>(null);
let helpAnchor = $state<string | null>(null);
let helpCard = $state<string | null>(null);
// A link into the reference — /?help=rules/rulebook-line-of-sight or
// /?card=force-field — opens the desk to that page.
{
const q = new URLSearchParams(location.search);
const card = q.get("card");
const help = q.get("help");
const tabs: HelpTab[] = ["play", "rules", "cards", "rulings", "about", "tally"];
if (card) { helpCard = card; showHelp = true; }
else if (help) {
const [t, ...rest] = help.split("/");
if (tabs.includes(t as HelpTab)) { helpTab = t as HelpTab; helpAnchor = rest.join("/") || null; showHelp = true; }
}
}
/** The address bar follows the reader through the help, so any page is a link. */
function helpNavigate(tab: HelpTab, anchor: string | null, card: string | null) {
const u = new URL(location.href);
u.searchParams.delete("help"); u.searchParams.delete("card");
if (card) u.searchParams.set("card", card);
else u.searchParams.set("help", anchor ? `${tab}/${anchor}` : tab);
// The slash stays a slash: /?help=rules/the-cards reads as a path should.
u.search = u.searchParams.toString().replace(/%2F/g, "/");
history.replaceState(null, "", u);
}
function helpClose() {
showHelp = false; helpAnchor = null; helpCard = null; helpTab = null;
const u = new URL(location.href);
u.searchParams.delete("help"); u.searchParams.delete("card");
history.replaceState(null, "", u);
}
let hotseatCount = $state(2);
let setupName = $state("");
let setupColor = $state(0);
@@ -1695,7 +1728,7 @@
<button class="mast-leave" title="something behaved unexpectedly? tell the wizards"
onclick={() => (feedbackOpen = true)}>🐞 report</button>
{/if}
<button class="mast-leave" class:mast-help-solo={!net.roomId} onclick={() => { helpTab = "play"; showHelp = true; }}>
<button class="mast-leave" class:mast-help-solo={!net.roomId} onclick={() => { helpTab = null; showHelp = true; }}>
help &amp; rules
</button>
<span class="mast-status" class:offline={net.status !== "connected"}>
@@ -1969,7 +2002,8 @@
{/if}
{#if showHelp}
<Help initialTab={helpTab} stats={net.stats} onstats={() => net.requestStats()} onclose={() => (showHelp = false)} />
<Help initialTab={helpTab} initialAnchor={helpAnchor} initialCard={helpCard} stats={net.stats}
onstats={() => net.requestStats()} onnavigate={helpNavigate} onclose={helpClose} />
{/if}
{#if view?.phase === "finished" && view.winner && victoryCurtain && !victorySeen}