Telepath's glimpse gets held up to the light

The engine sent the caster a private handRevealedPrivate event carrying
the victim's cards; the client logged the public line and dropped the
private one on the floor. The stolen mind is now a modal — the cards
fanned out, once, until dismissed — in the trap-notice pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-23 10:49:35 -04:00
co-authored by Claude Fable 5
parent 39ddbb452d
commit bde68aa85c
+38
View File
@@ -54,10 +54,16 @@
let trapNotice = $state<string | null>(null);
/** A Ward sprung on YOU earns the same courtesy. */
let wardBite = $state<{ owner: string; amount: number } | null>(null);
/** A mind YOU read: the stolen glimpse arrives once, in an event — hold
* it up until dismissed, or it is lost to the scrollback. */
let mindRead = $state<{ player: string; cards: CardInstance[] } | null>(null);
function playFx(events: Parameters<typeof scheduleFx>[0]) {
if (!view) return;
for (const e of events) {
if (e.type === "trapSprung" && e.player === view.you && e.cardId) trapNotice = e.cardId;
if (e.type === "handRevealedPrivate" && e.visibleTo === view.you) {
mindRead = { player: e.player, cards: e.cards };
}
if (e.type === "wardSprung" && e.victim === view.you) {
const dmg = events.find((d) => d.type === "damaged" && d.player === e.victim && d.source === "warded treasure");
wardBite = { owner: e.owner, amount: dmg?.type === "damaged" ? dmg.amount : 0 };
@@ -1332,6 +1338,28 @@
</div>
{/if}
{#if mindRead}
<div class="scrim attack-scrim" role="alertdialog" aria-label="a mind read">
<div class="attack-notice mind-read">
<div class="attack-headline">{mindRead.player}'s mind, laid bare</div>
<div class="mind-cards">
{#each mindRead.cards as card (card.instanceId)}
<Card {card} onfaq={(id) => (faqCardId = id)} />
{/each}
{#if mindRead.cards.length === 0}
<div class="attack-power">Nothing — their thoughts are empty-handed.</div>
{/if}
</div>
<div class="attack-power">
{mindRead.cards.length > 0
? `${mindRead.cards.length} card${mindRead.cards.length === 1 ? "" : "s"} commit them to memory; the glimpse does not linger.`
: ""}
</div>
<button class="stamp big" onclick={() => (mindRead = null)}>Enough</button>
</div>
</div>
{/if}
{#if wardBite}
<div class="scrim attack-scrim" role="alertdialog" aria-label="a warded treasure">
<div class="attack-notice">
@@ -2260,6 +2288,16 @@
.mast-room { margin-left: auto; font-size: 0.85rem; color: #a49c86; }
.mast-room b { color: #e9e1cb; letter-spacing: 0.12em; }
.mast-audience { font-size: 0.85rem; color: #a49c86; white-space: nowrap; }
.mind-read { max-width: min(92vw, 46rem); }
.mind-cards {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
max-height: 60vh;
overflow-y: auto;
padding: 0.4rem 0;
}
.gallery-note { font-size: 0.85rem; color: #8d8672; font-style: italic; padding: 0.4rem 0; }
.gallery-row { margin-top: 0.35rem; text-align: center; }
.gallery-row button:disabled { opacity: 0.4; cursor: default; }