Duration spells show themselves: chips on the sheet, size on the board

view.sustained was never rendered — every duration spell in play was
invisible, Big Man included, as playtesting found. Now each player's
scoresheet row lists the spells on them as violet chips (name and
turns remaining, tap to read the card), beside their displayed cards.
And on the board itself, Big Man makes the wizard token tower half
again as large, Shrink dwindles it — the exact at-a-glance "I have
become big" the table owes you.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 10:55:45 -04:00
co-authored by Claude Fable 5
parent 7ad3e7acc8
commit db1fbf7099
2 changed files with 24 additions and 6 deletions
+12 -1
View File
@@ -991,13 +991,22 @@
{p.handCount} cards{#if p.lostTurns > 0}&nbsp;· dazed {p.lostTurns}{/if}{#if p.carriedTreasureId}&nbsp;· 💰{/if}
</span>
</div>
{#if p.displayed.length > 0}
{@const spellsOn = view.sustained.filter((e) => e.targetId === p.id)}
{#if p.displayed.length > 0 || spellsOn.length > 0}
<div class="table-cards">
{#each p.displayed as c (c.instanceId)}
<button class="table-card" onclick={() => (peekCard = peekCard?.instanceId === c.instanceId ? null : c)}>
{cardDef(c.cardId).name}{#if view.wandCharges[c.instanceId] != null}&nbsp;·&nbsp;{"●".repeat(view.wandCharges[c.instanceId]!)}{/if}
</button>
{/each}
{#each spellsOn as e (e.id)}
<button class="table-card spell-chip" onclick={() => {
peekCard = { instanceId: `peek-${e.id}`, cardId: e.cardId };
peekCreatureId = null;
}}>
✦ {cardDef(e.cardId).name} · {e.remainingTurns}
</button>
{/each}
</div>
{/if}
{/each}
@@ -1602,6 +1611,8 @@
cursor: pointer;
}
.table-card:hover { background: #efe6cc; }
.table-card.spell-chip { background: #ece2f2; border-color: #8a6fa0; color: #4a3260; }
.table-card.spell-chip:hover { background: #e3d5ee; }
.score-row.dead { opacity: 0.5; text-decoration: line-through; }
.score-name { font-family: "Caveat", cursive; font-size: 1.25rem; line-height: 1; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.score-life { font-family: "Caveat", cursive; font-size: 1.4rem; font-weight: 700; min-width: 1.6rem; text-align: right; }
+12 -5
View File
@@ -74,6 +74,12 @@
if (cardId.endsWith("-wand")) return "magic-wand";
return null;
}
// BIG MAN towers; SHRINK dwindles. The token says so at a glance.
function wizardScale(id: string): number {
if (view.sustained.some((e) => e.cardId === "big-man" && e.targetId === id)) return 1.5;
if (view.sustained.some((e) => e.cardId === "shrink" && e.targetId === id)) return 0.62;
return 1;
}
function wizardArt(id: string): string {
return `/tokens/wizard-${colorIndexOf(id) % 6}.png`;
}
@@ -381,20 +387,21 @@
class="wizard"
>
{#if USE_TOKEN_ART}
{@const half = CELL * 0.3 * wizardScale(p.id)}
<image
href={wizardArt(p.id)}
x={cx - CELL * 0.3} y={cy - CELL * 0.3}
width={CELL * 0.6} height={CELL * 0.6}
x={cx - half} y={cy - half}
width={half * 2} height={half * 2}
preserveAspectRatio="xMidYMid slice"
class="token-art hit"
/>
<rect
x={cx - CELL * 0.3} y={cy - CELL * 0.3}
width={CELL * 0.6} height={CELL * 0.6}
x={cx - half} y={cy - half}
width={half * 2} height={half * 2}
class="wizard-ring" stroke={playerColor(p.id)}
/>
{:else}
<circle {cx} {cy} r={12} fill={playerColor(p.id)} stroke="#2b2218" stroke-width="2" />
<circle {cx} {cy} r={12 * wizardScale(p.id)} fill={playerColor(p.id)} stroke="#2b2218" stroke-width="2" />
<text x={cx} y={cy + 4} class="wizard-label">{p.id[0]?.toUpperCase()}</text>
{/if}
{#if p.carriedTreasureId}