From 4c2a2537231f5f492f5be650c8366050c4749932 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 16 Sep 2026 21:43:12 -0400 Subject: [PATCH] The tester's second pass: the rule as written, advice that follows the board, the fold, the story on the replay page, and one click to begin - Help said "walk, then act"; the rules let moves and cards come in any order, and now the help does too. - The council's advice is derived from the live view while its panel is open, so a Number just played for movement is no longer recommended. It rests when the turn is not yours; "ask again" had nothing to add. - The landing puts the name and the ways in before the clip, and beside it on a desktop: at 1280x720 the play button and Create/Join are above the fold. - The replay page opens with the same deed the share card promises, and "play free" stands beside the reel instead of after the history. - In hotseat the "pass the device" scrim stood above the roll-off, so the first click on "begin" landed on the handoff card. The handoff waits for the dice. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG --- packages/server/src/index.ts | 5 +- packages/web/src/App.svelte | 107 +++++++++++++++++++----------- packages/web/src/SharePage.svelte | 19 +++++- packages/web/src/rules.ts | 2 +- 4 files changed, 90 insertions(+), 43 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 4f4d657..e94854c 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -325,7 +325,10 @@ const httpServer = createServer((req, res) => { "cache-control": "public, max-age=60", "access-control-allow-origin": "*", }); - res.end(JSON.stringify({ steps: data.steps, actor: data.actor, round: data.round, whole: data.whole === true })); + res.end(JSON.stringify({ + steps: data.steps, actor: data.actor, round: data.round, whole: data.whole === true, + headline: data.whole ? null : headlineOf(data), + })); return; } const watch = url.match(/^\/watch\/([a-z0-9]{4,20})(\/og\.png)?$/); diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index c3aa866..557a721 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -1388,12 +1388,17 @@ } // The council of three: what each temperament would do from your seat. - let councilHints = $state(null); + // Its advice follows the board while the panel is open — a card just + // played is no longer recommended — and rests while it is not your turn. + let councilOpen = $state(false); + let hintsModule = $state(null); + const councilHints = $derived( + councilOpen && isYourTurn && view && hintsModule ? hintsModule.tableHints(view) : null, + ); async function askCouncil() { if (!view) return; - if (councilHints !== null) councilUnasked = false; // asked again: theirs to keep - const { tableHints } = await import("./hints"); - councilHints = tableHints(view); + hintsModule ??= await import("./hints"); + councilOpen = true; } /** "Play now": one tap from the landing to a game against a clockwork @@ -1466,7 +1471,7 @@ /** The unasked advice is for that one turn: it leaves when the turn does. */ let councilUnasked = $state(false); $effect(() => { - if (!view || !isYourTurn || prefs.coached || view.turn.round !== 1 || counciled || councilHints !== null) return; + if (!view || !isYourTurn || prefs.coached || view.turn.round !== 1 || counciled || councilOpen) return; counciled = true; councilUnasked = true; void askCouncil(); @@ -1476,7 +1481,7 @@ // touches it would leave this effect with nothing to wake it. const stillFirstTurn = isYourTurn && view?.turn.round === 1; if (!councilUnasked || stillFirstTurn) return; - councilHints = null; + councilOpen = false; councilUnasked = false; }); @@ -2235,7 +2240,7 @@ - {:else if local.handoffTo} + {:else if local.handoffTo && !openingRolls}
local.takeSeat()} onkeydown={(e) => e.key === "Enter" && local.takeSeat()}>
@@ -2248,39 +2253,45 @@ {#if !net.roomId && !local.active}
-
-
Wiz-War
-
Steal treasure. Blast through walls. Outwit your friends.
-
A turn-based wizard board game for two to six, free in your browser, with - friends or clockwork opponents. Carry home two treasures that aren't yours, or be the last wizard standing.
- - - Through a wizard's eyes: an ambush watches its corridor through a wormhole - - watch clips from the labyrinth - - -
- - or join one - -
- +
@@ -2874,7 +2885,7 @@ {#if councilHints}
The council considers your position… - +
{#if councilHints.length > 1 && councilHints.every((h) => h.advice === councilHints![0]!.advice)}
The council agrees: {councilHints[0]!.advice}
@@ -2887,7 +2898,6 @@
{/each} {/if} -
{/if} {#if isYourTurn && view.players.some((p) => p.alive && p.id !== view.you && @@ -3538,6 +3548,25 @@ } .hero { display: block; margin: 0.6rem auto 0; max-width: 24rem; border-radius: 6px; overflow: hidden; border: 1.5px solid #43331f; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35); } + /* The landing: the name and the ways in come before the clip on a + * phone, and stand beside it on a desktop, so the play button is never + * below the fold. */ + .lid-look { margin-top: 1.2rem; } + @media (min-width: 901px) { + .boxlid-inner.landing { + max-width: min(54rem, 100%); + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + grid-template-areas: "head head" "play look" "more more"; + column-gap: 2.2rem; + padding-top: 1.8rem; + } + .lid-head { grid-area: head; } + .lid-play { grid-area: play; } + .lid-look { grid-area: look; margin-top: 0; } + .boxlid-inner.landing .more-ways { grid-area: more; } + .boxlid-inner.landing .hero { margin-top: 0; } + } .hero img { display: block; width: 100%; } .play-now { display: block; margin: 0.2rem auto 0.8rem; font-size: 0.95rem; padding: 0.6rem 1.2rem; } .more-ways { margin-top: 1.2rem; } diff --git a/packages/web/src/SharePage.svelte b/packages/web/src/SharePage.svelte index f3241da..68bb6fc 100644 --- a/packages/web/src/SharePage.svelte +++ b/packages/web/src/SharePage.svelte @@ -15,6 +15,8 @@ actor: string; round: number; whole?: boolean; + /** The turn's deed in one line, as the share card says it. */ + headline?: string | null; } let data = $state(null); let failed = $state(false); @@ -32,7 +34,14 @@ {#if data} {/if} @@ -51,6 +60,9 @@ onclose={() => (reelKey += 1)} /> {/key}
+ {/if}
@@ -79,6 +90,7 @@ .share-head { display: flex; align-items: baseline; + flex-wrap: wrap; gap: 1rem; width: min(46rem, 100%); margin-bottom: 0.6rem; @@ -97,6 +109,9 @@ font-size: 0.85rem; color: #e9e1cb; } + .share-deed { display: block; } + .share-sub { display: block; text-transform: none; letter-spacing: 0.02em; color: #8d8672; font-size: 0.8rem; } + .share-play { width: min(46rem, 100%); margin-top: 0.8rem; text-align: center; } /* The reel is a modal everywhere else; here it stands in the page. */ .share-stage { width: min(46rem, 100%); } .share-stage :global(.replay-scrim) { diff --git a/packages/web/src/rules.ts b/packages/web/src/rules.ts index 2cde904..942e983 100644 --- a/packages/web/src/rules.ts +++ b/packages/web/src/rules.ts @@ -95,7 +95,7 @@ export const HOW_TO_PLAY: RulesSection[] = [ title: "Winning", body: [ "Carry two treasures that aren't yours home — one at a time, to your home square, and drop them there — or be the last wizard standing. Lose both of your own treasures to enemy homes and you're out.", - "A turn is: walk, then act. Step up to three squares, select a card and choose its target, and End turn. The banner above the board keeps count of your moves and your treasures home.", + "Move and play cards in any order, then end your turn. You have three steps and one attack a turn — a step, a spell, two more steps is a fine turn. The banner above the board keeps count of your moves and your treasures home.", ], }, {