From aeb58bdf4a60d73badf2a6de4c8025616b67f419 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sat, 3 Oct 2026 20:36:27 -0400 Subject: [PATCH] A treasure carried home says so, and a turn out of steps points at End turn A first-time player banked a stolen chest, stepped off with no moves left, and left (NQ6S): the score changed only in the turn bar's quiet tally, and a still board with no word read as a game that had stopped. Now a slip announces the score for the rest of the turn, and once the steps are spent the turn bar says to cast or end the turn while End turn beckons. Co-Authored-By: Claude Opus 5.5 --- packages/web/src/App.svelte | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 36dfcc2..93e5f30 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -1756,6 +1756,20 @@ view && me ? view.treasures.filter((t) => t.owner !== me!.id && t.position && t.position.x === me!.home.x && t.position.y === me!.home.y).length : 0, ); + /** Chests you carried home THIS turn: the drop is the game's biggest + * moment, and the turn bar's quiet tally alone let it pass unremarked. */ + let scoredThisTurn = $state(0); + let scoreTurnKey = ""; + let homeAtTurnStart = 0; + $effect(() => { + const key = view && view.activePlayerId === view.you ? `${view.turn.round}:${view.activePlayerId}` : ""; + if (key !== scoreTurnKey) { scoreTurnKey = key; homeAtTurnStart = treasuresHome; } + scoredThisTurn = key ? Math.max(0, treasuresHome - homeAtTurnStart) : 0; + }); + /** Steps spent, actions not: the turn waits on a spell or End turn, + * and a still board with no word reads as a game that froze. */ + const movesSpent = $derived(!!view && view.activePlayerId === view.you && !view.turn.actionsEnded && + view.turn.movementAllowance - view.turn.movementUsed <= 0); const paBurnable = $derived(me ? Array.from({ length: Math.max(0, me.life - 1) }, (_, i) => i + 1) : []); /** Two treasures can share a square: one dispatches, more ask whose. */ const treasuresHere = $derived( @@ -3291,8 +3305,16 @@ {/if} {:else} {view.turn.movementAllowance - view.turn.movementUsed} moves left{view.turn.attackUsed ? " ยท attack spent" : ""} ยท treasures home {treasuresHome} of 2 + {#if movesSpent} +
Out of steps โ€” cast a spell, or End turn.
+ {/if} {/if} + {#if scoredThisTurn > 0 && treasuresHome < 2} +
+ ๐Ÿ† Scored โ€” {treasuresHome} of 2. One more stolen treasure carried home wins the game. +
+ {/if} {#if !prefs.coached && !view.turn.actionsEnded} @@ -3658,7 +3680,7 @@ {:else if net.confirmed} โœ“ {net.confirmed} confirmed {/if} - + {/if} @@ -4401,6 +4423,14 @@ .stamp:disabled { opacity: 0.45; cursor: default; } .stamp.primary { background: #43331f; color: #e9e1cb; } .stamp.primary:hover:not(:disabled) { background: #5a4429; } + .stamp.beckon:not(:disabled) { animation: beckon 1.6s ease-in-out infinite; } + @keyframes beckon { + 0%, 100% { box-shadow: 2px 2px 0 rgba(0,0,0,0.35), 0 0 0 0 rgba(201, 167, 42, 0); } + 50% { box-shadow: 2px 2px 0 rgba(0,0,0,0.35), 0 0 0 4px rgba(201, 167, 42, 0.55); } + } + @media (prefers-reduced-motion: reduce) { + .stamp.beckon:not(:disabled) { animation: none; box-shadow: 0 0 0 3px rgba(201, 167, 42, 0.7); } + } .stamp.big { font-size: 0.95rem; padding: 0.6rem 1.3rem; } .stamp.tiny { font-size: 0.62rem; padding: 0.22rem 0.5rem; box-shadow: 1px 1px 0 rgba(0,0,0,0.3); } .prefs-slip { text-align: left; } @@ -4598,6 +4628,7 @@ .slip.urgent { border-left: 4px solid #b3372b; transform: rotate(0.4deg); } .slip.ambush-note { border-left: 4px solid #43331f; font-size: 0.85rem; } .slip.catchup { border-left: 4px solid #5b3f9e; } + .slip.scored { border-left: 4px solid #c9a72a; background: #f3e7b8; } .catchup-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; } .catchup-facts { list-style: none; margin: 0.45rem 0 0; padding: 0; font-size: 0.9rem; } .catchup-facts li { margin: 0.15rem 0; }