designpaca/apps/site/tools/capture-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

157 lines
6 KiB
JavaScript

import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, "..");
const chromePath = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
const shotsDir = path.join(rootDir, "src", "assets", "shots");
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;
}
if (!fs.existsSync(shotsDir)) {
fs.mkdirSync(shotsDir, { recursive: true });
}
const targets = [
{
url: path.join(rootDir, "public", "work", "thursday-flowers", "index.html"),
out: path.join(shotsDir, "work-thursday-flowers.png"),
altUrl: path.join(rootDir, "public", "work", "thursday-flowers", "index.html"),
altOut: path.join(shotsDir, "work-thursday-flowers-2.png"),
},
{
url: path.join(rootDir, "public", "work", "muwol-coffee", "index.html"),
out: path.join(shotsDir, "work-muwol-coffee.png"),
altUrl: path.join(rootDir, "public", "work", "muwol-coffee", "index.html"),
altOut: path.join(shotsDir, "work-muwol-coffee-2.png"),
},
{
url: path.join(rootDir, "public", "work", "pulsegate", "index.html"),
out: path.join(shotsDir, "work-pulsegate.png"),
altUrl: path.join(rootDir, "public", "work", "pulsegate", "index.html"),
altOut: path.join(shotsDir, "work-pulsegate-2.png"),
},
{
url: path.join(rootDir, "public", "work", "form-architecture", "index.html"),
out: path.join(shotsDir, "work-form-architecture.png"),
altUrl: path.join(rootDir, "public", "work", "form-architecture", "index.html"),
altOut: path.join(shotsDir, "work-form-architecture-2.png"),
},
{
url: path.join(rootDir, "public", "work", "overtone-sound", "index.html"),
out: path.join(shotsDir, "work-overtone-sound.png"),
altUrl: path.join(rootDir, "public", "work", "overtone-sound", "index.html"),
altOut: path.join(shotsDir, "work-overtone-sound-2.png"),
},
{
url: path.join(rootDir, "public", "work", "font-foundry", "index.html"),
out: path.join(shotsDir, "work-font-foundry.png"),
altUrl: path.join(rootDir, "public", "work", "font-foundry", "index.html"),
altOut: path.join(shotsDir, "work-font-foundry-2.png"),
},
{
url: path.join(rootDir, "public", "work", "lumina-optics", "index.html"),
out: path.join(shotsDir, "work-lumina-optics.png"),
altUrl: path.join(rootDir, "public", "work", "lumina-optics", "index.html"),
altOut: path.join(shotsDir, "work-lumina-optics-2.png"),
webgl: true, // three.js 캔버스 — swiftshader 필요
},
{
url: path.join(rootDir, "public", "work", "hyang-incense", "index.html"),
out: path.join(shotsDir, "work-hyang-incense.png"),
altUrl: path.join(rootDir, "public", "work", "hyang-incense", "index.html"),
altOut: path.join(shotsDir, "work-hyang-incense-2.png"),
},
{
url: path.join(rootDir, "public", "work", "synapse-bci", "index.html"),
out: path.join(shotsDir, "work-synapse-bci.png"),
altUrl: path.join(rootDir, "public", "work", "synapse-bci", "index.html"),
altOut: path.join(shotsDir, "work-synapse-bci-2.png"),
},
{
url: path.join(rootDir, "public", "work", "baekje-celadon", "index.html"),
out: path.join(shotsDir, "work-baekje-celadon.png"),
altUrl: path.join(rootDir, "public", "work", "baekje-celadon", "index.html"),
altOut: path.join(shotsDir, "work-baekje-celadon-2.png"),
},
{
url: path.join(rootDir, "public", "work", "orbit-aerospace", "index.html"),
out: path.join(shotsDir, "work-orbit-aerospace.png"),
altUrl: path.join(rootDir, "public", "work", "orbit-aerospace", "index.html"),
altOut: path.join(shotsDir, "work-orbit-aerospace-2.png"),
},
{
url: path.join(rootDir, "public", "work", "gaon-lms", "index.html"),
out: path.join(shotsDir, "work-gaon-lms.png"),
altUrl: path.join(rootDir, "public", "work", "gaon-lms", "index.html"),
altOut: path.join(shotsDir, "work-gaon-lms-2.png"),
},
{
url: path.join(rootDir, "public", "work", "dure-enrollment", "index.html"),
out: path.join(shotsDir, "work-dure-enrollment.png"),
altUrl: path.join(rootDir, "public", "work", "dure-enrollment", "index.html"),
altOut: path.join(shotsDir, "work-dure-enrollment-2.png"),
},
];
const requested = requestedSlugs(process.argv);
const slugOf = (target) => path.basename(path.dirname(target.url));
const known = new Set(targets.map(slugOf));
const unknown = [...requested].filter((slug) => !known.has(slug));
if (unknown.length) throw new Error(`Unknown showcase slug(s): ${unknown.join(", ")}`);
const selectedTargets = requested.size ? targets.filter((target) => requested.has(slugOf(target))) : targets;
console.log("📸 Capturing high-resolution screenshots...");
const shot = (t, url, out) => {
const flags = t.webgl
? ["--no-sandbox", "--use-angle=swiftshader"] // --disable-gpu 는 WebGL 을 죽인다
: ["--disable-gpu", "--no-sandbox"];
execFileSync(chromePath, [
"--headless=new",
"--hide-scrollbars",
...flags,
"--force-device-scale-factor=1",
"--window-size=1440,900",
`--screenshot=${out}`,
`file://${url}`,
]);
};
for (const t of selectedTargets) {
console.log(`- Capturing ${path.basename(t.out)}`);
try {
shot(t, t.url, t.out);
} catch (e) {
console.error(`Error capturing ${t.out}:`, e.message);
}
if (t.altOut) {
console.log(`- Capturing ${path.basename(t.altOut)}`);
try {
shot(t, t.altUrl, t.altOut);
} catch (e) {
console.error(`Error capturing ${t.altOut}:`, e.message);
}
}
}
console.log("✅ All screenshots captured successfully!");