#!/usr/bin/env node /** * Generates the baked nine-slice texture used by Card variant="textured". * * This file is the source of truth for the procedural visual. It deliberately * lives outside runtime code: edit the parameters below, run this script, then * visually compare the generated asset before committing it. */ import { chromium } from "@playwright/test"; import { mkdir } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const OUTPUT_DIRECTORY = path.resolve(HERE, "../../src/shared/ui/assets"); const DPR = 2; // Approved texture parameters, archived from the former runtime SVG filter. const THRESHOLD_BIAS = 0.302; const SLOPE = 8; const FREQUENCY = 0.999; const OCTAVES = 3; const SEED = 5315; const TEXTURES = [ { filename: "card-texture.png", color: "white", cardSize: 640, outset: 96, blur: 66, innerBand: 112, }, { filename: "card-texture-dark.png", color: "#171b21", cardSize: 640, outset: 96, blur: 66, innerBand: 112, }, { filename: "card-texture-compact.png", color: "white", cardSize: 320, outset: 24, blur: 24, innerBand: 44, }, { filename: "card-texture-dark-compact.png", color: "#171b21", cardSize: 320, outset: 24, blur: 24, innerBand: 44, }, ]; await mkdir(OUTPUT_DIRECTORY, { recursive: true }); const browser = await chromium.launch(); try { for (const texture of TEXTURES) { const captureSize = texture.cardSize + texture.outset * 2; const dilate = Math.round(texture.blur * 0.85); const output = path.join(OUTPUT_DIRECTORY, texture.filename); const page = await browser.newPage({ deviceScaleFactor: DPR, viewport: { height: captureSize, width: captureSize }, }); await page.setContent(`
`); await page.locator("#stage").screenshot({ omitBackground: true, path: output, }); await page.close(); console.log(`Generated ${output}`); console.log(`Asset: ${captureSize * DPR}×${captureSize * DPR}px @${DPR}x`); console.log( `Runtime slice: ${(texture.outset + texture.innerBand) * DPR}px; outset: ${texture.outset}px`, ); } } finally { await browser.close(); }