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; } }