// Run with npm run art. Both illustrations originate in the live board component. import { readFile, writeFile, unlink } from 'node:fs/promises'; import { compile } from 'svelte/compiler'; import { render } from 'svelte/server'; import sharp from 'sharp'; import { createGame } from '../src/lib/game/index.ts'; const root = new URL('../', import.meta.url); const source = await readFile(new URL('src/lib/components/BoardArtwork.svelte', root), 'utf8'); const compiled = compile(source.replace("'$lib/game'", "'../src/lib/game/index.ts'"), { generate: 'server', css: 'injected', filename: 'BoardArtwork.svelte' }); const temporary = new URL(`.board-art-${process.pid}.mjs`, import.meta.url); try { await writeFile(temporary, compiled.js.code); const { default: BoardArtwork } = await import(temporary.href); for (const set of ['copenhagen', 'tablut']) { const result = render(BoardArtwork, { props: { g: createGame({ a: 'Defenders', b: 'Attackers' }, 0, undefined, { set }), decorative: true } }); let svg = result.body.replace(//g, ''); const css = [...result.head.matchAll(/]*>([\s\S]*?)<\/style>/g)].map(m => m[1]).join('\n'); svg = svg.replace(/(]*>)/, '$1'); // Resolve custom properties for standalone SVG rasterizers, which do not // consistently implement CSS variable inheritance. Browsers use the component. const paletteSource = source.slice(source.indexOf('/* Copenhagen:')); const base = paletteSource.slice(0, paletteSource.indexOf('/* Tablut:')); const overrides = set === 'tablut' ? paletteSource.slice(paletteSource.indexOf('/* Tablut:'), paletteSource.indexOf('\n\t.artwork {')) : ''; const values = Object.fromEntries([...`${base}\n${overrides}`.matchAll(/(--[\w-]+):\s*([^;]+);/g)].map(m => [m[1], m[2]])); values['--piece-rim'] = values['--defender-rim']; values['--font'] = 'Georgia, serif'; values['--frost'] = '#9ccbdd'; svg = svg.replace(/:where\((\.[\w-]+)\)/g, '$1'); svg = svg.replace(/var\((--[\w-]+)\)/g, (_, key) => values[key] ?? 'inherit'); svg = svg.replace('', ['attacker', 'defender', 'king'].map(kind => `.piece.${kind} .base{fill:${values[`--${kind}-rim`]}}.piece.${kind} .body,.piece.${kind} .turned{stroke:${values[`--${kind}-rim`]}}`).join('') + ''); await writeFile(new URL(`static/board-${set}.svg`, root), svg); } const board = await readFile(new URL('static/board-copenhagen.svg', root), 'utf8'); const image = Buffer.from(board).toString('base64'); const share = ` Hnefatafl The Viking board game. One king, one throne, four corners, and a ring of attackers closing in. Play a housecarl or a friend. Copenhagen or Linnaeus’s Tablut. tafl.kestrelsnest.social Free · no accounts `; await writeFile(new URL('docs/og-card.svg', root), share); await sharp(Buffer.from(share)).png().toFile(new URL('static/og.png', root).pathname); console.log('Generated both board illustrations and static/og.png (1200 × 630).'); } finally { await unlink(temporary).catch(() => {}); }