Victory gets its moment: a gold-ringed proclamation

Winning showed only as a small slip in the rail — anticlimactic, as
playtesting put it. The engine now records HOW the game was won
(winReason: treasures carried home, or last wizard standing) alongside
the winner, the view carries it, and the finish opens a gold-ringed
modal: trophy, the winner's name in tall caps, and the manner of
victory in a handwritten line. "Behold the final board" dismisses it
to the finished table. In hotseat the one screen proclaims to the
whole table at once; in async games each player is greeted by it when
they next open the game.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 11:11:24 -04:00
co-authored by Claude Fable 5
parent 8f7beb5ef3
commit 13aeeea0ef
3 changed files with 49 additions and 0 deletions
+43
View File
@@ -15,6 +15,8 @@
let joinCode = $state("");
let claimPhrase = $state("");
let showHelp = $state(false);
/** The finished game whose victory fanfare has been dismissed. */
let victorySeen = $state(false);
/** Which pending attack the player has already acknowledged (modal dismissed). */
let attackNoticeSeen = $state<string | null>(null);
/** A face-up card being peeked at from the scoresheet (view-only). */
@@ -697,6 +699,25 @@
<Help initialTab={helpTab} onclose={() => (showHelp = false)} />
{/if}
{#if view?.phase === "finished" && view.winner && !victorySeen}
<div class="scrim attack-scrim" role="alertdialog" aria-label="the game is won">
<div class="attack-notice victory-notice">
<div class="victory-trophy">🏆</div>
<div class="victory-name">{view.winner}</div>
<div class="victory-how">
{#if view.winReason === "treasures"}
carried two stolen treasures home — the maze is theirs
{:else if view.winReason === "lastStanding"}
the last wizard standing
{:else}
wins the game
{/if}
</div>
<button class="stamp big" onclick={() => (victorySeen = true)}>Behold the final board</button>
</div>
</div>
{/if}
{#if view?.stack && attackNoticeKey && attackNoticeSeen !== attackNoticeKey}
<div class="scrim attack-scrim" role="alertdialog" aria-label="you are under attack">
<div class="attack-notice">
@@ -1750,6 +1771,17 @@
max-width: 11rem;
}
.victory-notice { border-color: #a5842c; box-shadow: 0 0 0 4px rgba(201, 167, 42, 0.35), 0 18px 50px rgba(0, 0, 0, 0.6); }
.victory-trophy { font-size: 3rem; line-height: 1; }
.victory-name {
font-family: "Oswald", sans-serif;
font-size: 1.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #43331f;
}
.victory-how { font-family: "Caveat", cursive; font-size: 1.25rem; color: #6b5a41; }
.inspector {
position: fixed;
right: 0.9rem;
@@ -1829,6 +1861,17 @@
max-width: 11rem;
}
.victory-notice { border-color: #a5842c; box-shadow: 0 0 0 4px rgba(201, 167, 42, 0.35), 0 18px 50px rgba(0, 0, 0, 0.6); }
.victory-trophy { font-size: 3rem; line-height: 1; }
.victory-name {
font-family: "Oswald", sans-serif;
font-size: 1.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #43331f;
}
.victory-how { font-family: "Caveat", cursive; font-size: 1.25rem; color: #6b5a41; }
.inspector { right: 0.5rem; bottom: 0.5rem; }
.inspector-card { transform: scale(1.3); }
}