{#snippet sayBox(extra: string, withDie: boolean)}
{ e.preventDefault(); const t = chatDraft.trim(); if (!t) return; if (net.sendChat(t)) chatDraft = ""; else net.flash("Not sent — the table is out of reach; your words wait in the box"); }}> {#if net.chatPending}sending…{/if} {#if withDie} {/if}
{/snippet} {#snippet gallerySayBox(extra: string)} {/snippet} {#if fpvWorkshop || tokenWorkshop || fxWorkshop}
Wiz-War {fpvWorkshop ? "the eyes workshop" : tokenWorkshop ? "the token workshop" : "the flourish workshop"} ← back to the table the clips tokens flourishes eyes
{#if fpvWorkshop} {:else if tokenWorkshop} {:else} {/if} {:else}
Wiz-War sixth edition {__BUILD_STAMP__} {#if local.active} hotseat {:else if net.roomId} {#if net.you === net.hostId && net.roomId && (net.audience > 0 || net.galleryTalk)} {:else if net.audience > 0} 👁 {net.audience}{net.galleryTalk ? " 💬" : ""} {/if} {#if !net.spectating} {/if} {/if} {net.status === "connected" ? "" : "reconnecting…"}
{#if offline} {/if} {#if showDiscards && view}
(showDiscards = false)} onkeydown={() => {}}>
{/if} {#if peekCard || discardPeek || peekTreasure || peekEdge || peekEffect}
{ peekCard = null; peekCreatureId = null; peekToken = null; discardPeek = null; peekTreasure = null; peekEdge = null; peekEffect = null; }} onkeydown={() => {}}>
{#if peekEffect} {#if peekCard}
(faqCardId = id)} />
{/if}
{spellName(peekEffect.cardId)} — {effectNote(peekEffect)}
{:else if peekEdge}
{peekEdge}
{:else if peekTreasure}
{peekTreasure.note}
{:else} {#if peekCard && peekToken} {/if}
(faqCardId = id)} />
{#if peekNote}
{peekNote}
{/if} {/if}
{/if} {#if illusionPrompt}
(illusionPrompt = null)} onkeydown={() => {}}>
{/if} {#if view?.wardPending} {#if view.wardPending.ownerId === view.you}
{view.wardPending.takerId} grabs your treasure!
"When a player picks up one of your treasures, you may play at that time (out of turn) this card on him." Three points, and the Ward is spent.
{/if} {/if} {#if megaPrompt && view} {@const beast = view.creatures.find((c) => c.id === megaPrompt!.creatureId)}
Mega-Monster: the {beast ? cardDef(beast.kind).name : "monster"} grows
"Doubles the existing life-points OR movement rate of any one monster." Which? {#if beast} Now {Number.isFinite(beast.maxDamage) ? `${beast.maxDamage - beast.damage} of ${beast.maxDamage} hits` : "unkillable"} and {beast.movesPerTurn} move{beast.movesPerTurn === 1 ? "" : "s"} a turn. {/if}
{/if} {#if view?.pushPending && view.pushPending.pusheeId === view.you}
{view.pushPending.giantId} bears down on you!
The giant fills the corridor and shoves you ahead of him. The way forks here — which way do you go?
{#each view.pushPending.exits as d (d)} {/each}
{/if} {#if view?.slowDeathPending && view.slowDeathPending.playerId === view.you} {@const sdPoints = view.slowDeathPending.points} {@const sdAbsorb = view.yourHand.find((c) => c.cardId === "absorb")} {@const sdBlunt = view.yourHand.find((c) => c.cardId === "blunt")}
Slow Death bites for {sdPoints}!
The draw's bites land as one blow — counter the total: Absorb soaks up to three points, Blunt halves it (fractions round up).
{#if sdAbsorb} {/if} {#if sdBlunt} {/if}
{/if} {#if feedbackOpen && net.roomId}
(feedbackOpen = false)} onkeydown={() => {}}>
{/if} {#if prefsOpen}
(prefsOpen = false)} onkeydown={() => {}}>
{/if} {#if faqCardId} (faqCardId = null)} /> {/if} {#if showHelp} net.requestStats()} onnavigate={helpNavigate} onclose={helpClose} /> {/if} {#if view?.phase === "finished" && view.winner && victoryCurtain && !victorySeen}
🏆
{view.winner}
{#if view.winReason === "treasures"} carried two stolen treasures home — the maze is theirs {:else if view.winReason === "lastStanding"} the last wizard standing {:else} wins the game {/if}
{/if} {#if trapNotice}
A cruel draw!
{trapNotice === "gift-from-below" ? "It bites for 3 points, then the deck deals you another." : "The trap snaps shut — you lose a turn."}
{/if} {#if caution}
Hold on —
{#each caution.reasons as r (r)}
{r}
{/each}
{/if} {#if cardReveal}
{cardReveal.title}
{#each cardReveal.cards as card (card.instanceId)} (faqCardId = id)} /> {/each} {#if cardReveal.cards.length === 0}
Nothing — their thoughts are empty-handed.
{/if}
{cardReveal.cards.length > 0 ? `${cardReveal.cards.length} card${cardReveal.cards.length === 1 ? "" : "s"} — ${cardReveal.note}` : ""}
{/if} {#if wardBite}
The treasure bites back!
{wardBite.pending ? `${wardBite.owner}'s treasure was warded — it bites for 3 unless you answer it. Your counteractions come next.` : wardBite.amount > 0 ? `${wardBite.owner}'s treasure was warded — it bites you for ${wardBite.amount} point${wardBite.amount === 1 ? "" : "s"}.` : `${wardBite.owner}'s treasure was warded — but its bite finds no purchase.`}
{/if} {#if view && view.outOfTurnWindow?.playerId === view.you && !momentSeen}
The maze holds its breath
Time parts for your {cardDef(view.outOfTurnWindow.kind).name} — pick an attack card and a target, or let the moment pass.
{/if} {#if openingRolls}
The dice decide who casts first
{#each openingRolls.players as p (p)}
{p === openingRolls.first ? "🏆 " : ""}{p} {#each openingRolls.rolls[p] ?? [] as r, i (i)}🎲{r}{/each}
{/each}
{#if Object.values(openingRolls.rolls).some((r) => r.length > 1)}
Tied wizards rolled again.
{/if}
{/if} {#if view?.stack && attackNoticeKey && attackNoticeSeen !== attackNoticeKey && !noFanfare}
{#if view.stack.defenderId === view.you && attackingCreature} {view.stack.attackerId}'s {cardDef(attackingCreature.kind).name} attacks you! {:else if view.stack.defenderId === view.you && view.stack.reflectedBase} Your own spell comes screaming back! {:else 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 && attackingCreature}
{:else if view.stack.defenderId === view.you && view.stack.attackCard} {@const illusionOf = view.stack.attackCard.cardId === "illusionary-attack" ? (view.stack.params?.cardId ?? null) : null} {#if illusionOf}
(faqCardId = id)} /> (faqCardId = id)} />
an ILLUSIONARY {cardDef(illusionOf).name} — it bites only if believed
{:else}
{/if} {#if view.stack.reflectedBase}
REFLECTED — {view.stack.reflectedBase.damage} damage inbound unless you answer it
{:else if view.stack.numberValue != null || view.stack.amplifyFactor > 1}
{view.stack.numberValue != null ? `powered by a ${view.stack.numberValue}` : ""} {view.stack.amplifyFactor > 1 ? ` — AMPLIFIED ×${view.stack.amplifyFactor}` : ""}
{/if} {:else if view.stack.defenderId === view.you && view.stack.sourceName} {#if BLOW_CARD[view.stack.sourceName]}
(faqCardId = id)} />
{/if}
{view.stack.sourceName.toUpperCase()} — {view.stack.params?.damage ?? 0} damage inbound unless you answer it
{:else if view.stack.defenderId === view.you}
a bare-knuckled punch
{:else if view.stack.counters.length > 0}
{/if} {#if view.stack.defenderId === view.you && view.stack.counters.length > 0} {@const standing = view.stack.counters.filter((c) => c.player === view.you && !c.nullified)} {@const broken = view.stack.counters.filter((c) => c.player === view.you && c.nullified)} {#if broken.length > 0 && standing.length === 0}
Your {broken.map((c) => cardDef(c.card.cardId).name).join(" and ")} {broken.length === 1 ? "was" : "were"} nullified — the attack comes on unchecked.
{:else if standing.length > 0}
Your {standing.map((c) => cardDef(c.card.cardId).name).join(" and ")} stand{standing.length === 1 ? "s" : ""} — only what slips past {standing.length === 1 ? "it" : "them"} will land. Counter again, or let it resolve.
{/if} {/if}
{/if} {#if net.moment && net.moment.steps.length > 0} net.requestShare()} onclose={() => net.closeMoment()} /> {:else if net.catchUp && net.catchUp.length > 0} net.requestShare(-1) : null} onclose={() => net.closeCatchUp()} /> {:else if local.replaySteps && local.replaySteps.length > 0} (local.replaySteps = null)} /> {/if} {#if net.error} {/if} {#if net.transferCode}
Speak this phrase into your other device (good for 10 minutes, one use): {net.transferCode.code}
{/if} {#if local.setup}
wizard {local.setup.names.length + 1} of {local.setup.count}
What is your name?
Your color
{#each [0, 1, 2, 3, 4, 5] as c (c)} {@const taken = local.setup.colors.includes(c)} {/each}
{ e.preventDefault(); const err = local.submitName(setupName, setupColor); if (err) net.error = err; else setupName = ""; }}>
{#if local.setup.names.length > 0}
so far: {local.setup.names.join(", ")}
{/if}
{:else if local.handoffTo && !openingRolls}
local.takeSeat()} onkeydown={(e) => e.key === "Enter" && local.takeSeat()}>
pass the device to
{local.handoffTo}
tap when only they can see the screen
{/if} {#if !net.roomId && !local.active}
Wiz-War
Steal treasure. Blast through walls. Outwit your friends.
A turn-based wizard board game for two to six, free in your browser, with friends or clockwork opponents. Carry home two treasures that aren't yours, or be the last wizard standing.
or join one
Through a wizard's eyes: an ambush watches its corridor through a wormhole watch clips from the labyrinth {#if family.length}

Also at the Kestrel's Nest: {#each family as g, i (g.slug)}{i > 0 ? ", " : " "}{g.name}{/each}

{/if}
More ways to play — pass one device around, or claim a transferred seat
or play here, passing the device:
{#each [2, 3, 4, 5, 6] as n (n)} {/each}
{#if local.hasSave()} {/if}
{#if net.seats.length > 0}
your games {#if !net.notificationsEnabled} {/if} {#if noFanfare} {/if}
{#each net.seats as seat (seat.roomId + seat.name)} {@const g = net.games.find((x) => x.roomId === seat.roomId && x.name === seat.name)}
{#if g?.rematch && g.rematch.by !== seat.name} {/if}
{/each} {#if net.feedbackReports.length > 0}
your reports to the wizards
{#each net.feedbackReports as r (r.id)} {@const thread = r.thread ?? []}
{r.roomId}
“{r.happened}”{#if r.image} 📷{/if}
{#each thread as line, j (j)} {#if line.from === "player"}
you — {line.text}
{:else}
{line.status} — {line.text}
{/if} {/each} {#if !thread.length}
the wizards are studying the moment…
{:else if thread[thread.length - 1]!.from === "player"}
your word is with the wizards…
{/if} {#if thread.some((l) => l.from === "desk")}
{ e.preventDefault(); const t = (reportAnswers[r.id] ?? "").trim(); if (t && net.answerReport(r, t)) reportAnswers = { ...reportAnswers, [r.id]: "" }; }}>
{/if}
{/each} {/if}
{/if}
{:else if !local.active && !net.started}
Gather your wizards
Room {net.roomId} · {net.players.length} of 6 seats filled
{#if net.spectating} {#if net.players.length < 6}
Join {net.hostId ?? "the"}'s game
Choose your wizard's name and take a seat; the boards flip when the host starts.

👁 Just watching? You're in the Peanut Gallery until you sit.

{:else}

👁 The table is full — you watch from the Peanut Gallery. Waiting for the boards to flip…

{/if} {/if}
    {#each net.players as p (p)} {@const chosen = net.roomColors[p]} {@const isYou = p === net.you}
  • {#if chosen !== undefined} {:else} {/if} {p}{isYou ? " (you)" : ""} {#if net.roomBots[p]}⚙ {net.roomBots[p]}{:else if chosen === undefined}{p === net.hostId ? "host · " : ""}choosing a wizard…{:else if p === net.hostId}host{:else}wizard{/if} {#if isYou && !net.spectating} {/if} {#if net.you === net.hostId && p !== net.hostId} {/if}
  • {#if isYou && !net.spectating && colorPickerOpen}
  • {#each [0, 1, 2, 3, 4, 5] as c (c)} {@const takenBy = Object.entries(net.roomColors).find(([, v]) => v === c)?.[0]} {/each}
  • {/if} {/each} {#each net.expected.filter((n) => !net.players.includes(n)) as n (n)}
  • {n} {net.challenge && n === net.keeper ? `called to the table by ${net.challenge.by} — awaiting an answer` : "from the last table — not yet seated"}
  • {/each} {#if net.players.length < 6}
  • An empty seat {net.players.length < 2 ? "one more wizard and the boards can flip" : "room for another"}
  • {/if}
{#if !net.spectating}
{#if net.you === net.hostId && net.players.length < 6} {/if}
the link itself, for pasting by hand {inviteLink}
{#if net.keeper && !net.challenge && !net.players.includes(net.keeper) && net.players.length + net.expected.length < 6}
{net.keeper} keeps this table and answers every raven — sometimes within the minute, sometimes after a night's sleep. {keeperHour ? `It is ${keeperHour} where ${net.keeper} lives.` : ""} The seat is held either way.
{:else if net.challenge}
The raven has flown to {net.keeper}, called by {net.challenge.by}. {keeperHour ? `It is ${keeperHour} there.` : ""} A seat is held for them: if they can come now, they will take it; if they are at supper, or asleep, or off in the world, they will leave a word below when they can. Meanwhile the table is yours — seat a clockwork wizard, invite a friend, or open another game in a new tab and play on; this room keeps its place on your ledger until you come back, as long as nobody abandons it.
{/if} {@const lobbyTalk = net.log.filter((l) => l.text.startsWith("\u{1F4AC}")).slice(-6)}
{#each lobbyTalk as line, i (i)}
{line.text}
{/each} {@render sayBox("lobby-say", false)}
{#if net.you === net.hostId && addingBot && net.players.length < 6}
Difficulty {#each ["apprentice", "adept", "archmage"] as t (t)} {/each}
Personality {#each ["hunter", "berserker", "worrier", "mystery"] as st (st)} {/each}
{BOT_BLURBS[botStyle]}
{/if} {:else} {@const galleryTalkLines = net.log.filter((l) => l.text.startsWith("\u{1F4AC}")).slice(-6)}
{#each galleryTalkLines as line, i (i)}
{line.text}
{/each} {#if net.galleryTalk} {@render gallerySayBox("lobby-say")} {:else}

👁 The gallery listens; the host has not opened its mouth.

{/if}
{/if} {#if net.you === net.hostId}
{#if net.players.length < 2}
Waiting for one more wizard
{/if}
{:else}

Waiting for {net.hostId} to flip the boards…

{/if}
{:else if view}
{#snippet tableGuidance()} {#if youMustRespond || selectedDef || selectedCreature || youMustDiscard || discardMode || discardSelection.size > 0}
{#if selectedCreature && !selectedDef} {@const sc = view.creatures.find((c) => c.id === selectedCreature)} {#if sc} {#if sc.kind === "alter-ego"} Your double stands ready — select a card and it casts from the double's square, by the double's sight. {:else} Commanding {cardDef(sc.kind).name} — {creatureStats(sc)}. Tap a square beside it to march, a target in its square to attack{sc.kind === "troll" ? ", or a wall line beside it to punch" : ""}. {/if} {#if dmWarnCell} ⚠ it will claw YOU the moment it enters your square — tap again if you mean it {/if} {/if} {/if} {#if youMustRespond && view.stack} {#if view.stack.defenderId === view.you && selectedCard?.cardId === "teleport"} {pendingTeleport ? "Escaping by teleport — tap the marked square again to jump." : "Escaping by teleport — tap a square within four spaces to mark it."} {:else if view.stack.defenderId === view.you} {view.stack.attackerId} attacks with {attackingCreature ? `the ${cardDef(attackingCreature.kind).name}` : view.stack.attackCard?.cardId === "illusionary-attack" && view.stack.params?.cardId ? `an illusionary ${cardDef(view.stack.params.cardId).name}` : view.stack.attackCard ? cardDef(view.stack.attackCard.cardId).name : view.stack.tearTreasure ? "a grab at your treasure" : view.stack.sourceName ? `a ${view.stack.sourceName}` : "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} Mark the cards to throw away, then confirm. {/if} {#if discardSelection.size > 0} {/if} {#if discardMode || (discardSelection.size > 0 && !youMustDiscard)} {/if} {#if selectedDef} {selectedDef.name} {#if pendingCellFor} — now click where {pendingCellFor} goes{selectedCard?.cardId === "mental-force" ? " (within three walked spaces of them — no sight needed)" : ""} {/if} {#if selectedCard && EDGE_CARDS.has(selectedCard.cardId)} — {EDGE_HINTS[selectedCard.cardId] ?? "click a wall line"} {/if} {#if selectedCard && WAND_IDS.has(selectedCard.cardId) && view.wandCharges[selectedCard.instanceId] == null && !attachedNumber} — first use sets the charges: tap a NUMBER card first, or cast bare for a single charge {:else if selectedDef.cardType === "attack" && !cellSelectMode && !EDGE_CARDS.has(selectedCard?.cardId ?? "")} — click a target, or a wall line to batter it{attachedNumber ? ` (powered by a ${numberTotal})` : ""} {/if} {#if attachedMods.length > 0} [+ {attachedMods.map((m) => cardDef(m.cardId).name).join(", ")}] {/if} {#if attachedMods.some((m) => m.cardId === "power-attack")} {/if} {#if selectedCard?.cardId === "boobytrap"} — place 4 tokens ({trapCells.length}/4; the first is real) {/if} {#if selectedCard?.cardId === "redirection"} {tradeFrom ? "— now the exit it should connect TO" : "— click the first exit"} {:else if selectedCard && TWO_CELL_CARDS.has(selectedCard.cardId)} {tradeFrom ? "— now the second square" : "— click the first square"} {/if} {#if selectedCard?.cardId === "teleport" && !youMustRespond} {pendingTeleport ? "— tap the marked square again to jump, or pick another" : "— tap a destination (up to four squares, walls ignored)"} {#if pendingTeleport} {/if} {/if} {#if selectedCard?.cardId === "mega-monster"} — tap the monster, then choose whether to double its life-points or its movement {/if} {#if selectedCard?.cardId === "pick-lock" || selectedCard?.cardId === "master-key"} {/if} {#if selectedCard?.cardId === "master-key" && isYourTurn && !me?.displayed.some((c) => c.instanceId === selectedCard!.instanceId)} {/if} {#if selectedCard?.cardId === "rotate-sector"} — click the sector {/if} {#if selectedCard?.cardId === "relocate-sector"} {pendingSectorFrom ? (relocateGhosts?.length ? "— now tap a dashed landing beside the maze" : "— that sector has nowhere legal to go; pick another") : "— click the sector to move"} {/if} {#if selectedCard && NAMED_CARDS.has(selectedCard.cardId)} {#each nameSuggestions as n (n)}{/each} {#if selectedCard.cardId === "deja-vu"} {:else} — then click the target {/if} {/if} {#if selectedCard?.cardId === "swap-meet"} {#if !swapMeetTarget} — click the other trader {:else if myTradables.length === 0} — you carry no items to trade {:else if !swapMine} — trade away your: {#each myTradables as c (c.key)} {/each} {:else if theirTradables.length === 0} — they show no item you could claim {:else} — and take their: {#each theirTradables as c (c.key)} {/each} {/if} {/if} {#if selectedCard?.cardId === "waterbolt"} (knockback {numberTotal - wbDamage}) {/if} {#if selectedCard?.cardId === "power-run"} {/if} {#if selectedCard && confirmCastable(selectedCard.cardId)} {/if} {#if selectedCard && isMovableObject(selectedCard.cardId) && isYourTurn} {/if} {#if selectedCard && isYourTurn && !youMustRespond} {/if} {#if selectedCard?.cardId === "add"} — ADD rides a second NUMBER: play one number for movement, then play another, and the Add spends itself {/if} {#if selectedCard && isNumberCard(selectedCard.cardId)} {@const needsAdd = view.turn.numberPlayedForMovement} {@const haveAdd = view.yourHand.some((c) => c.cardId === "add")} {#if needsAdd && !haveAdd} — a second movement number needs an ADD card {:else} {/if} {/if} {/if}
{/if} {/snippet}
{#if view.you && !net.spectating} {/if}
{Math.round(boardZoom * 100)}%
{#if stepCaption && me}
This is you — tap a lit square beside your wizard to walk
{/if}
{#if prefs.liveFp && view.you && !net.spectating} setPref("liveFp", false)} onstride={(side) => tryMove(side)} canStride={yourMoment} ontarget={fpvTarget} onfacing={(s) => (fpvFacing = s)} {litCells} aimBeings={selectedCard != null && !CELL_CARDS.has(selectedCard.cardId) && !EDGE_CARDS.has(selectedCard.cardId)} onpickup={(w) => dispatch(w.kind === "treasure" ? { type: "pickUpTreasure", treasureId: w.id } : { type: "pickUpObject", instanceId: w.id })} onCreatureMove={(creatureId, direction) => dispatch({ type: "moveCreature", creatureId, direction })} onCreatureAttack={(creatureId, targetId) => dispatch({ type: "creatureAttack", creatureId, targetId })} onbody={(id) => (fpvBody = id)} onWarpStep={(creatureId) => dispatch(creatureId ? { type: "creatureWarpStep", creatureId } : { type: "warpStep" })} edgeSelect={edgeSelectMode && yourMoment} /> {/if}
0 ? trapCells : null} effects={boardFx} markedSector={pendingSectorOrigin} ghostSlots={relocateGhosts} onGhostClick={clickGhostSlot} onCellPeek={(c) => peekAt(c)} onIllusionClick={(cell, side) => (illusionPrompt = { cell, side })} onEdgePeek={(tip) => (peekEdge = tip)} {litCells} spotCells={stepCells} {sightTrace} povFacing={prefs.liveFp && view.you && !net.spectating ? fpvFacing : null} povCreatureId={prefs.liveFp && !net.spectating ? fpvBody : null} />
{#if ambushVia}
{cardDef(ambushVia.cardId).name} — set an ambush {#if !ambushTrigger} springs when an opponent… {:else if !ambushSpell} — now tap the attack card to commit {:else if ambushNeedsCell && !ambushCell} {cardDef(ambushSpell.cardId).name} — click where the trap will send them {:else} {cardDef(ambushSpell.cardId).name}{ambushCell ? ` → (${ambushCell.x}, ${ambushCell.y})` : ""}{attachedNumber ? ` with a ${numberTotal}` : " (tap a number to power it)"} {/if}
{/if} {#if !castPanelOn} {@render tableGuidance()} {/if}
{#if dockLead && isYourTurn}
{dockLead} {#if selectedCard?.cardId === "teleport" && pendingTeleport} {/if} {#if selectedCard && isNumberCard(selectedCard.cardId)} {@const needsAdd = view.turn.numberPlayedForMovement} {@const haveAdd = view.yourHand.some((c) => c.cardId === "add")} {#if !(needsAdd && !haveAdd)} {/if} {/if}
{/if} {#if isYourTurn}
{#if onWarpToken} {/if} {#if pickChoice} — whose? {#each treasuresHere as t (t.id)} {/each} {/if} {#each objectsHere as obj (obj.instanceId)} {/each} {#each tearTargets as t (t.id)} {/each}
{#if net.pending} sending {net.pending.label}… {:else if net.confirmed} ✓ {net.confirmed} confirmed {/if}
{/if}
{#if selectedCard && !inspectorHidden && !castPanelOn}
{/if} {#if view.phase === "finished" && view.revealedHands}
The hands, face-up
{#each revealFolded ? [] : view.players as p (p.id)}
{p.id === view.winner ? "🏆 " : ""}{p.id} {#if !p.alive}as they fell{/if}
{#each view.revealedHands[p.id] ?? [] as card (card.instanceId)} (peekCard = card)} onfaq={(id) => (faqCardId = id)} /> {/each} {#if (view.revealedHands[p.id] ?? []).length === 0} empty-handed {/if}
{/each}
{/if}
{#if net.spectating} 👁 You watch from the Peanut Gallery — hands stay secret, even from you. {/if} {#each view.phase === "finished" ? [] : orderedHand as card (card.instanceId)}
{ draggingId = card.instanceId; e.dataTransfer?.setData("text/plain", card.instanceId); }} ondragover={(e) => e.preventDefault()} ondrop={(e) => { e.preventDefault(); dropCardOn(card.instanceId); }} ondragend={() => (draggingId = null)}> 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)} onfaq={(id) => (faqCardId = id)} />
{/each}
{/if}
{/if}