diff --git a/docs/artwork.md b/docs/artwork.md new file mode 100644 index 0000000..a7856e2 --- /dev/null +++ b/docs/artwork.md @@ -0,0 +1,19 @@ +# Board artwork + +`src/lib/components/BoardArtwork.svelte` owns both palettes, surface filters, +wear, grid, inlays and counters. `Board.svelte` supplies interactive state; +`Hall.svelte` supplies an opening position from the game engine. Update the +shared component to change either illustration and the game together. + +After editing the artwork, run: + +```sh +npm run art +``` + +This renders the same Svelte component into `static/board-copenhagen.svg` and +`static/board-tablut.svg`, then regenerates `docs/og-card.svg` and the 1200 × 630 +`static/og.png`. Preview the share composition in `docs/og-card.html`. +The share composition and copy live in `docs/render-art.mjs`. Commit generated +assets with source changes. The export uses Sharp as a development dependency; +it does not add any image-processing code to the shipped app. diff --git a/docs/og-card.html b/docs/og-card.html index b8bfcd2..2698be6 100644 --- a/docs/og-card.html +++ b/docs/og-card.html @@ -1,33 +1,13 @@ -
The Viking board game. One king, one throne, four corners, and a ring of attackers closing in. Play a housecarl or a friend, by Copenhagen rules or Linnaeus's Tablut.
-tafl.kestrelsnest.social · free, no accounts
-{room.error}
{/if} -{liveText}
@@ -436,51 +286,6 @@ padding: 0 var(--gutter); max-width: 1280px; margin: 0 auto; - /* Copenhagen: a crafted oak board, polished counters and brass inlay. */ - --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); - --coord: #302216; - --square: rgba(255, 240, 210, 0.08); - --square-edge: rgba(255, 250, 235, 0.11); - --throne: rgba(240, 165, 58, 0.35); - --corner: rgba(109, 52, 29, 0.22); - --attacker: #2a2320; - --attacker-rim: #211a15; - --defender: #ece4d0; - --defender-rim: #b9ab8b; - --king: #f0d58a; - --king-rim: #a67c1a; - --crown: #6b4a0f; - } - - /* Tablut: an excavated-object aesthetic, with limestone and aged counters. */ - .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-line: rgba(36, 32, 27, 0.45); - --coord: #37362c; - --square: rgba(255, 250, 235, 0.05); - --square-edge: rgba(255, 250, 235, 0.09); - --throne: rgba(120, 100, 70, 0.4); - --corner: transparent; - --attacker: #3d3530; - --attacker-rim: #29281f; - --defender: #cfc4ad; - --defender-rim: #8c8069; - --king: #c9b27a; - --king-rim: #7d6636; - --crown: #5a4a2a; } @media (max-width: 860px) { @@ -540,132 +345,11 @@ flex-wrap: wrap; } - .calm .piece, - .calm .square, - .calm .wood { - transition: none; - } - .frame { max-width: 620px; margin: 0 auto; } - svg { - width: 100%; - height: auto; - display: block; - } - - .wood { - stroke: var(--surface-dark); - stroke-width: 1.4; - } - - .stain { - fill: #fff; - pointer-events: none; - } - - .chip { - fill: rgba(28, 24, 20, 0.42); - stroke: rgba(230, 222, 205, 0.18); - stroke-width: 0.6; - pointer-events: none; - } - - .crack { - fill: none; - stroke: rgba(43, 39, 29, 0.48); - stroke-width: 0.5; - stroke-linecap: round; - pointer-events: none; - } - - .coord { - fill: var(--coord); - font-family: var(--font); - font-size: 9px; - font-weight: 600; - pointer-events: none; - } - - .square { - fill: var(--square); - stroke: var(--wood-line); - stroke-width: 0.65; - cursor: default; - transition: fill 0.3s ease; - } - - .square.edge { - fill: var(--square-edge); - } - - .square.throne { - fill: var(--throne); - } - - .square.corner { - fill: var(--corner); - } - - .square.last { - fill: rgba(140, 200, 224, 0.28); - } - - .square.target { - cursor: pointer; - } - - .square.fallen { - fill: rgba(224, 101, 92, 0.45); - } - - .square:focus-visible { - outline: none; - stroke: var(--frost); - stroke-width: 2.5; - } - - .hint { - fill: var(--frost); - opacity: 0.85; - } - - .piece { - transition: - transform 0.36s cubic-bezier(0.2, 0.7, 0.2, 1), - opacity 0.3s ease; - } - - .piece.dying { - opacity: 0; - } - - .board-rim { fill: none; stroke: var(--rim-light); stroke-width: 0.7; opacity: 0.65; pointer-events: none; } - .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 { --piece-rim: var(--defender-rim); } - .piece.attacker { --piece-rim: var(--attacker-rim); } - .piece.king { --piece-rim: var(--king-rim); } - .piece .shadow { fill: #16130e; opacity: 0.28; } - .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; } - .bevel-dark { fill: none; stroke: #211d16; stroke-width: 0.8; opacity: 0.3; stroke-linecap: round; } - .turned { fill: none; stroke: var(--piece-rim); stroke-width: 0.45; opacity: 0.35; } - .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; } - .board[data-set='tablut'] .turned { opacity: 0.12; } - .piece .crown { fill: var(--crown); stroke: var(--crown); stroke-width: 0.7; stroke-linejoin: round; } - .piece .crown-gleam { fill: #f8e5b2; stroke: #f8e5b2; stroke-width: 0.9; opacity: 0.7; } - .piece.selected .body { stroke: var(--frost); stroke-width: 2.5; } .how { max-width: 620px; diff --git a/src/lib/components/BoardArtwork.svelte b/src/lib/components/BoardArtwork.svelte new file mode 100644 index 0000000..b2a08db --- /dev/null +++ b/src/lib/components/BoardArtwork.svelte @@ -0,0 +1,348 @@ + + + diff --git a/src/lib/components/Hall.svelte b/src/lib/components/Hall.svelte index 3d46b6f..cd4af8f 100644 --- a/src/lib/components/Hall.svelte +++ b/src/lib/components/Hall.svelte @@ -11,7 +11,8 @@ import { unreadTalk } from '$lib/net/talk'; import type { RoomView } from '$lib/net/view'; import type { State } from '$lib/game'; - import { game } from '$lib/game'; + import { game, createGame } from '$lib/game'; + import BoardArtwork from './BoardArtwork.svelte'; let name = $state(playerName()); let code = $state(''); @@ -22,15 +23,7 @@ ); let prefsOpen = $state(false); - /** The hall's still of the opening position, on whichever board the selector names. */ - const still = $derived( - options.set === 'tablut' - ? { n: 9, wood: '#8d8577', line: 'rgba(40,34,28,0.5)', dark: '#3d3530', darkRim: '#1c1714', pale: '#cfc4ad', paleRim: '#8c8069', king: '#c9b27a', kingRim: '#7d6636', corners: false, - a: [[3,0],[4,0],[5,0],[4,1],[0,3],[8,3],[0,4],[1,4],[7,4],[8,4],[0,5],[8,5],[4,7],[3,8],[4,8],[5,8]], d: [[4,2],[4,3],[2,4],[3,4],[5,4],[6,4],[4,5],[4,6]] } - : { n: 11, wood: '#8a5a2b', line: 'rgba(30,16,6,0.5)', dark: '#2a2320', darkRim: '#0d0a08', pale: '#ece4d0', paleRim: '#b9ab8b', king: '#f0d58a', kingRim: '#a67c1a', corners: true, - a: [[3,0],[4,0],[5,0],[6,0],[7,0],[5,1],[0,3],[10,3],[0,4],[10,4],[0,5],[1,5],[9,5],[10,5],[0,6],[10,6],[0,7],[10,7],[5,9],[3,10],[4,10],[5,10],[6,10],[7,10]], d: [[5,3],[4,4],[5,4],[6,4],[3,5],[4,5],[6,5],[7,5],[4,6],[5,6],[6,6],[5,7]] } - ); - const mid = $derived((still.n - 1) / 2); + const opening = $derived(createGame({ preview: 'Defenders', opponent: 'Attackers' }, 0, undefined, options)); let busy = $state(false); let error = $state(''); let seats = $state(allSeats()); @@ -217,21 +210,7 @@