A crowded table: keyed threats, a compact roster, a ledger selector, host-only starts
Threats are keyed by seat as well as hand and spell, so two opponents building the same spell no longer crash the board (regression test). Past three wizards the rail lists the others on a line each with health, condition and whether they have moved; a row of names turns the ledger toward any wizard, as do pressing a warning or choosing a target; each seat keeps one colour across the ledger, the warnings and the rail. The result strip folds untouched wizards into one line. A full table no longer begins by itself: the host begins at every size and may send a bot away. Target lists carry health and condition, and an intent sentence says what the reveal will do. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
c24fccaa35
commit
5c79a65319
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Duel } from '$lib/game/duel.svelte';
|
||||
import { seatColor } from '$lib/game/colors';
|
||||
import { glyph } from '$lib/game/glyphs';
|
||||
import { SPELL_BY_ID, type Hand } from '$lib/game/spells';
|
||||
import { hiddenFromViewer, type HistoryEntry, type WizardId } from '$lib/game/state';
|
||||
@@ -71,15 +72,41 @@
|
||||
void turns;
|
||||
if (scroller) scroller.scrollTop = scroller.scrollHeight;
|
||||
});
|
||||
|
||||
const crowded = $derived(duel.others.length >= 3);
|
||||
|
||||
// The focused wizard's columns slide in just past the pinned ones.
|
||||
$effect(() => {
|
||||
const who = duel.focus;
|
||||
void duel.focusTick;
|
||||
if (!who || !scroller) return;
|
||||
const th = scroller.querySelector<HTMLElement>(`th[data-who="${who}"]`);
|
||||
const pinned = scroller.querySelector<HTMLElement>('thead th.pin-right');
|
||||
if (!th) return;
|
||||
const left = pinned ? pinned.offsetLeft + pinned.offsetWidth : 0;
|
||||
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
scroller.scrollTo({ left: th.offsetLeft - left, behavior: reduce ? 'auto' : 'smooth' });
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if crowded}
|
||||
<nav class="seats" aria-label="Wizards in the ledger">
|
||||
<span class="muted">Turn the ledger toward</span>
|
||||
{#each duel.others as who (who)}
|
||||
{@const w = duel.state.wizards[who]}
|
||||
<button type="button" class="seat" class:focused={duel.focus === who} class:gone={!w.alive || w.surrendered} onclick={() => duel.focusOn(who)}>
|
||||
<span class="dot" style="background: {seatColor(duel.state, who)}"></span>{w.name}
|
||||
</button>
|
||||
{/each}
|
||||
</nav>
|
||||
{/if}
|
||||
<div class="scroller" bind:this={scroller}>
|
||||
<table class="ledger">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="turn" scope="col"><span class="visually-hidden">Turn</span></th>
|
||||
{#each columns as who (who)}
|
||||
<th class:you={who === duel.me} class:foe={who !== duel.me} class:gone={!duel.state.wizards[who].alive || duel.state.wizards[who].surrendered} colspan="2" scope="colgroup">{duel.state.wizards[who].name}{#if who === duel.me} (you){/if}</th>
|
||||
<th data-who={who} class:you={who === duel.me} class:pin-left={who === duel.me} class:foe={who !== duel.me} class:focused={duel.focus === who} class:gone={!duel.state.wizards[who].alive || duel.state.wizards[who].surrendered} colspan="2" scope="colgroup">{#if crowded && who !== duel.me}<span class="dot" style="background: {seatColor(duel.state, who)}"></span>{/if}{duel.state.wizards[who].name}{#if who === duel.me} (you){/if}</th>
|
||||
{/each}
|
||||
</tr>
|
||||
<tr class="hands">
|
||||
@@ -144,6 +171,50 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.seats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.3rem 0.4rem;
|
||||
padding: 0.2rem 0 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.seat {
|
||||
background: none;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 999px;
|
||||
padding: 0.1rem 0.6rem 0.1rem 0.45rem;
|
||||
color: var(--bone-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.seat:hover {
|
||||
border-color: var(--bone-dim);
|
||||
color: var(--bone);
|
||||
}
|
||||
|
||||
.seat.focused {
|
||||
border-color: var(--frost);
|
||||
color: var(--bone);
|
||||
}
|
||||
|
||||
.seat.gone {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
thead th.focused {
|
||||
color: var(--frost);
|
||||
}
|
||||
|
||||
.scroller {
|
||||
max-height: clamp(170px, 30vh, 400px);
|
||||
overflow: auto;
|
||||
|
||||
Reference in New Issue
Block a user