Files
waving-hands/src/lib/components/MobileBar.svelte
T
Eric WagonerandClaude Opus 5.5 ee630276b3 Easier to make a move, and plainer words around the game
From feedback on the BoardGameGeek thread. The hands are framed and
labelled as yours, so they no longer read as sitting under your
opponent's columns. A move can be typed: the first letter goes to the
left hand, the second to the right, K stabs, a dash rests, Backspace
takes one back and Enter seals. The chosen pair, what it will do, any
warning and the button now sit in one row that stays in view. A clap
with one hand is warned of before the turn is sealed. The button says
what the player does: Seal the turn. The chronicle reads oldest first
and scrolls to the newest line. Headings, tabs and notes lose the
label-and-colon construction and the abstract headings; the chronicle's
imagery stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
2026-10-10 18:29:49 -04:00

133 lines
3.1 KiB
Svelte

<script lang="ts">
import type { Duel } from '$lib/game/duel.svelte';
import { HANDS } from '$lib/game/spells';
import { MAX_HP } from '$lib/game/state';
let { duel }: { duel: Duel } = $props();
/** Past three wizards the bar names you, the wizard in focus, and the count; the roster is a tap away. */
const crowded = $derived(duel.others.length >= 3);
const shown = $derived(crowded ? [duel.focus ?? duel.foeId].filter((id) => id !== duel.me) : duel.others);
function toRoster() {
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
document.getElementById('roster')?.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' });
}
</script>
{#if !duel.state.over}
<div class="bar" class:watching={duel.spectating}>
<div class="health" class:many={duel.others.length > 1}>
{#if !duel.spectating}<span><em>{duel.you.name}</em> {duel.you.hp}/{MAX_HP}</span>{/if}
{#each shown as id (id)}
{@const w = duel.state.wizards[id]}
<span class:gone={!w.alive || w.surrendered}><em>{w.name}</em> {w.hp}/{MAX_HP}</span>
{/each}
{#if crowded}
<button type="button" class="count" onclick={toRoster}>{duel.state.seats.length} wizards</button>
{/if}
</div>
{#if duel.spectating}
<span class="gallery">the gallery</span>
{:else}
<div class="gestures" aria-label="Chosen gestures">
{#each HANDS as hand (hand)}
<span class="letter" class:empty={duel.chosenGesture('main', hand) === null}>{duel.drawn('main', hand)}</span>
{/each}
</div>
<button type="button" class="reveal" disabled={!duel.ready} onclick={() => duel.reveal()}>
{duel.out ? 'Out' : duel.moved ? 'Waiting' : duel.surrendering ? 'Surrender' : duel.timeStopped ? 'Act' : 'Seal'}
</button>
{/if}
</div>
{/if}
<style>
.bar {
display: none;
}
/* Must match COMPACT_QUERY in duel.svelte.ts. */
@media (max-width: 860px) {
.bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 5;
display: grid;
grid-template-columns: 1fr auto auto;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 16px calc(0.5rem + env(safe-area-inset-bottom));
background: var(--slate);
border-top: 1px solid var(--rule-strong);
}
}
.bar.watching {
grid-template-columns: 1fr auto;
}
.gallery {
font-size: 0.8rem;
font-style: italic;
color: var(--bone-faint);
}
.health {
display: flex;
flex-direction: column;
font-size: 0.8rem;
color: var(--bone-dim);
font-variant-numeric: tabular-nums;
line-height: 1.3;
}
.health.many {
flex-direction: row;
flex-wrap: wrap;
gap: 0 0.6rem;
font-size: 0.72rem;
}
.health em {
color: var(--bone);
}
.health .gone {
text-decoration: line-through;
opacity: 0.6;
}
.count {
background: none;
border: 0;
padding: 0;
font: inherit;
color: var(--frost);
text-decoration: underline dotted;
}
.gestures {
display: flex;
gap: 0.5rem;
}
.letter {
font-size: 1.6rem;
color: var(--ember);
min-width: 1.2em;
text-align: center;
}
.letter.empty {
color: var(--bone-faint);
}
.reveal {
padding: 0.5rem 1rem;
font-size: 1rem;
}
</style>