// Contact sheets for visual review of a previously generated static theme matrix. // node scripts/theme-matrix-gallery.mjs [matrixDir] [mode] [width] import { readFile, mkdir, writeFile } from 'node:fs/promises'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { join } from 'node:path'; import { chromium } from 'playwright'; const root = fileURLToPath(new URL('../', import.meta.url)); const matrix = process.argv[2] ?? join(root, '../../verification/theme-matrix/current-full'); const mode = process.argv[3] ?? 'dark'; const width = Number(process.argv[4] ?? 1280); const rows = JSON.parse(await readFile(join(matrix, 'results.json'), 'utf8')).results.filter(r => r.mode === mode && r.width === width && r.shot); const cells = [...new Set(rows.map(r => r.cell))]; const themes = [...new Set(rows.map(r => r.theme))]; const out = join(matrix, 'gallery'); await mkdir(out, { recursive: true }); const browser = await chromium.launch({ headless: true }); let sheet = 0; try { const page = await browser.newPage({ viewport: { width: 1700, height: 1000 }, deviceScaleFactor: 1 }); for (let ci = 0; ci < cells.length; ci += 4) { for (let ti = 0; ti < themes.length; ti += 7) { const subsetCells = cells.slice(ci, ci + 4), subsetThemes = themes.slice(ti, ti + 7); const cards = subsetCells.flatMap(cell => subsetThemes.map(theme => { const r = rows.find(x => x.cell === cell && x.theme === theme); return `
${cell} · ${theme}
`; })).join(''); const html = `

${mode} ${width} · pages ${ci+1}-${Math.min(ci+4,cells.length)} · themes ${ti+1}-${Math.min(ti+7,themes.length)}

${cards}
`; const file = join(out, `sheet-${String(++sheet).padStart(2, '0')}.html`); await writeFile(file, html); await page.goto(pathToFileURL(file).href); await page.locator('img').first().waitFor(); await page.screenshot({ path: file.replace(/\.html$/, '.png'), fullPage: true }); } } } finally { await browser.close(); } console.log(`${cells.length} page/layer families × ${themes.length} themes at ${mode}/${width}; ${sheet} contact sheets in ${out}`);