Six things a first-time tester found: the rules counted five kinds that cross boards and named nine; a lesson's screen-reader line kept describing a piece after the move, and the outcome was never read; the chronicle's move links were too faint and small to look live; the hall's Play button sat disabled with no word about the name it waits for, and nothing said the drake is the computer; the lessons forgot which scenes were done

Scenes played right are remembered in this browser, ticked on their tabs, and the page opens on the first one left.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
This commit is contained in:
Eric WagonerandClaude Fable 5.1 committed 2026-10-27 20:49:37 -04:00
1 parent 2de6e1f6ba
commit 7dd113b1ff
5 files changed
+48 -7

No files matched your search

+2 -2
View File
@@ -52,8 +52,8 @@
}
h3 {
font-size: 0.8rem;
color: var(--bone-faint);
font-size: 0.85rem;
color: var(--bone-dim);
margin: 0.6rem 0 0.2rem;
font-weight: 500;
}
+1
View File
@@ -216,6 +216,7 @@
<span>Your name</span>
<input type="text" bind:value={name} maxlength={NAME_MAX} placeholder="e.g. Morwenna" autocomplete="nickname" />
</label>
{#if !ready}<p class="option-note muted small">A name first, and the buttons wake. The drake is the computer, playing for the house.</p>{/if}
{#if game.options?.length}
<div class="options">
{#each game.options as o (o.key)}
+3 -1
View File
@@ -23,12 +23,14 @@
const checkSquare = $derived(inCheck(g.cells, g.toMove) ? kingSquare(g.cells, g.toMove) : -1);
const calm = $derived(reducedMotion());
const showCoords = $derived(prefs.coordinates !== false);
let liveText = $state('');
$effect(() => {
// A move or a new scene leaves nothing selected, and nothing to say about one.
void g.cells;
selected = null;
inspected = null;
liveText = '';
});
let liveText = $state('');
function tap(i: number) {
if (selected !== null && targets.includes(i)) {
+41 -3
View File
@@ -1,6 +1,7 @@
<script lang="ts">
// Learn the pieces: one scene a piece. The boards the scene needs, what to
// look at, the move made by the reader, and what came of it.
import { onMount } from 'svelte';
import { page } from '$app/state';
import LessonBoard from '$lib/components/LessonBoard.svelte';
import PiecePanel from '$lib/components/PiecePanel.svelte';
@@ -12,6 +13,27 @@
const withRoom = (path: string) => (room ? `${path}?room=${room}` : path);
let n = $state(0);
/** The scenes this browser has already played right, so a reader can come back to the next one. */
const DONE = 'wyrm:learn:done';
let done = $state<string[]>([]);
onMount(() => {
try {
done = JSON.parse(localStorage.getItem(DONE) ?? '[]');
} catch {
done = [];
}
const next = SCENES.findIndex((s) => !done.includes(s.id));
if (next > 0) setScene(next);
});
function finished(id: string) {
if (done.includes(id)) return;
done = [...done, id];
try {
localStorage.setItem(DONE, JSON.stringify(done));
} catch {
// Remembered for this visit only.
}
}
const scene = $derived(SCENES[n]);
/** The scene's position, moved on by the reader or set again. */
let cells = $state<Cell[]>(sceneCells(SCENES[0]));
@@ -36,6 +58,7 @@
if (mated) told += ' Checkmate.';
outcome = { told, text: right ? scene.after : (scene.aside ?? scene.after), right };
cells = played.cells;
if (right) finished(scene.id);
}
/** The board and the panel read a game; this one is the scene's, with the reader on the scene's side. */
@@ -60,7 +83,7 @@
<nav class="scenes" aria-label="Scenes">
{#each SCENES as s, i (s.id)}
<button type="button" class="scene-tab" class:current={i === n} onclick={() => setScene(i)}>{s.title}</button>
<button type="button" class="scene-tab" class:current={i === n} class:done={done.includes(s.id)} aria-current={i === n ? 'true' : undefined} onclick={() => setScene(i)}>{#if done.includes(s.id)}<span class="tick" aria-label="done">✓</span>{/if}{s.title}</button>
{/each}
</nav>
@@ -73,8 +96,10 @@
<h2>{scene.title}</h2>
<p>{scene.before}</p>
{#if outcome}
<p class="told" class:right={outcome.right}>{outcome.told}</p>
<p>{outcome.text}</p>
<div aria-live="polite">
<p class="told" class:right={outcome.right}>{outcome.told}</p>
<p>{outcome.text}</p>
</div>
<div class="ways">
<button type="button" class="quiet" onclick={() => setScene(n)}>Set the scene again</button>
{#if n < SCENES.length - 1}<button type="button" class="commit small" onclick={() => setScene(n + 1)}>Next: {SCENES[n + 1].title}</button>{:else}<a class="commit small" href="/learn/game">Next: a game told</a>{/if}
@@ -117,6 +142,19 @@
color: var(--ember);
}
.scene-tab.done {
color: var(--bone);
}
.scene-tab.done.current {
color: var(--ember);
}
.tick {
color: var(--frost);
margin-right: 0.3em;
}
.stage {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(300px, 420px);
+1 -1
View File
@@ -110,7 +110,7 @@
<section id="between">
<h2>Between the boards</h2>
<p>
Five kinds of piece pass between boards. The Griffon, the Hero and the Paladin leap, and nothing in the way stops them. The Mage and the Elemental do not leap. A Mage going from the underworld to the air needs the square on the land between to be empty, and an Elemental needs the square beside it empty on the way up and the square beneath it empty on the way down. The Sylph, the Dwarf, the Cleric and the King move straight up or down, one board at a time, onto the square itself.
Nine kinds of piece pass between boards. The Griffon, the Hero and the Paladin leap, and nothing in the way stops them. The Mage and the Elemental do not leap. A Mage going from the underworld to the air needs the square on the land between to be empty, and an Elemental needs the square beside it empty on the way up and the square beneath it empty on the way down. The Sylph, the Dwarf, the Cleric and the King move straight up or down, one board at a time, onto the square itself.
</p>
</section>