121 lines
4 KiB
JavaScript
121 lines
4 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";
|
|
|
|
import {
|
|
collectPageFailures,
|
|
prepareStillPage,
|
|
resolveChrome,
|
|
routeForFile,
|
|
startPublicServer,
|
|
waitForStableVisual,
|
|
} from "./capture-stable.mjs";
|
|
|
|
const here = path.dirname(url.fileURLToPath(import.meta.url));
|
|
const root = path.resolve(here, "..");
|
|
const shots = path.join(root, "src", "assets", "shots");
|
|
const publicRoot = path.join(root, "public");
|
|
|
|
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 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 server = await startPublicServer(publicRoot);
|
|
let browser;
|
|
|
|
try {
|
|
browser = await puppeteer.launch({
|
|
executablePath: resolveChrome(),
|
|
headless: "new",
|
|
args: [
|
|
"--no-sandbox",
|
|
"--use-angle=swiftshader",
|
|
"--enable-unsafe-swiftshader",
|
|
"--force-device-scale-factor=1",
|
|
],
|
|
defaultViewport: { width: 1440, height: 900, deviceScaleFactor: 1 },
|
|
});
|
|
|
|
for (const job of selectedJobs) {
|
|
const page = await browser.newPage();
|
|
const failures = collectPageFailures(page, server.origin);
|
|
try {
|
|
await prepareStillPage(page);
|
|
const file = path.join(publicRoot, "work", job.slug, "index.html");
|
|
const response = await page.goto(server.origin + routeForFile(publicRoot, file), {
|
|
waitUntil: "domcontentloaded",
|
|
timeout: 30000,
|
|
});
|
|
if (!response?.ok()) throw new Error(`${job.slug} returned ${response?.status() ?? "no response"}`);
|
|
await waitForStableVisual(page, { settleMs: job.settle });
|
|
if (failures.length) throw new Error(failures.join(" · "));
|
|
|
|
const main = path.join(shots, `work-${job.slug}.png`);
|
|
await page.screenshot({ path: main });
|
|
await page.screenshot({ path: path.join(publicRoot, "work", job.slug, "og.png") });
|
|
|
|
await page.evaluate((selector) => {
|
|
document.querySelector(selector)?.scrollIntoView({ behavior: "auto", block: "start" });
|
|
}, job.second.scrollTo);
|
|
await new Promise((resolve) => setTimeout(resolve, job.second.wait));
|
|
|
|
const alt = path.join(shots, `work-${job.slug}-2.png`);
|
|
await page.screenshot({ path: alt });
|
|
const kb = (filePath) => Math.round(fs.statSync(filePath).size / 1024);
|
|
console.log(`${job.slug}: 본컷 ${kb(main)}KB · 2컷 ${kb(alt)}KB`);
|
|
} finally {
|
|
await page.close();
|
|
}
|
|
}
|
|
} finally {
|
|
if (browser) await browser.close();
|
|
await server.close();
|
|
}
|
|
|
|
console.log("정착 상태 촬영 완료");
|