194 lines
7.7 KiB
JavaScript
194 lines
7.7 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import puppeteer from "puppeteer-core";
|
|
|
|
import {
|
|
collectPageFailures,
|
|
prepareStillPage,
|
|
resolveChrome,
|
|
routeForFile,
|
|
startPublicServer,
|
|
waitForStableVisual,
|
|
} from "./capture-stable.mjs";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const rootDir = path.resolve(__dirname, "..");
|
|
const shotsDir = path.join(rootDir, "src", "assets", "shots");
|
|
const publicRoot = path.join(rootDir, "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;
|
|
}
|
|
|
|
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"),
|
|
},
|
|
{
|
|
url: path.join(rootDir, "public", "work", "cheongon-care", "index.html"),
|
|
out: path.join(shotsDir, "work-cheongon-care.png"),
|
|
altUrl: path.join(rootDir, "public", "work", "cheongon-care", "protocol", "index.html"),
|
|
altOut: path.join(shotsDir, "work-cheongon-care-2.png"),
|
|
},
|
|
{
|
|
url: path.join(rootDir, "public", "work", "cask-archive", "index.html"),
|
|
out: path.join(shotsDir, "work-cask-archive.png"),
|
|
altUrl: path.join(rootDir, "public", "work", "cask-archive", "docent", "index.html"),
|
|
altOut: path.join(shotsDir, "work-cask-archive-2.png"),
|
|
},
|
|
{
|
|
url: path.join(rootDir, "public", "work", "materia-interior", "index.html"),
|
|
out: path.join(shotsDir, "work-materia-interior.png"),
|
|
altUrl: path.join(rootDir, "public", "work", "materia-interior", "projects", "hannam-duplex", "index.html"),
|
|
altOut: path.join(shotsDir, "work-materia-interior-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 settled, high-resolution screenshots...");
|
|
|
|
const server = await startPublicServer(publicRoot);
|
|
let browser;
|
|
|
|
async function shot(file, output) {
|
|
const page = await browser.newPage();
|
|
const failures = collectPageFailures(page, server.origin);
|
|
try {
|
|
await prepareStillPage(page);
|
|
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
|
|
const route = routeForFile(publicRoot, file);
|
|
const response = await page.goto(server.origin + route, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
if (!response?.ok()) throw new Error(`${route} returned ${response?.status() ?? "no response"}`);
|
|
await waitForStableVisual(page);
|
|
if (failures.length) throw new Error(failures.join(" · "));
|
|
await page.screenshot({ path: output, type: "png", fullPage: false });
|
|
const size = Math.round(fs.statSync(output).size / 1024);
|
|
console.log(`CAPTURE ${path.basename(output)} (${size}KB)`);
|
|
} finally {
|
|
await page.close();
|
|
}
|
|
}
|
|
|
|
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 target of selectedTargets) {
|
|
await shot(target.url, target.out);
|
|
if (target.altOut) await shot(target.altUrl, target.altOut);
|
|
}
|
|
} finally {
|
|
if (browser) await browser.close();
|
|
await server.close();
|
|
}
|
|
|
|
console.log("✅ All requested screenshots captured after visual settling.");
|