diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte
index 62a019d..975798f 100644
--- a/packages/web/src/App.svelte
+++ b/packages/web/src/App.svelte
@@ -89,6 +89,16 @@
const youMustDiscard = $derived(view != null && view.pendingDiscard === view.you);
const youMustShield = $derived(view?.chaosPending?.queue[0] === view?.you && view != null);
const holdingWard = $derived(view?.yourHand.some((c) => c.cardId === "ward") ?? false);
+ /** A live moment to interrupt: another wizard acts, no attack is pending. */
+ const canInterruptNow = $derived(
+ view != null && !isYourTurn && !view.stack && !view.chaosPending &&
+ !view.outOfTurnWindow && view.phase === "playing" && view.turn.round > 1 && !local.active,
+ );
+ const interruptCards = $derived(
+ canInterruptNow
+ ? view!.yourHand.filter((c) => c.cardId === "interrupt" || c.cardId === "opportunity-fire")
+ : [],
+ );
const selectedDef = $derived(selectedCard ? cardDef(selectedCard.cardId) : null);
const EDGE_CARDS = new Set([
@@ -1004,6 +1014,11 @@
{:else}
{view.activePlayerId} is taking their turn…
{/if}
+ {#if interruptCards.length > 0}
+
+ ⚡ Your {cardDef(interruptCards[0]!.cardId).name} can seize this moment — tap the glowing card.
+
+ {/if}
{#if view.yourWardArmed}
🗡 Your ward is set — a thief who grabs your treasure bleeds for 3.
@@ -1272,6 +1287,7 @@
attachedMods.some((m) => m.instanceId === card.instanceId)}
marked={discardSelection.has(card.instanceId)}
displayed={view.players.find((p) => p.id === view.you)?.displayed.some((c) => c.instanceId === card.instanceId) ?? false}
+ glowing={interruptCards.some((c) => c.instanceId === card.instanceId)}
charges={view.wandCharges[card.instanceId] ?? null}
onclick={() => selectCard(card)}
/>
diff --git a/packages/web/src/Card.svelte b/packages/web/src/Card.svelte
index 2f24db1..6fe0ee9 100644
--- a/packages/web/src/Card.svelte
+++ b/packages/web/src/Card.svelte
@@ -8,6 +8,7 @@
attached = false,
marked = false,
displayed = false,
+ glowing = false,
charges = null,
onclick,
}: {
@@ -16,6 +17,8 @@
attached?: boolean;
marked?: boolean;
displayed?: boolean;
+ /** Playable RIGHT NOW out of turn — a golden pulse invites the tap. */
+ glowing?: boolean;
charges?: number | null;
onclick?: () => void;
} = $props();
@@ -40,6 +43,7 @@
class="card"
class:selected
class:attached
+ class:glowing
class:marked
class:number={isNumber}
{onclick}
@@ -192,7 +196,16 @@
white-space: nowrap;
}
+ .card.glowing {
+ box-shadow: 0 0 0 2px #c9a72a, 0 0 14px 3px rgba(201, 167, 42, 0.55);
+ animation: card-glow 1.4s ease-in-out infinite;
+ }
+ @keyframes card-glow {
+ 0%, 100% { box-shadow: 0 0 0 2px #c9a72a, 0 0 14px 3px rgba(201, 167, 42, 0.55); }
+ 50% { box-shadow: 0 0 0 2px #c9a72a, 0 0 6px 1px rgba(201, 167, 42, 0.3); }
+ }
@media (prefers-reduced-motion: reduce) {
+ .card.glowing { animation: none; }
.card { transition: none; }
.card:hover, .card.selected, .card.attached { transform: none; }
}