Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
127 lines
3.4 KiB
Svelte
127 lines
3.4 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import Board from '$lib/components/Board.svelte';
|
|
import { COMPACT_QUERY, duel, NAME_MAX } from '$lib/game/duel.svelte';
|
|
|
|
let confirmingNew = $state(false);
|
|
let renaming = $state(false);
|
|
let nameDraft = $state('');
|
|
|
|
function startRename() {
|
|
nameDraft = duel.you.name;
|
|
renaming = true;
|
|
}
|
|
|
|
function saveName(e: SubmitEvent) {
|
|
e.preventDefault();
|
|
duel.setName(nameDraft);
|
|
renaming = false;
|
|
}
|
|
|
|
function startNew() {
|
|
if (duel.inProgress && !confirmingNew) {
|
|
confirmingNew = true;
|
|
return;
|
|
}
|
|
confirmingNew = false;
|
|
duel.newGame();
|
|
}
|
|
|
|
$effect(() => {
|
|
duel.persist();
|
|
});
|
|
// The store's idea of a narrow screen must agree with the stylesheets' breakpoint.
|
|
$effect(() => {
|
|
const query = window.matchMedia(COMPACT_QUERY);
|
|
const apply = () => (duel.compact = query.matches);
|
|
apply();
|
|
query.addEventListener('change', apply);
|
|
return () => query.removeEventListener('change', apply);
|
|
});
|
|
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Waving Hands: against the bot</title>
|
|
</svelte:head>
|
|
|
|
<header class="masthead">
|
|
<div>
|
|
<h1>Waving Hands</h1>
|
|
{#if renaming}
|
|
<form class="rename" onsubmit={saveName}>
|
|
<label class="field">
|
|
<span>Your name</span>
|
|
<!-- svelte-ignore a11y_autofocus -->
|
|
<input type="text" bind:value={nameDraft} maxlength={NAME_MAX} autofocus autocomplete="nickname" placeholder="Leave empty for a drawn name" />
|
|
</label>
|
|
<button type="submit" class="reveal small">Save</button>
|
|
<button type="button" class="quiet" onclick={() => (renaming = false)}>Cancel</button>
|
|
</form>
|
|
{:else}
|
|
<p class="standfirst">
|
|
You are <em>{duel.you.name}</em><button type="button" class="link" onclick={startRename}>change name</button>, duelling <em>{duel.foe.name}</em>.
|
|
{#if duel.state.turn === 0}Each turn, each wizard chooses one gesture per hand, and the gestures are shown together. The right run of gestures on one hand is a spell.{/if}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
<nav class="actions">
|
|
<a class="quiet" href="/guide">How to play</a>
|
|
<a class="quiet" href="/rules">Rules</a>
|
|
<button type="button" class="quiet" onclick={startNew}>New duel</button>
|
|
<a class="quiet" href="/">The hall</a>
|
|
</nav>
|
|
</header>
|
|
|
|
{#if confirmingNew}
|
|
<div class="confirm" role="alertdialog" aria-labelledby="confirm-heading">
|
|
<p id="confirm-heading">Abandon the duel against {duel.foe.name}? Turn {duel.turnNumber} will be lost.</p>
|
|
<div class="choices">
|
|
<button type="button" class="reveal small" onclick={startNew}>Start a new duel</button>
|
|
<button type="button" class="quiet" onclick={() => (confirmingNew = false)}>Keep playing</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<Board {duel} />
|
|
|
|
<footer class="colophon">
|
|
<p>After Richard Bartle's <em>Waving Hands</em> (1977), also known as Spellbinder and Spellcaster. <a href="/rules">Read the full rules</a>. A rule read wrong or a bug found: <a href="mailto:eric@ericwagoner.com">send word</a>.</p>
|
|
</footer>
|
|
|
|
<style>
|
|
.standfirst .link {
|
|
margin-left: 0.5em;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.rename {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 0.5rem 0.75rem;
|
|
margin-top: 0.4rem;
|
|
}
|
|
|
|
.rename input {
|
|
width: 14em;
|
|
}
|
|
|
|
.confirm {
|
|
max-width: 1280px;
|
|
margin: 0 auto 0.75rem;
|
|
padding: 0.75rem var(--gutter);
|
|
}
|
|
|
|
.confirm p {
|
|
color: var(--blood);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.choices {
|
|
display: flex;
|
|
gap: 0.6rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
</style>
|