Files

115 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
/**
* skins-montage — regenerate the root README "skins at a glance" collage.
*
* Reads the market catalog (market/dist/manifest.js, generated by market-build),
* lays every skin's light preview out in a 6-column grid (last row centered),
* and screenshots it into docs/images/skins-montage.png. Re-run after adding,
* removing, or renaming a skin, and update the README skin count in the same
* change.
*
* Requires playwright + its chromium browser (see scripts/capture-previews).
*
* Usage:
* node scripts/skins-montage.mjs
*/
import { readFileSync, writeFileSync, mkdtempSync, existsSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve, dirname } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { chromium } from 'playwright'
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const manifestPath = join(root, 'market/dist/manifest.js')
const out = join(root, 'docs/images/skins-montage.png')
// --- catalog ---------------------------------------------------------------
let text
try {
text = readFileSync(manifestPath, 'utf8')
} catch {
console.error('market/dist/manifest.js not found; run `pnpm market:build` first')
process.exit(1)
}
const manifest = JSON.parse(
text
.replace(/^\/\*[\s\S]*?\*\/\s*/, '') // generated header comment
.replace(/^window\.SKIN_MANIFEST\s*=\s*/, '')
.replace(/;\s*$/, ''),
)
const skins = manifest.skins ?? []
if (!skins.length) {
console.error('market manifest has no skins; run `pnpm market:build` first')
process.exit(1)
}
// --- page ------------------------------------------------------------------
const CELL = 200 // CSS px; deviceScaleFactor 2 makes the output 2x
const GAP = 6
const COLS = 6
for (const skin of skins) {
if (!existsSync(join(root, 'market/dist', skin.preview?.light ?? ''))) {
console.error(`skin ${skin.id}: preview/light image missing; run node scripts/capture-previews ${skin.id}`)
process.exit(1)
}
}
const cells = skins
.map((skin) => {
const src = pathToFileURL(join(root, 'market/dist', skin.preview.light)).href
const label = `${skin.name} · ${skin.nameEn}`
return `<figure class="cell"><img src="${src}" alt="${skin.id}"><figcaption>${label}</figcaption></figure>`
})
.join('\n')
const html = `<!doctype html>
<html><head><meta charset="utf-8"><style>
body { margin: 0; background: #fff; }
.grid {
display: flex; flex-wrap: wrap; justify-content: center; gap: ${GAP}px;
width: ${COLS * CELL + (COLS - 1) * GAP}px; padding: 0;
}
.cell { margin: 0; width: ${CELL}px; }
.cell img {
display: block; width: ${CELL}px; height: ${CELL * 9 / 16}px;
object-fit: cover; object-position: top; background: #e3e8ee;
}
figcaption {
font: 11px/1.45 -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Segoe UI', sans-serif;
color: #4a5560; text-align: center; margin-top: 2px;
height: 32px; overflow: hidden; white-space: normal;
}
</style></head><body><div class="grid">${cells}</div></body></html>`
const tmp = mkdtempSync(join(tmpdir(), 'skins-montage-'))
const pagePath = join(tmp, 'montage.html')
writeFileSync(pagePath, html)
// --- capture ---------------------------------------------------------------
const browser = await chromium.launch()
const page = await browser.newPage({
viewport: { width: COLS * CELL + (COLS - 1) * GAP + 40, height: 600 },
deviceScaleFactor: 2,
})
page.on('console', (m) => {
if (m.type() === 'error') console.error('[page]', m.text())
})
await page.goto(pathToFileURL(pagePath).href)
await page.waitForLoadState('networkidle')
const report = await page.evaluate(() => ({
imgs: [...document.images].map((img) => ({ src: img.src, ok: img.complete && img.naturalWidth > 0 })),
scrollW: document.documentElement.scrollWidth,
}))
for (const img of report.imgs) {
if (!img.ok) throw new Error('image failed to load: ' + img.src)
}
if (report.scrollW > 1300) throw new Error(`grid overflow: ${report.scrollW}px`)
const box = await page.locator('.grid').boundingBox()
await page.screenshot({ path: out, clip: { x: box.x, y: box.y, width: box.width, height: box.height } })
await browser.close()
console.log(`wrote ${out} (${skins.length} skins)`)