A table for a dozen wizards, begun by its host

Rooms take up to twelve seats and the host begins the duel with whoever
is seated; a full table begins by itself. The lobby is a roster with a
bot seat and the host's begin button. The ledger pins the turn and your
own columns and scrolls sideways to the rest; the phone bar shows every
wizard's health; a wizard who is out watches the rest; each charmed
wizard gets their own gesture; an unnamed spell goes at whoever hurt you
last, else the weakest opponent; a stab has a target from the start.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
Eric Wagoner
2026-09-23 09:34:21 -04:00
co-authored by Claude Fable 5.1
parent 79947e31ba
commit 58d7db106e
15 changed files with 299 additions and 61 deletions
+59 -5
View File
@@ -113,7 +113,7 @@
<section class="lobby">
<p class="eyebrow">room {roomId}</p>
<h2>Take a seat</h2>
<p>Another wizard has opened this duel and is waiting for an opponent.</p>
<p>A wizard has opened this table and is gathering opponents.</p>
<form class="rename" onsubmit={takeSeat}>
<label class="field">
<span>Your name</span>
@@ -123,13 +123,34 @@
</form>
</section>
{:else if room && lobby}
{@const r = room.remote!}
{@const hostName = r.seats.find((s) => s.id === r.host)?.name ?? 'the host'}
<section class="lobby">
<p class="eyebrow">room {roomId}</p>
<h2>Waiting for {room.remote!.size - room.remote!.seats.length === 1 ? 'one more wizard' : `${room.remote!.size - room.remote!.seats.length} more wizards`}</h2>
<p>Send this link, or the code. Whoever opens it takes the empty seat, and the duel begins.</p>
<h2>The table is laid</h2>
<p>Send this link, or the code. Whoever opens it takes a seat; the table holds a dozen.</p>
<p class="link-row"><code>{link}</code><button type="button" class="quiet" onclick={copyLink}>{copied ? 'Copied' : 'Copy link'}</button></p>
<p class="muted">so far: {room.remote!.seats.map((s) => s.name).join(', ')}</p>
<p><button type="button" class="quiet" onclick={() => room?.addBot()}>Seat the bot instead</button></p>
<ul class="roster">
{#each r.seats as s (s.id)}
<li>
<span class="seat-name">{s.name}</span>
<span class="muted">{s.id === r.host ? 'opened the table' : s.bot ? 'the bot' : 'seated'}{s.id === r.seat ? ', you' : ''}</span>
</li>
{/each}
<li class="empty muted">{r.size - r.seats.length} of {r.size} seats empty</li>
</ul>
<div class="ways">
{#if r.seats.length < r.size}
<button type="button" class="quiet" onclick={() => room?.addBot()}>Seat a bot</button>
{/if}
{#if room.isHost}
<button type="button" class="reveal small" disabled={r.seats.length < 2} onclick={() => room?.begin()}>Begin the duel</button>
<span class="muted">{r.seats.length < 2 ? 'once a second wizard is seated' : `with ${r.seats.length} wizards`}</span>
{:else}
<span class="muted">{hostName} will begin the duel when the table is ready.</span>
{/if}
</div>
{#if r.error}<p class="notice">{r.error}</p>{/if}
</section>
{:else if room}
<Board duel={room} />
@@ -245,6 +266,39 @@
font-size: 1rem;
}
.roster {
list-style: none;
margin: 0.9rem 0 0.6rem;
padding: 0;
max-width: 28em;
}
.roster li {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.3rem 0;
border-bottom: 1px solid var(--rule);
}
.roster li.empty {
border-bottom: 0;
justify-content: flex-start;
font-style: italic;
}
.seat-name {
font-weight: 500;
}
.ways {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 0.9rem;
margin-top: 0.4rem;
}
.link-row {
display: flex;
flex-wrap: wrap;