designpaca/apps/site/tools/capture-new-shots.mjs
Yun Chan 2b7fa32b5b
All checks were successful
ci / build (push) Successful in 51s
release / release (push) Successful in 2m34s
feat: 한국어 우선 쇼케이스 정리
2026-08-26 18:14:41 +09:00

109 lines
3.5 KiB
JavaScript

// 신규 쇼케이스 3종의 카드 컷 촬영 — 본컷(히어로)과 -2컷(그 페이지의 두 번째 주장).
// WebGL 이 있는 페이지가 있으므로 --disable-gpu 를 쓰지 않고 swiftshader 로 소프트 렌더한다.
//
// 사용: node tools/capture-new-shots.mjs
import puppeteer from "puppeteer-core";
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
const here = path.dirname(url.fileURLToPath(import.meta.url));
const root = path.resolve(here, "..");
const shots = path.join(root, "src", "assets", "shots");
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function requestedSlugs(argv) {
const slugs = new Set();
for (let index = 2; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === "--slug") {
const slug = argv[index + 1];
if (!slug) throw new Error("--slug requires a showcase slug");
slugs.add(slug);
index += 1;
} else if (argument.startsWith("--slug=")) {
slugs.add(argument.slice("--slug=".length));
} else {
throw new Error(`Unknown argument: ${argument}`);
}
}
return slugs;
}
const CHROME = [
"C:/Program Files/Google/Chrome/Application/chrome.exe",
"C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",
process.env.CHROME_PATH,
].filter(Boolean).find((p) => fs.existsSync(p));
if (!CHROME) {
console.error("크롬을 찾지 못했다.");
process.exit(2);
}
const JOBS = [
{
slug: "rfs-cfd",
// 유체가 정상류에 들 때까지 기다린다. 갓 켠 화면을 찍으면 빈 판이 나온다
settle: 5200,
second: { scrollTo: "#solver", wait: 700 },
},
{
slug: "twentyoz-corp",
settle: 700,
// 두 번째 컷은 '한 일' 원장 — 이 사이트의 본체다
second: { scrollTo: "#ledger", wait: 500 },
},
{
slug: "saebyeok-cheonggwa",
settle: 900,
second: { scrollTo: "#season", wait: 500 },
},
];
const requested = requestedSlugs(process.argv);
const known = new Set(JOBS.map((job) => job.slug));
const unknown = [...requested].filter((slug) => !known.has(slug));
if (unknown.length) throw new Error(`Unknown showcase slug(s): ${unknown.join(", ")}`);
const selectedJobs = requested.size ? JOBS.filter((job) => requested.has(job.slug)) : JOBS;
const browser = await puppeteer.launch({
executablePath: CHROME,
headless: "new",
args: [
"--no-sandbox",
"--use-angle=swiftshader",
"--enable-unsafe-swiftshader",
"--force-device-scale-factor=1",
],
defaultViewport: { width: 1440, height: 900 },
});
for (const job of selectedJobs) {
const page = await browser.newPage();
await page.setCacheEnabled(false);
const file = path.join(root, "public/work", job.slug, "index.html");
await page.goto(url.pathToFileURL(file).href, { waitUntil: "networkidle0" });
await page.evaluate(() => document.fonts.ready);
await sleep(job.settle);
const main = path.join(shots, `work-${job.slug}.png`);
await page.screenshot({ path: main });
await page.screenshot({ path: path.join(root, "public/work", job.slug, "og.png") });
await page.evaluate((sel) => {
document.querySelector(sel)?.scrollIntoView({ behavior: "instant", block: "start" });
}, job.second.scrollTo);
await sleep(job.second.wait);
const alt = path.join(shots, `work-${job.slug}-2.png`);
await page.screenshot({ path: alt });
await page.close();
const kb = (p) => Math.round(fs.statSync(p).size / 1024);
console.log(`${job.slug}: 본컷 ${kb(main)}KB · 2컷 ${kb(alt)}KB`);
}
await browser.close();
console.log("촬영 완료");