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
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
db682814ea
commit
ee630276b3
+124
-17
@@ -11,7 +11,7 @@
|
||||
import { castKey, listText, tokensText, type Duel } from '$lib/game/duel.svelte';
|
||||
import { seatColor } from '$lib/game/colors';
|
||||
import { GESTURE_SHORT, glyph } from '$lib/game/glyphs';
|
||||
import { MAGIC_GESTURES } from '$lib/game/spells';
|
||||
import { MAGIC_GESTURES, type Gesture } from '$lib/game/spells';
|
||||
|
||||
let { duel }: { duel: Duel } = $props();
|
||||
|
||||
@@ -20,8 +20,37 @@
|
||||
const won = $derived(outcome?.winner === duel.me);
|
||||
/** Past three wizards the rail lists the others on a line each. */
|
||||
const crowded = $derived(duel.others.length >= 3);
|
||||
/** The button's word for what the player is about to do. */
|
||||
const sealLabel = $derived(
|
||||
duel.moved ? 'Waiting' : duel.sending ? 'Sending' : duel.surrendering ? 'Surrender' : duel.timeStopped ? 'Act in the stopped moment' : 'Seal the turn'
|
||||
);
|
||||
|
||||
/** Letters typed anywhere on the board fill the hands, left then right; Enter seals the turn. */
|
||||
const KEYS: Record<string, Gesture> = { f: 'F', p: 'P', s: 'S', w: 'W', d: 'D', c: 'C', k: '>', '>': '>', '-': '-', '.': '-', ' ': '-' };
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
const el = e.target as HTMLElement | null;
|
||||
if (el && (el.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName))) return;
|
||||
if (outcome || duel.out || duel.moved || duel.spectating) return;
|
||||
if (e.key === 'Enter') {
|
||||
if (duel.ready) {
|
||||
e.preventDefault();
|
||||
duel.reveal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Backspace') {
|
||||
e.preventDefault();
|
||||
duel.untype();
|
||||
return;
|
||||
}
|
||||
const g = KEYS[e.key.toLowerCase()];
|
||||
if (g && duel.typeGesture(g)) e.preventDefault();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={onKey} />
|
||||
|
||||
<main class="board">
|
||||
<section class="play">
|
||||
<Ledger {duel} />
|
||||
@@ -54,7 +83,7 @@
|
||||
<h2 id="move-heading">Time stands still. Take your extra turn.</h2>
|
||||
<p class="threats"><span class="muted">{duel.foe.name} cannot move, see this, or resist anything you do. Last turn's enchantments do not bind you.</span></p>
|
||||
{:else}
|
||||
<h2 id="move-heading">Turn {duel.turnNumber}: write your gestures{#if duel.hasted}, twice{/if}</h2>
|
||||
<h2 id="move-heading">Your gestures for turn {duel.turnNumber}{#if duel.hasted}, twice over{/if}</h2>
|
||||
{/if}
|
||||
|
||||
{#if duel.threats.length && !duel.timeStopped}
|
||||
@@ -111,7 +140,7 @@
|
||||
</label>
|
||||
</div>
|
||||
{#if duel.hasted}
|
||||
<p class="haste-note">You are hastened: a second pair of gestures follows the first, and both take effect together.</p>
|
||||
<p class="haste-note">You are hastened, so a second pair of gestures follows the first, and both take effect together.</p>
|
||||
<div class="hands">
|
||||
<HandPicker {duel} hand="left" set="haste" />
|
||||
<HandPicker {duel} hand="right" set="haste" />
|
||||
@@ -191,22 +220,35 @@
|
||||
{#if duel.remote?.error}
|
||||
<p class="warning">{duel.remote.error}</p>
|
||||
{/if}
|
||||
{#if duel.intentText && !duel.moved}
|
||||
<p class="intent">{duel.intentText}</p>
|
||||
{/if}
|
||||
<div class="commit">
|
||||
<div class="pair" aria-label="This turn's gestures">
|
||||
{#each duel.activeSets as set (set)}
|
||||
<span class="hand-pair">
|
||||
<span class="which">left</span> <span class="letter" class:empty={duel.chosenGesture(set, 'left') === null}>{duel.drawn(set, 'left')}</span>
|
||||
<span class="which">right</span> <span class="letter" class:empty={duel.chosenGesture(set, 'right') === null}>{duel.drawn(set, 'right')}</span>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="notes">
|
||||
{#if duel.moved}
|
||||
<p class="waiting">Your move is in. Waiting for {duel.awaitingText}.</p>
|
||||
<p class="waiting">Your hands are still. Waiting for {duel.awaitingText}.</p>
|
||||
{:else if duel.intentText}
|
||||
<p class="intent">{duel.intentText}</p>
|
||||
{/if}
|
||||
{#if duel.surrendering}
|
||||
<p class="warning">Both palms at once is surrender. You will lose the duel.</p>
|
||||
{:else if duel.loneClap && !duel.moved}
|
||||
<p class="warning">A clap needs both hands; one hand clapping does nothing. Clap with both, or rest the other hand.</p>
|
||||
{/if}
|
||||
{#if duel.conflict}
|
||||
<p class="warning">Those two spells share a gesture. A gesture can finish only one spell; let one of them pass.</p>
|
||||
<p class="warning">Those two spells share a gesture, and a gesture can finish only one spell. Let one of them pass.</p>
|
||||
{/if}
|
||||
|
||||
<button type="button" class="reveal" disabled={!duel.ready} onclick={() => duel.reveal()}>
|
||||
{duel.moved ? 'Waiting' : duel.sending ? 'Sending' : duel.surrendering ? 'Surrender' : duel.timeStopped ? 'Act in the stopped moment' : 'Reveal gestures'}
|
||||
</button>
|
||||
{#if !duel.moved}
|
||||
<p class="keys muted">Or type: F P S W D C, K to stab, a dash to rest; Backspace undoes, Enter seals.</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button type="button" class="reveal" disabled={!duel.ready} onclick={() => duel.reveal()}>{sealLabel}</button>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -378,8 +420,66 @@
|
||||
margin: 1rem 0 0.6rem;
|
||||
}
|
||||
|
||||
.move > .reveal {
|
||||
margin-top: 0.8rem;
|
||||
/* Your hands, framed apart from the ledger's columns above them. */
|
||||
.hands {
|
||||
background: var(--slate);
|
||||
border: 1px solid var(--rule-strong);
|
||||
border-radius: 6px;
|
||||
padding: 0.7rem 0.9rem 0.9rem;
|
||||
}
|
||||
|
||||
/* The turn about to be sealed, kept in view while the hands and orders scroll. */
|
||||
.commit {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 4;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.4rem 1rem;
|
||||
margin-top: 0.9rem;
|
||||
padding: 0.6rem 0.2rem;
|
||||
background: var(--slate-deep);
|
||||
border-top: 1px solid var(--rule-strong);
|
||||
}
|
||||
|
||||
.pair {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.hand-pair {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.which {
|
||||
font-size: 0.75rem;
|
||||
font-style: italic;
|
||||
color: var(--bone-faint);
|
||||
}
|
||||
|
||||
.pair .letter {
|
||||
font-size: 1.5rem;
|
||||
min-width: 1ch;
|
||||
}
|
||||
|
||||
.pair .letter.empty {
|
||||
color: var(--bone-faint);
|
||||
}
|
||||
|
||||
.notes p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.notes p + p {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.keys {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.verdict {
|
||||
@@ -407,8 +507,16 @@
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
/* The sticky bar carries the reveal button on phones. */
|
||||
.move > .reveal {
|
||||
/* On phones the bar at the foot of the screen carries the pair and the button. */
|
||||
.commit {
|
||||
position: static;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.commit .pair,
|
||||
.commit .reveal,
|
||||
.keys {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -425,7 +533,6 @@
|
||||
|
||||
.waiting {
|
||||
color: var(--frost);
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.reveal.again {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
import type { LogEntry } from '$lib/game/state';
|
||||
|
||||
let { log }: { log: LogEntry[] } = $props();
|
||||
@@ -9,14 +10,23 @@
|
||||
if (!groups.has(entry.turn)) groups.set(entry.turn, []);
|
||||
groups.get(entry.turn)!.push(entry);
|
||||
}
|
||||
return [...groups.entries()].reverse();
|
||||
return [...groups.entries()];
|
||||
});
|
||||
|
||||
// Read top to bottom like a conversation; each new turn scrolls into view at the foot.
|
||||
let box = $state<HTMLElement | null>(null);
|
||||
$effect(() => {
|
||||
void log.length;
|
||||
void tick().then(() => {
|
||||
if (box) box.scrollTop = box.scrollHeight;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="chronicle">
|
||||
<section class="chronicle" bind:this={box}>
|
||||
<h2>Chronicle</h2>
|
||||
{#if turns.length === 0}
|
||||
<p class="muted">Nothing has happened yet. Choose a gesture for each hand and reveal them.</p>
|
||||
<p class="muted">Nothing has happened yet. Choose a gesture for each hand and seal the turn.</p>
|
||||
{/if}
|
||||
{#each turns as [turn, entries] (turn)}
|
||||
<article>
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="caption">The ledger: every gesture both wizards have made, and the spells they finished.</p>
|
||||
<p class="caption">A few turns of a duel. Each row is a turn, each letter one hand's gesture, and a spell is named under the gesture that finished it.</p>
|
||||
</div>
|
||||
|
||||
{#if seats.length || local}
|
||||
@@ -357,7 +357,7 @@
|
||||
<p>A rule read wrong, a bug, a duel you would like to tell of: write to <a href="mailto:eric@ericwagoner.com">eric@ericwagoner.com</a>, <a href="https://bsky.app/profile/kestrelsnest.social" target="_blank" rel="noreferrer">@kestrelsnest.social</a> on Bluesky, or <a href="https://toots.kestrelsnest.social/@eric" target="_blank" rel="noreferrer">@eric@toots.kestrelsnest.social</a> on Mastodon. The keeper of this hall roosts at <a href="https://kestrelsnest.social" target="_blank" rel="noreferrer">kestrelsnest.social</a>.</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
<About bind:open={aboutOpen} title="About Waving Hands" tabs={[{ id: 'game', label: 'The game', body: aboutGame }, { id: 'tally', label: 'The tally', body: aboutTally }, { id: 'family', label: 'More games', body: aboutFamily }, { id: 'word', label: 'Send word', body: aboutWord }]} />
|
||||
<About bind:open={aboutOpen} title="About Waving Hands" tabs={[{ id: 'game', label: 'Origins', body: aboutGame }, { id: 'tally', label: 'Duels so far', body: aboutTally }, { id: 'family', label: 'More games', body: aboutFamily }, { id: 'word', label: 'Send word', body: aboutWord }]} />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -21,20 +21,20 @@
|
||||
const hiddenPlans = $derived(duel.plans[hand].length - plans.length);
|
||||
const whyForced = $derived(
|
||||
duel.you.constraints.amnesia
|
||||
? 'Amnesia: you must repeat last turn.'
|
||||
? 'Amnesia holds this hand to last turn\'s gesture.'
|
||||
: duel.you.constraints.paralysedHand === hand
|
||||
? "Paralysed in last turn's position."
|
||||
? "Paralysis has locked this hand in last turn's position."
|
||||
: ''
|
||||
);
|
||||
</script>
|
||||
|
||||
<fieldset class="hand">
|
||||
<legend>{hand === 'left' ? 'Left hand' : 'Right hand'}{#if set === 'haste'}<span class="again">, again</span>{/if}</legend>
|
||||
<legend>{hand === 'left' ? 'Your left hand' : 'Your right hand'}{#if set === 'haste'}<span class="again">, again</span>{/if}</legend>
|
||||
|
||||
{#if forced}
|
||||
<p class="locked"><span class="letter big">{glyph(forced, duel.gestureFor(set, other))}</span> {whyForced}</p>
|
||||
{:else if charmed}
|
||||
<p class="locked"><span class="letter big">?</span> Charmed: {duel.foe.name} chooses this hand's gesture.</p>
|
||||
<p class="locked"><span class="letter big">?</span> {duel.foe.name} has charmed this hand and chooses its gesture.</p>
|
||||
{:else}
|
||||
<div class="gestures" role="group" aria-label="{hand} hand gesture{set === 'haste' ? ', second pair' : ''}">
|
||||
{#each GESTURES as g (g)}
|
||||
@@ -53,7 +53,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{#if duel.you.constraints.fear && !duel.timeStopped}
|
||||
<p class="hint">Fear: no clap, digit, fingers or snap this turn.</p>
|
||||
<p class="hint">Fear keeps this hand from clapping, pointing, wriggling or snapping this turn.</p>
|
||||
{/if}
|
||||
{#if set === 'main' && duel.showPlans}
|
||||
{#if duel.plans[hand].length === 0}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
{/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' : 'Reveal'}
|
||||
{duel.out ? 'Out' : duel.moved ? 'Waiting' : duel.surrendering ? 'Surrender' : duel.timeStopped ? 'Act' : 'Seal'}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{#if details.length}
|
||||
<h3>The ledgers in detail</h3>
|
||||
<h3>Every count</h3>
|
||||
<dl class="ledger">
|
||||
{#each details as d (d.label)}
|
||||
<dt>{shown(d.n)}</dt><dd>{d.label}</dd>
|
||||
|
||||
@@ -696,6 +696,35 @@ export class Duel {
|
||||
return list;
|
||||
}
|
||||
|
||||
/** The hands a typed gesture fills, in the order they are filled: left then right, and the second pair after the first when hastened. */
|
||||
private typingSlots(): { set: SetName; hand: Hand }[] {
|
||||
return this.activeSets.flatMap((set) =>
|
||||
HANDS.filter((h) => !this.forcedFor(set, h) && this.charmedHand !== h).map((hand) => ({ set, hand }))
|
||||
);
|
||||
}
|
||||
|
||||
/** A gesture typed on the keyboard goes to the first open hand. Returns false when no hand can take it. */
|
||||
typeGesture(gesture: Gesture): boolean {
|
||||
const slot = this.typingSlots().find((s) => this.sets[s.set][s.hand].gesture === null);
|
||||
if (!slot || !this.allowed[slot.hand].includes(gesture)) return false;
|
||||
this.sets[slot.set][slot.hand].gesture = gesture;
|
||||
this.syncAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Backspace clears the last hand filled. */
|
||||
untype(): void {
|
||||
const slot = [...this.typingSlots()].reverse().find((s) => this.sets[s.set][s.hand].gesture !== null);
|
||||
if (!slot) return;
|
||||
this.sets[slot.set][slot.hand].gesture = null;
|
||||
this.syncAll();
|
||||
}
|
||||
|
||||
/** A clap with only one hand does nothing; said before the turn is sealed, not after. */
|
||||
loneClap = $derived(
|
||||
this.activeSets.some((set) => (this.gestureFor(set, 'left') === 'C') !== (this.gestureFor(set, 'right') === 'C'))
|
||||
);
|
||||
|
||||
choose(set: SetName, hand: Hand, gesture: Gesture): void {
|
||||
const draft = this.sets[set][hand];
|
||||
draft.gesture = draft.gesture === gesture ? null : gesture;
|
||||
|
||||
@@ -49,3 +49,28 @@ describe('threats at a crowded table', () => {
|
||||
expect(duel.targetLabel('C')).toBe('Cass · 8 HP · invisible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('typing a move', () => {
|
||||
it('fills the left hand, then the right, and backspace takes the last one back', () => {
|
||||
const duel = new Duel(createGame({ A: 'You', B: 'Foe' }, 1));
|
||||
expect(duel.typeGesture('S')).toBe(true);
|
||||
expect(duel.typeGesture('P')).toBe(true);
|
||||
expect(duel.sets.main.left.gesture).toBe('S');
|
||||
expect(duel.sets.main.right.gesture).toBe('P');
|
||||
expect(duel.typeGesture('W')).toBe(false);
|
||||
duel.untype();
|
||||
expect(duel.sets.main.right.gesture).toBeNull();
|
||||
expect(duel.sets.main.left.gesture).toBe('S');
|
||||
});
|
||||
|
||||
it('warns of a clap made with one hand before the turn is sealed', () => {
|
||||
const duel = new Duel(createGame({ A: 'You', B: 'Foe' }, 1));
|
||||
duel.typeGesture('C');
|
||||
duel.typeGesture('W');
|
||||
expect(duel.loneClap).toBe(true);
|
||||
expect(duel.ready).toBe(true);
|
||||
duel.untype();
|
||||
duel.typeGesture('C');
|
||||
expect(duel.loneClap).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<a href="#ledger">The ledger</a>
|
||||
<a href="#hands">Your hands</a>
|
||||
<a href="#threats">What they may do</a>
|
||||
<a href="#reveal">The reveal</a>
|
||||
<a href="#reveal">Sealing the turn</a>
|
||||
<a href="#rail">Health, creatures and the sheet</a>
|
||||
<a href="#phone">On a phone</a>
|
||||
<a href="#gallery">The Peanut Gallery and table talk</a>
|
||||
@@ -53,7 +53,7 @@
|
||||
<figure>
|
||||
<img src="/guide/hands.png" alt="Two panels of gesture buttons, left hand and right, each with a list beneath of spells under way and the gesture that continues them." />
|
||||
</figure>
|
||||
<p>Press a gesture for each hand. Beneath each hand is what that hand is part-way through: <em>D → F-W, Cure light wounds</em> means the digit you made last turn is the first gesture of that spell, and F then W would finish it. Press a line to make its next gesture; the diamond pins a spell so it stays at the top while you work toward it.</p>
|
||||
<p>Press a gesture for each hand, or type them: the first letter you type goes to your left hand and the second to your right, with K for a stab, a dash to rest, Backspace to take one back and Enter to seal the turn. The two letters wait in a row at the foot of the hands, beside the button, however far the page has scrolled. Beneath each hand is what that hand is part-way through: <em>D → F-W, Cure light wounds</em> means the digit you made last turn is the first gesture of that spell, and F then W would finish it. Press a line to make its next gesture; the diamond pins a spell so it stays at the top while you work toward it.</p>
|
||||
<p>When a gesture finishes a spell, <em>Finishes</em> appears with the spell chosen and <em>at</em> naming the likeliest subject. Change either. A gesture can belong to more than one spell so long as each run is unbroken, which is how F-F-F followed by S-S-D-D casts a Paralysis and then a Fireball; but one gesture may finish only one spell, and the page will tell you when two of your choices would share one.</p>
|
||||
<p>Some hands are not yours to move. A hand paralysed or charmed by a rival, or a turn under amnesia, shows its forced gesture locked. Both palms at once is surrender, and the page warns before you do it.</p>
|
||||
</section>
|
||||
@@ -68,11 +68,11 @@
|
||||
</section>
|
||||
|
||||
<section id="reveal">
|
||||
<h2>The reveal</h2>
|
||||
<h2>Sealing the turn</h2>
|
||||
<figure>
|
||||
<img src="/guide/result.png" alt="The result strip: each wizard's health before and after the turn, then the events of the turn in a line each." />
|
||||
</figure>
|
||||
<p><strong>Reveal gestures</strong> commits your turn. Against the bot the turn resolves at once; at a table it resolves when the last wizard has moved, and until then the button reads <em>Waiting</em>. Everything happens together: a shield made this turn stops a missile made this turn, a creature summoned this turn strikes this turn, and a cure lands in the same breath as the wound.</p>
|
||||
<p><strong>Seal the turn</strong> commits your two gestures; nobody else sees them until every wizard has sealed theirs, and then all hands are shown at once. Against the bot the turn resolves at once; at a table it resolves when the last wizard has moved, and until then the button reads <em>Waiting</em>. Everything happens together: a shield made this turn stops a missile made this turn, a creature summoned this turn strikes this turn, and a cure lands in the same breath as the wound.</p>
|
||||
<p>The strip above the hands says what changed: each wizard's health, then the events worth knowing, and the first time a rule surprises you, a line explaining it. The chronicle in the rail keeps the whole story.</p>
|
||||
</section>
|
||||
|
||||
@@ -93,9 +93,9 @@
|
||||
<section id="phone">
|
||||
<h2>On a phone</h2>
|
||||
<figure class="narrow">
|
||||
<img src="/guide/phone.png" alt="The duel on a phone: the ledger, the result, the hands, and a bar fixed at the foot with every wizard's health, the chosen gestures and the reveal button." />
|
||||
<img src="/guide/phone.png" alt="The duel on a phone: the ledger, the result, the hands, and a bar fixed at the foot with every wizard's health, the chosen gestures and the button that seals the turn." />
|
||||
</figure>
|
||||
<p>Everything is here, in one column. The bar at the foot keeps every wizard's health, your two chosen gestures and the reveal button in reach; after a reveal the page brings the ledger's last rows and the result into view. Lists are shorter, and <em>More spells</em> opens the rest.</p>
|
||||
<p>Everything is here, in one column. The bar at the foot keeps every wizard's health, your two chosen gestures and the button that seals the turn in reach; after each turn the page brings the ledger's last rows and the result into view. Lists are shorter, and <em>More spells</em> opens the rest.</p>
|
||||
</section>
|
||||
|
||||
<section id="gallery">
|
||||
|
||||
Reference in New Issue
Block a user