The guide keeps the room too, every cross-link carries it, and a move picked from the record brings the board into view on a phone
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
c5d487edbf
commit
145667ea0d
+115
-77
@@ -3,15 +3,16 @@
|
|||||||
// on them as tokens that slide and fade, and two taps or a keyboard to
|
// on them as tokens that slide and fade, and two taps or a keyboard to
|
||||||
// move. Above it, a strip says whose move it is and what the side is
|
// move. Above it, a strip says whose move it is and what the side is
|
||||||
// for; beside it, the record of moves, each one a click from being shown
|
// for; beside it, the record of moves, each one a click from being shown
|
||||||
// on the board. Copenhagen is fresh oak and clean stone; Tablut, the
|
// on the board. Copenhagen is a newly crafted oak reenactment set;
|
||||||
// older game, is grey, bleached and a little uneven, by the tokens under
|
// Tablut evokes an excavated stone board and worn counters. This is an
|
||||||
// data-set and a heavier grain and warp in the filters.
|
// artistic treatment, not a claim to reconstruct a particular find.
|
||||||
import TableTalk from './TableTalk.svelte';
|
import TableTalk from './TableTalk.svelte';
|
||||||
import { beyond, cellOf, cornersOf, isEdge, movesFrom, rulesetOf, sideOf, throneOf, type Move, type Side } from '$lib/game';
|
import { beyond, cellOf, cornersOf, isEdge, movesFrom, rulesetOf, sideOf, throneOf, type Move, type Side } from '$lib/game';
|
||||||
import type { Room } from '$lib/net/room.svelte';
|
import type { Room } from '$lib/net/room.svelte';
|
||||||
import { prefs, reducedMotion } from '$lib/prefs.svelte';
|
import { prefs, reducedMotion } from '$lib/prefs.svelte';
|
||||||
|
|
||||||
let { room }: { room: Room } = $props();
|
let { room }: { room: Room } = $props();
|
||||||
|
const artId = $props.id();
|
||||||
|
|
||||||
const g = $derived(room.state!);
|
const g = $derived(room.state!);
|
||||||
const set = $derived(rulesetOf(g));
|
const set = $derived(rulesetOf(g));
|
||||||
@@ -54,7 +55,7 @@
|
|||||||
const chips = $derived.by(() => {
|
const chips = $derived.by(() => {
|
||||||
if (!worn) return [] as { x: number; y: number; r: number; d: string }[];
|
if (!worn) return [] as { x: number; y: number; r: number; d: string }[];
|
||||||
const spots = [
|
const spots = [
|
||||||
[1, 0, 3], [size - 1, 2, 0], [3, size - 1, 1], [size - 2, size - 2, 2], [0, size - 3, 3], [size - 4, 0, 1], [2, 4, 0]
|
[0, 0, 0], [size - 1, 2, 1], [3, size - 1, 2], [0, size - 3, 3], [size - 4, 0, 1]
|
||||||
] as const;
|
] as const;
|
||||||
return spots.map(([cx, cy, corner]) => {
|
return spots.map(([cx, cy, corner]) => {
|
||||||
const x = PAD_L + cx * CELL + (corner === 1 || corner === 2 ? CELL : 0);
|
const x = PAD_L + cx * CELL + (corner === 1 || corner === 2 ? CELL : 0);
|
||||||
@@ -71,8 +72,8 @@
|
|||||||
const w = PAD_L + size * CELL;
|
const w = PAD_L + size * CELL;
|
||||||
const h = PAD_T + size * CELL;
|
const h = PAD_T + size * CELL;
|
||||||
return [
|
return [
|
||||||
`M${PAD_L + CELL * 0.6} ${PAD_T + 2} c 6 14, -4 22, 8 36 s 2 18, 14 26 s 4 10, 2 22`,
|
`M${PAD_L + 22} ${PAD_T} l3 11 -2 7 5 12 -1 9 4 8 -2 12 m-1 -29 -8 5 -3 7`,
|
||||||
`M${w - 3} ${h - CELL * 2.4} c -12 4, -18 -6, -30 2 s -14 12, -26 8 s -12 -4, -18 6`
|
`M${w} ${h - 62} l-12 3 -6 -2 -9 6 -7 1 -5 6 -10 3 m22 -10 -2 -7 -5 -4`
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -270,31 +271,55 @@
|
|||||||
<div class="frame" class:calm>
|
<div class="frame" class:calm>
|
||||||
<svg viewBox="0 0 {width} {height}" role="grid" aria-label="The board">
|
<svg viewBox="0 0 {width} {height}" role="grid" aria-label="The board">
|
||||||
<defs>
|
<defs>
|
||||||
<filter id="grain" x="0" y="0" width="100%" height="100%">
|
<linearGradient id={`${artId}-surface`} x2="0.85" y2="1">
|
||||||
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="7" result="noise" />
|
<stop stop-color="var(--surface-light)" />
|
||||||
<feColorMatrix in="noise" type="saturate" values="0" result="grey" />
|
<stop offset="0.48" stop-color="var(--wood)" />
|
||||||
<feComponentTransfer in="grey" result="soft"><feFuncA type="linear" slope={worn ? 0.5 : 0.14} /></feComponentTransfer>
|
<stop offset="1" stop-color="var(--surface-dark)" />
|
||||||
|
</linearGradient>
|
||||||
|
<filter id={`${artId}-grain`} x="0" y="0" width="100%" height="100%">
|
||||||
|
<feTurbulence type="fractalNoise" baseFrequency={worn ? '0.48' : '0.025 0.5'} numOctaves="3" seed="7" result="noise" />
|
||||||
|
<feColorMatrix in="noise" type="saturate" values="0" />
|
||||||
|
<feComponentTransfer result="soft"><feFuncA type="linear" slope={worn ? 0.2 : 0.13} /></feComponentTransfer>
|
||||||
<feBlend in="SourceGraphic" in2="soft" mode="multiply" />
|
<feBlend in="SourceGraphic" in2="soft" mode="multiply" />
|
||||||
</filter>
|
</filter>
|
||||||
<filter id="worn" x="-5%" y="-5%" width="110%" height="110%">
|
<filter id={`${artId}-worn`} x="-5%" y="-5%" width="110%" height="110%">
|
||||||
<feTurbulence type="fractalNoise" baseFrequency="0.07" numOctaves="2" seed="3" result="warp" />
|
<feTurbulence type="fractalNoise" baseFrequency="0.09" numOctaves="2" seed="3" result="warp" />
|
||||||
<feDisplacementMap in="SourceGraphic" in2="warp" scale={worn ? 2.8 : 0} xChannelSelector="R" yChannelSelector="G" />
|
<feDisplacementMap in="SourceGraphic" in2="warp" scale="0.65" xChannelSelector="R" yChannelSelector="G" />
|
||||||
</filter>
|
</filter>
|
||||||
<filter id="mottle" x="0" y="0" width="100%" height="100%">
|
<filter id={`${artId}-mottle`} x="0" y="0" width="100%" height="100%">
|
||||||
<feTurbulence type="fractalNoise" baseFrequency="0.018" numOctaves="3" seed="11" result="blotch" />
|
<feTurbulence type="fractalNoise" baseFrequency="0.025" numOctaves="3" seed="11" />
|
||||||
<feColorMatrix in="blotch" type="matrix" values="0 0 0 0 0.16 0 0 0 0 0.14 0 0 0 0 0.11 0 0 0 0.55 0" result="stain" />
|
<feColorMatrix type="matrix" values="0 0 0 0 0.20 0 0 0 0 0.17 0 0 0 0 0.12 0.28 0 0 0 0" />
|
||||||
<feBlend in="SourceGraphic" in2="stain" mode="multiply" />
|
<feComposite in2="SourceGraphic" operator="in" />
|
||||||
</filter>
|
</filter>
|
||||||
|
{#each ['attacker', 'defender', 'king'] as kind (kind)}
|
||||||
|
<radialGradient id={`${artId}-${kind}`} cx="32%" cy="24%" r="80%">
|
||||||
|
<stop stop-color={`var(--${kind}-light)`} />
|
||||||
|
<stop offset="0.65" stop-color={`var(--${kind})`} />
|
||||||
|
<stop offset="1" stop-color={`var(--${kind}-rim)`} />
|
||||||
|
</radialGradient>
|
||||||
|
{/each}
|
||||||
</defs>
|
</defs>
|
||||||
<rect class="wood" x="0" y="0" {width} {height} rx="6" filter="url(#grain)" />
|
<rect class="wood" x="0.7" y="0.7" width={width - 1.4} height={height - 1.4} rx={worn ? 3 : 6} fill={`url(#${artId}-surface)`} filter={`url(#${artId}-grain)`} />
|
||||||
|
<rect class="board-rim" x="2" y="2" width={width - 4} height={height - 4} rx={worn ? 2 : 5} />
|
||||||
{#if worn}
|
{#if worn}
|
||||||
<rect class="stain" x="0" y="0" {width} {height} rx="6" filter="url(#mottle)" />
|
<rect class="stain" x="2" y="2" width={width - 4} height={height - 4} rx="3" filter={`url(#${artId}-mottle)`} />
|
||||||
|
{:else}
|
||||||
|
{#each [0.22, 0.51, 0.79] as part (part)}
|
||||||
|
<path class="join" d={`M${PAD_L + size * CELL * part} 3 v${height - 6}`} />
|
||||||
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
<g class="surface-wear" aria-hidden="true">
|
||||||
|
{#each chips as chip (chip.d)}<path class="chip" d={chip.d} />{/each}
|
||||||
|
{#each cracks as crack (crack)}
|
||||||
|
<path class="crack-light" d={crack} transform="translate(0.35 0.4)" />
|
||||||
|
<path class="crack" d={crack} />
|
||||||
|
{/each}
|
||||||
|
</g>
|
||||||
{#each showCoords ? Array.from({ length: size }, (_, n) => n) : [] as n (n)}
|
{#each showCoords ? Array.from({ length: size }, (_, n) => n) : [] as n (n)}
|
||||||
<text class="coord" x={PAD_L + n * CELL + CELL / 2} y={height - 4} text-anchor="middle">{String.fromCharCode(97 + n)}</text>
|
<text class="coord" x={PAD_L + n * CELL + CELL / 2} y={height - 4} text-anchor="middle">{String.fromCharCode(97 + n)}</text>
|
||||||
<text class="coord" x={PAD_L - 4} y={PAD_T + n * CELL + CELL / 2 + 3} text-anchor="end">{size - n}</text>
|
<text class="coord" x={PAD_L - 4} y={PAD_T + n * CELL + CELL / 2 + 3} text-anchor="end">{size - n}</text>
|
||||||
{/each}
|
{/each}
|
||||||
<g filter="url(#worn)">
|
<g>
|
||||||
{#each g.cells as piece, i (i)}
|
{#each g.cells as piece, i (i)}
|
||||||
{@const c = cellOf(size, i)}
|
{@const c = cellOf(size, i)}
|
||||||
{@const x = PAD_L + c.x * CELL}
|
{@const x = PAD_L + c.x * CELL}
|
||||||
@@ -323,11 +348,12 @@
|
|||||||
<circle class="hint" cx={x + CELL / 2} cy={y + CELL / 2} r={CELL * 0.14} pointer-events="none" />
|
<circle class="hint" cx={x + CELL / 2} cy={y + CELL / 2} r={CELL * 0.14} pointer-events="none" />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#each chips as chip (chip.d)}
|
{#each [throne, ...corners] as place (place)}
|
||||||
<path class="chip" d={chip.d} />
|
{@const c = cellOf(size, place)}
|
||||||
{/each}
|
<g class="inlay" transform={`translate(${PAD_L + c.x * CELL + 20} ${PAD_T + c.y * CELL + 20})`} aria-hidden="true">
|
||||||
{#each cracks as crack (crack)}
|
<path d="M0 -15 L15 0 0 15 -15 0 Z" />
|
||||||
<path class="crack" d={crack} />
|
{#if !worn}<path d="M-7 0 H7 M0 -7 V7" />{/if}
|
||||||
|
</g>
|
||||||
{/each}
|
{/each}
|
||||||
{#each tokens as t (t.id)}
|
{#each tokens as t (t.id)}
|
||||||
{@const c = cellOf(size, t.at)}
|
{@const c = cellOf(size, t.at)}
|
||||||
@@ -340,11 +366,21 @@
|
|||||||
style="transform: translate({cx}px, {cy}px)"
|
style="transform: translate({cx}px, {cy}px)"
|
||||||
pointer-events="none"
|
pointer-events="none"
|
||||||
>
|
>
|
||||||
<ellipse class="shadow" cx="1.5" cy="2.5" rx={CELL * 0.34} ry={CELL * 0.3} />
|
<ellipse class="shadow" cx="1" cy="3" rx="14" ry="12.5" />
|
||||||
<circle class="body" cx="0" cy="0" r={CELL * 0.33} />
|
<g filter={worn ? `url(#${artId}-worn)` : undefined}>
|
||||||
{#if t.piece === 'k'}
|
<circle class="base" cy="1.2" r="13.6" />
|
||||||
<path class="crown" d="M-9 4 l0 -9 l4.5 5 l4.5 -8 l4.5 8 l4.5 -5 l0 9 z" />
|
<circle class="body" r="13.2" fill={`url(#${artId}-${t.piece === 'a' ? 'attacker' : t.piece === 'k' ? 'king' : 'defender'})`} />
|
||||||
{/if}
|
<path class="bevel-light" d="M-11.5 3 A12 12 0 0 1 8 -9" />
|
||||||
|
<path class="bevel-dark" d="M11.5 -3 A12 12 0 0 1 -7 9.8" />
|
||||||
|
<circle class="turned" r="10.2" />
|
||||||
|
{#if worn}
|
||||||
|
<path class="counter-wear" transform={`rotate(${(t.id * 137) % 360})`} d="M-10 -6 l2 1 M4 10 l2 -1 M-5 -9 l3 0.6" />
|
||||||
|
{/if}
|
||||||
|
{#if t.piece === 'k'}
|
||||||
|
<path class="crown-gleam" transform="translate(0.25 0.55)" d="M-8 4 V-5 L-4 0 0 -7 4 0 8 -5 V4 Z M-7 6 H7" />
|
||||||
|
<path class="crown" d="M-8 4 V-5 L-4 0 0 -7 4 0 8 -5 V4 Z M-7 6 H7" />
|
||||||
|
{/if}
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
{/each}
|
{/each}
|
||||||
</g>
|
</g>
|
||||||
@@ -400,16 +436,22 @@
|
|||||||
padding: 0 var(--gutter);
|
padding: 0 var(--gutter);
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
/* Copenhagen: fresh oak, clean stone. */
|
/* Copenhagen: a crafted oak board, polished counters and brass inlay. */
|
||||||
--wood: #8a5a2b;
|
--surface-light: #bd935c;
|
||||||
|
--surface-dark: #79502d;
|
||||||
|
--rim-light: #e0bf84;
|
||||||
|
--attacker-light: #63564a;
|
||||||
|
--defender-light: #fff4d8;
|
||||||
|
--king-light: #ffe4a0;
|
||||||
|
--wood: #a77945;
|
||||||
--wood-line: rgba(30, 16, 6, 0.55);
|
--wood-line: rgba(30, 16, 6, 0.55);
|
||||||
--coord: rgba(255, 240, 210, 0.55);
|
--coord: #302216;
|
||||||
--square: rgba(255, 240, 210, 0.08);
|
--square: rgba(255, 240, 210, 0.08);
|
||||||
--square-edge: rgba(255, 250, 235, 0.11);
|
--square-edge: rgba(255, 250, 235, 0.11);
|
||||||
--throne: rgba(240, 165, 58, 0.35);
|
--throne: rgba(240, 165, 58, 0.35);
|
||||||
--corner: rgba(224, 101, 92, 0.35);
|
--corner: rgba(109, 52, 29, 0.22);
|
||||||
--attacker: #2a2320;
|
--attacker: #2a2320;
|
||||||
--attacker-rim: #0d0a08;
|
--attacker-rim: #211a15;
|
||||||
--defender: #ece4d0;
|
--defender: #ece4d0;
|
||||||
--defender-rim: #b9ab8b;
|
--defender-rim: #b9ab8b;
|
||||||
--king: #f0d58a;
|
--king: #f0d58a;
|
||||||
@@ -417,17 +459,23 @@
|
|||||||
--crown: #6b4a0f;
|
--crown: #6b4a0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tablut: the older game, cut in stone and kept in a long hall for a thousand years. */
|
/* Tablut: an excavated-object aesthetic, with limestone and aged counters. */
|
||||||
.board[data-set='tablut'] {
|
.board[data-set='tablut'] {
|
||||||
|
--surface-light: #aaa18b;
|
||||||
|
--surface-dark: #797564;
|
||||||
|
--rim-light: #c4b99e;
|
||||||
|
--attacker-light: #625e51;
|
||||||
|
--defender-light: #e2d5b3;
|
||||||
|
--king-light: #e5cc92;
|
||||||
--wood: #8a8578;
|
--wood: #8a8578;
|
||||||
--wood-line: rgba(36, 32, 27, 0.45);
|
--wood-line: rgba(36, 32, 27, 0.45);
|
||||||
--coord: rgba(30, 26, 20, 0.5);
|
--coord: #37362c;
|
||||||
--square: rgba(255, 250, 235, 0.05);
|
--square: rgba(255, 250, 235, 0.05);
|
||||||
--square-edge: rgba(255, 250, 235, 0.09);
|
--square-edge: rgba(255, 250, 235, 0.09);
|
||||||
--throne: rgba(120, 100, 70, 0.4);
|
--throne: rgba(120, 100, 70, 0.4);
|
||||||
--corner: transparent;
|
--corner: transparent;
|
||||||
--attacker: #3d3530;
|
--attacker: #3d3530;
|
||||||
--attacker-rim: #1c1714;
|
--attacker-rim: #29281f;
|
||||||
--defender: #cfc4ad;
|
--defender: #cfc4ad;
|
||||||
--defender-rim: #8c8069;
|
--defender-rim: #8c8069;
|
||||||
--king: #c9b27a;
|
--king: #c9b27a;
|
||||||
@@ -510,12 +558,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wood {
|
.wood {
|
||||||
fill: var(--wood);
|
stroke: var(--surface-dark);
|
||||||
transition: fill 1.2s ease;
|
stroke-width: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stain {
|
.stain {
|
||||||
fill: rgba(120, 110, 96, 0.22);
|
fill: #fff;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,8 +576,8 @@
|
|||||||
|
|
||||||
.crack {
|
.crack {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: rgba(24, 20, 16, 0.55);
|
stroke: rgba(43, 39, 29, 0.48);
|
||||||
stroke-width: 0.9;
|
stroke-width: 0.5;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -538,13 +586,14 @@
|
|||||||
fill: var(--coord);
|
fill: var(--coord);
|
||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.square {
|
.square {
|
||||||
fill: var(--square);
|
fill: var(--square);
|
||||||
stroke: var(--wood-line);
|
stroke: var(--wood-line);
|
||||||
stroke-width: 1;
|
stroke-width: 0.65;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
transition: fill 0.3s ease;
|
transition: fill 0.3s ease;
|
||||||
}
|
}
|
||||||
@@ -594,40 +643,29 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.piece .shadow {
|
.board-rim { fill: none; stroke: var(--rim-light); stroke-width: 0.7; opacity: 0.65; pointer-events: none; }
|
||||||
fill: rgba(0, 0, 0, 0.35);
|
.join { fill: none; stroke: #50351f; stroke-width: 0.6; opacity: 0.18; pointer-events: none; }
|
||||||
}
|
.surface-wear { pointer-events: none; }
|
||||||
|
.crack-light { fill: none; stroke: #e1d6bd; stroke-width: 0.55; opacity: 0.45; }
|
||||||
|
.inlay { fill: none; stroke: var(--rim-light); stroke-width: 0.8; opacity: 0.75; pointer-events: none; }
|
||||||
|
.board[data-set='tablut'] .inlay { stroke: #494638; opacity: 0.45; }
|
||||||
|
|
||||||
.piece .body {
|
.piece { --piece-rim: var(--defender-rim); }
|
||||||
stroke-width: 2;
|
.piece.attacker { --piece-rim: var(--attacker-rim); }
|
||||||
transition:
|
.piece.king { --piece-rim: var(--king-rim); }
|
||||||
fill 1.2s ease,
|
.piece .shadow { fill: #16130e; opacity: 0.28; }
|
||||||
stroke 1.2s ease;
|
.piece .base { fill: var(--piece-rim); }
|
||||||
}
|
.piece .body { stroke: var(--piece-rim); stroke-width: 0.65; }
|
||||||
|
.bevel-light { fill: none; stroke: #fff1cc; stroke-width: 0.9; opacity: 0.48; stroke-linecap: round; }
|
||||||
.piece.attacker .body {
|
.bevel-dark { fill: none; stroke: #211d16; stroke-width: 0.8; opacity: 0.3; stroke-linecap: round; }
|
||||||
fill: var(--attacker);
|
.turned { fill: none; stroke: var(--piece-rim); stroke-width: 0.45; opacity: 0.35; }
|
||||||
stroke: var(--attacker-rim);
|
.counter-wear { fill: none; stroke: #534b37; stroke-width: 0.5; opacity: 0.3; stroke-linecap: round; }
|
||||||
}
|
.attacker .counter-wear { stroke: #d6c6a3; opacity: 0.23; }
|
||||||
|
.board[data-set='tablut'] .bevel-light { opacity: 0.25; stroke-width: 1.1; }
|
||||||
.piece.defender .body {
|
.board[data-set='tablut'] .turned { opacity: 0.12; }
|
||||||
fill: var(--defender);
|
.piece .crown { fill: var(--crown); stroke: var(--crown); stroke-width: 0.7; stroke-linejoin: round; }
|
||||||
stroke: var(--defender-rim);
|
.piece .crown-gleam { fill: #f8e5b2; stroke: #f8e5b2; stroke-width: 0.9; opacity: 0.7; }
|
||||||
}
|
.piece.selected .body { stroke: var(--frost); stroke-width: 2.5; }
|
||||||
|
|
||||||
.piece.king .body {
|
|
||||||
fill: var(--king);
|
|
||||||
stroke: var(--king-rim);
|
|
||||||
}
|
|
||||||
|
|
||||||
.piece .crown {
|
|
||||||
fill: var(--crown);
|
|
||||||
}
|
|
||||||
|
|
||||||
.piece.selected .body {
|
|
||||||
stroke: var(--frost);
|
|
||||||
stroke-width: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how {
|
.how {
|
||||||
max-width: 620px;
|
max-width: 620px;
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// How to play: a walk through the page, from the hall to a first game.
|
// How to play: a walk through the page, from the hall to a first game.
|
||||||
|
import { page } from '$app/state';
|
||||||
|
|
||||||
|
/** Opened from a table, the page offers the way back to it and carries it to its cross-links. */
|
||||||
|
const room = $derived((page.url.searchParams.get('room') ?? '').toUpperCase());
|
||||||
|
const withRoom = $derived((path: string) => (room ? `${path}?room=${room}` : path));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -8,9 +13,9 @@
|
|||||||
|
|
||||||
<main class="guide">
|
<main class="guide">
|
||||||
<header>
|
<header>
|
||||||
<a class="back" href="/">Back to the hall</a> · <a class="back" href="/rules">The rules</a>
|
{#if room}<a class="back" href={`/join/${room}`}>Back to your game</a> · {/if}<a class="back" href="/">Back to the hall</a> · <a class="back" href={withRoom('/rules')}>The rules</a>
|
||||||
<h1>How to play</h1>
|
<h1>How to play</h1>
|
||||||
<p class="lede">A walk through the page, from the hall to a first game. The game's own rules are on <a href="/rules">the rules page</a>.</p>
|
<p class="lede">A walk through the page, from the hall to a first game. The game's own rules are on <a href={withRoom('/rules')}>the rules page</a>.</p>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="#hall">The hall</a> · <a href="#table">The table</a> · <a href="#board">The board</a> · <a href="#capture">A capture</a> · <a href="#gallery">The Peanut Gallery and table talk</a> ·
|
<a href="#hall">The hall</a> · <a href="#table">The table</a> · <a href="#board">The board</a> · <a href="#capture">A capture</a> · <a href="#gallery">The Peanut Gallery and table talk</a> ·
|
||||||
<a href="#first">A first game</a>
|
<a href="#first">A first game</a>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<button type="button" class="quiet" onclick={() => (prefsOpen = true)}>Preferences</button>
|
<button type="button" class="quiet" onclick={() => (prefsOpen = true)}>Preferences</button>
|
||||||
<a class="quiet" href="/guide">How to play</a>
|
<a class="quiet" href={room ? `/guide?room=${roomId}` : '/guide'}>How to play</a>
|
||||||
<a class="quiet" href={room ? `/rules?room=${roomId}` : '/rules'}>Rules</a>
|
<a class="quiet" href={room ? `/rules?room=${roomId}` : '/rules'}>Rules</a>
|
||||||
{#if room}
|
{#if room}
|
||||||
<button type="button" class="quiet" title="Something behaved unexpectedly? Tell the keeper." onclick={() => (reporting = true)}>Report</button>
|
<button type="button" class="quiet" title="Something behaved unexpectedly? Tell the keeper." onclick={() => (reporting = true)}>Report</button>
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<footer class="colophon">
|
<footer class="colophon">
|
||||||
<p>Hnefatafl. <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>
|
<p>Hnefatafl. <a href={room ? `/rules?room=${roomId}` : '/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>
|
</footer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
/** Opened from a table, the page offers the way back to it. */
|
/** Opened from a table, the page offers the way back to it. */
|
||||||
const room = $derived((page.url.searchParams.get('room') ?? '').toUpperCase());
|
const room = $derived((page.url.searchParams.get('room') ?? '').toUpperCase());
|
||||||
|
const withRoom = $derived((path: string) => (room ? `${path}?room=${room}` : path));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
|
|
||||||
<main class="rules">
|
<main class="rules">
|
||||||
<header>
|
<header>
|
||||||
{#if room}<a class="back" href={`/join/${room}`}>Back to your game</a> · {/if}<a class="back" href="/">Back to the hall</a> · <a class="back" href="/guide">How to play</a>
|
{#if room}<a class="back" href={`/join/${room}`}>Back to your game</a> · {/if}<a class="back" href="/">Back to the hall</a> · <a class="back" href={withRoom('/guide')}>How to play</a>
|
||||||
<h1>The rules</h1>
|
<h1>The rules</h1>
|
||||||
<p class="lede">
|
<p class="lede">
|
||||||
Hnefatafl is the tafl family's best-known member: a Viking board game played across Scandinavia and the North Atlantic from the fourth century until chess displaced it. No
|
Hnefatafl is the tafl family's best-known member: a Viking board game played across Scandinavia and the North Atlantic from the fourth century until chess displaced it. No
|
||||||
|
|||||||
Reference in New Issue
Block a user