diff --git a/CONVENTIONS.md b/CONVENTIONS.md
index 5e5e543..2c9e8ac 100644
--- a/CONVENTIONS.md
+++ b/CONVENTIONS.md
@@ -158,6 +158,29 @@ emoji in the interface; the words do the work. Phone width first: a sticky
bar for the move if the board runs long, lists that shorten, gutters of
16px, no horizontal scroll.
+## The voice
+
+Every game speaks in its own world. Wherever the page tells a player what
+happened, it gives an image as well as the fact: not "Corwin takes 5 from
+a fireball" but "A fireball bursts over Corwin, for 5". The fact stays
+exact and whole (the number, the duration, who is affected, the turn a
+thing will happen), and the image carries it.
+
+- **The chronicle** is where the voice matters most. Each move, blow,
+ enchantment, arrival and death gets its own picture. Something that
+ unfolds over turns (an illness, a siege, a fuse) is told a turn at a
+ time, names the turn it ends on when it begins, and warns on the last
+ turn anything can be done about it.
+- **The rest of the surround** speaks the same way: the lobby, the guide,
+ the status lines, the verdict. Plain words for controls and buttons;
+ the images belong to what happens, not to what you press.
+- **Our own words.** An older implementation's prose is a guide to the
+ tone, never a source to copy.
+- **Changing text is not changing rules.** Chronicle wording is not part
+ of the ledger, so it ships without a rules revision; the deploy gate
+ compares outcomes, not sentences. A test that pins a phrase should pin
+ the fact inside it.
+
## Things learned the hard way
- Caddy's `try_files` rewrites `/api` before `reverse_proxy` unless the
diff --git a/new-game.sh b/new-game.sh
index 4905ce1..fda0887 100755
--- a/new-game.sh
+++ b/new-game.sh
@@ -37,6 +37,8 @@ Next:
and src/lib/components/Board.svelte with its board. Keep the tests green.
2. Fill in the hall's pitch, tag and about panel (src/lib/components/Hall.svelte),
the rules page, the guide, src/app.html's description, and the palette in src/app.css.
+ Write every line a player reads in the game's own voice: an image with each
+ fact (docs/conventions.md, "The voice").
Add static/og.png (1200 by 630) and static/apple-touch-icon.png (180 by 180):
app.html already points at them, and the static fallback serves the hall's
HTML for a missing file, so a share card without the image breaks quietly.
diff --git a/template/src/lib/components/Board.svelte b/template/src/lib/components/Board.svelte
index 5d4e225..a4a6681 100644
--- a/template/src/lib/components/Board.svelte
+++ b/template/src/lib/components/Board.svelte
@@ -56,12 +56,12 @@
{:else if room.spectating}
You watch from the Peanut Gallery
-
{room.awaitingText ? `${room.awaitingText} ${room.awaiting.length === 1 ? 'is' : 'are'} still choosing.` : 'The round is being written.'}
+
{room.awaitingText ? `${room.awaitingText} ${room.awaiting.length === 1 ? 'turns a card over and over, still choosing.' : 'turn their cards over and over, still choosing.'}` : 'Cards are being chosen, face down.'}
{:else}
Round {g.rounds.length + 1}: name a number
-
The highest number named by exactly one player scores. First to {TARGET}.
+
Every player lays a card face down; the highest card nobody else laid takes the point. First to {TARGET}.
{#each Array.from({ length: HIGHEST }, (_, i) => i + 1) as n (n)}
diff --git a/template/src/lib/game/index.ts b/template/src/lib/game/index.ts
index 6485635..82b943f 100644
--- a/template/src/lib/game/index.ts
+++ b/template/src/lib/game/index.ts
@@ -68,7 +68,7 @@ export function resolveRound(previous: State, inputs: Record): St
if (scorer) state.players[scorer].points += 1;
state.rounds.push({ picks, scorer });
if (scorer && state.players[scorer].points >= TARGET) {
- state.over = { winner: scorer, reason: `${state.players[scorer].name} reached ${TARGET} points.` };
+ state.over = { winner: scorer, reason: `${state.players[scorer].name} lays down the last card and sweeps the table: ${TARGET} points, and the game.` };
}
return state;
}