diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index ddd82ea..d2af0f4 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -15,6 +15,8 @@ let joinCode = $state(""); let claimPhrase = $state(""); let showHelp = $state(false); + /** Which pending attack the player has already acknowledged (modal dismissed). */ + let attackNoticeSeen = $state(null); let helpTab = $state<"play" | "rules" | "cards" | "about">("play"); let hotseatCount = $state(2); let setupName = $state(""); @@ -70,6 +72,11 @@ const view = $derived(local.active ? local.view : net.view); const isYourTurn = $derived(view !== null && view.activePlayerId === view.you && !view.stack); const youMustRespond = $derived(view?.stack != null && view.stack.waitingOn === view.you); + const attackNoticeKey = $derived( + youMustRespond && view?.stack + ? `${view.stack.attackerId}:${view.stack.attackCard?.instanceId ?? "punch"}:${view.stack.counters.length}` + : null, + ); const youMustDiscard = $derived(view != null && view.pendingDiscard === view.you); const selectedDef = $derived(selectedCard ? cardDef(selectedCard.cardId) : null); @@ -557,6 +564,31 @@ (showHelp = false)} /> {/if} + {#if view?.stack && attackNoticeKey && attackNoticeSeen !== attackNoticeKey} +
+
+
+ {#if view.stack.defenderId === view.you} + {view.stack.attackerId} attacks you! + {:else} + {view.stack.counters[view.stack.counters.length - 1]?.player} counters your spell! + {/if} +
+ {#if view.stack.defenderId === view.you && view.stack.attackCard} +
+ {#if view.stack.numberValue != null} +
powered by a {view.stack.numberValue}
+ {/if} + {:else if view.stack.defenderId === view.you} +
👊 a bare-knuckled punch
+ {:else if view.stack.counters.length > 0} +
+ {/if} + +
+
+ {/if} + {#if net.catchUp && net.catchUp.length > 0} net.closeCatchUp()} /> {/if} @@ -790,13 +822,10 @@ {:else if youMustRespond}
{#if view.stack?.defenderId === view.you} - {view.stack.attackerId} attacks you - {#if view.stack.attackCard}with {cardDef(view.stack.attackCard.cardId).name}{:else}with a punch{/if}! - Play a counteraction, or + Under attack — respond by your hand. {:else} - Respond to the counteraction, or + Your spell is countered — respond by your hand. {/if} -
{:else if view.stack}
Waiting on {view.stack.waitingOn}…
@@ -864,8 +893,20 @@ {/if} - {#if selectedDef || youMustDiscard || discardMode || discardSelection.size > 0} + {#if youMustRespond || selectedDef || youMustDiscard || discardMode || discardSelection.size > 0}
+ {#if youMustRespond && view.stack} + + {#if view.stack.defenderId === view.you} + {view.stack.attackerId} attacks with + {view.stack.attackCard ? cardDef(view.stack.attackCard.cardId).name : "a punch"} — + tap a counteraction card, or + {:else} + {view.stack.waitingOn === view.you ? "They counter your spell — counter back, or" : ""} + {/if} + + + {/if} {#if youMustDiscard} Hand over the limit — mark cards and discard. {:else if discardMode && discardSelection.size === 0} @@ -1470,6 +1511,40 @@ .actions { display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap; min-height: 2rem; } .draw-pick { color: #a49c86; font-size: 0.9rem; } + .attack-scrim { + position: fixed; + inset: 0; + background: rgba(10, 12, 16, 0.72); + display: grid; + place-items: center; + z-index: 45; + padding: 1rem; + } + .attack-notice { + background: #efe8d4; + border: 2px solid #43331f; + border-radius: 8px; + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6); + padding: 1.2rem 1.6rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.8rem; + max-width: min(22rem, 92vw); + text-align: center; + } + .attack-headline { + font-family: "Oswald", sans-serif; + font-size: 1.15rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #b3372b; + } + .attack-card :global(.card) { transform: scale(1.35); margin: 1.2rem 0; } + .attack-card :global(.card:hover) { transform: scale(1.35); } + .attack-power { font-family: "Courier Prime", monospace; color: #6b5a41; font-size: 0.9rem; } + .attack-fist { font-size: 1.1rem; color: #43331f; } + .inspector { position: fixed; right: 0.9rem; @@ -1503,7 +1578,41 @@ cursor: pointer; } @media (max-width: 900px) { - .inspector { right: 0.5rem; bottom: 0.5rem; } + .attack-scrim { + position: fixed; + inset: 0; + background: rgba(10, 12, 16, 0.72); + display: grid; + place-items: center; + z-index: 45; + padding: 1rem; + } + .attack-notice { + background: #efe8d4; + border: 2px solid #43331f; + border-radius: 8px; + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6); + padding: 1.2rem 1.6rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.8rem; + max-width: min(22rem, 92vw); + text-align: center; + } + .attack-headline { + font-family: "Oswald", sans-serif; + font-size: 1.15rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #b3372b; + } + .attack-card :global(.card) { transform: scale(1.35); margin: 1.2rem 0; } + .attack-card :global(.card:hover) { transform: scale(1.35); } + .attack-power { font-family: "Courier Prime", monospace; color: #6b5a41; font-size: 0.9rem; } + .attack-fist { font-size: 1.1rem; color: #43331f; } + + .inspector { right: 0.5rem; bottom: 0.5rem; } .inspector-card { transform: scale(1.3); } }