feat(site): redesign promotion experience

This commit is contained in:
Yun Chan 2026-08-30 17:52:54 +09:00
parent 6930906583
commit 13dd160c16
39 changed files with 5764 additions and 5738 deletions

View file

@ -0,0 +1,53 @@
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import puppeteer from "puppeteer-core";
const here = path.dirname(url.fileURLToPath(import.meta.url));
const siteRoot = path.resolve(here, "..");
const origin = process.env.DESIGNPACA_PREVIEW_ORIGIN || "http://localhost:4321";
const chromeCandidates = [
process.env.CHROME_PATH,
"C:/Program Files/Google/Chrome/Application/chrome.exe",
"C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",
"/usr/bin/google-chrome",
"/usr/bin/chromium",
];
const executablePath = chromeCandidates.find((candidate) => candidate && fs.existsSync(candidate));
if (!executablePath) {
throw new Error("Chrome/Chromium executable not found. Set CHROME_PATH before capturing site OG images.");
}
const captures = [
{ path: "/", output: "src/assets/og-designpaca-ko.png" },
{ path: "/en/", output: "src/assets/og-designpaca-en.png" },
];
const browser = await puppeteer.launch({
executablePath,
headless: true,
args: ["--force-color-profile=srgb", "--no-first-run", "--no-default-browser-check"],
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1200, height: 630, deviceScaleFactor: 1 });
await page.emulateMediaFeatures([{ name: "prefers-reduced-motion", value: "reduce" }]);
for (const capture of captures) {
const target = new URL(capture.path, origin).href;
const response = await page.goto(target, { waitUntil: "networkidle0", timeout: 30_000 });
if (!response?.ok()) throw new Error(`Failed to load ${target}: ${response?.status() ?? "no response"}`);
await page.evaluate(() => document.fonts.ready);
await page.screenshot({
path: path.join(siteRoot, capture.output),
type: "png",
captureBeyondViewport: false,
});
console.log(`${capture.output} · 1200x630`);
}
} finally {
await browser.close();
}

View file

@ -1,186 +0,0 @@
/**
* 방문자에게 보이는 한국어 본문을 존댓말로 바꾼다.
*
* 제목(h1·h2·name·rule·caught)은 건드리지 않는다 — 단정문이 카피로서 힘을 갖는 자리다.
* 바꾸는 것은 **설명하는 문장**과 **명령형**이다.
* 방문자에게 "~해라" 라고 말하는 화면은 무례하다.
*/
import fs from "node:fs";
const FILE = "src/i18n/content.ts";
/** 이 키의 값만 바꾼다. 나머지는 그대로 둔다 */
const TARGET_KEYS = new Set([
"lead", "note", "how", "problem", "use", "what", "does", "becomes", "made", "alt", "sample",
"why", "fixed", "caught", "caveat", "installNote", "description", "verdict", "caption",
]);
/** 순서가 중요하다. 긴 패턴을 먼저 잡지 않으면 짧은 것이 먼저 먹는다 */
const RULES = [
// ── 명령형 — 방문자에게 명령하지 않는다 ──
[/해라\./g, "하세요."], [/해라$/g, "하세요"],
[/봐라\./g, "보세요."], [/봐라$/g, "보세요"],
[/마라\./g, "마세요."], [/마라$/g, "마세요"],
[/써라\./g, "쓰세요."], [/써라$/g, "쓰세요"],
[/가라\./g, "가세요."], [/골라라/g, "고르세요"],
// ── 서술 ──
[/하지 않는다/g, "하지 않습니다"],
[/되지 않는다/g, "되지 않습니다"],
[/지 않는다/g, "지 않습니다"],
[/않는다/g, "않습니다"],
[/것이 아니다/g, "것이 아닙니다"],
[/아니다/g, "아닙니다"],
[/것이다/g, "것입니다"],
[/뿐이다/g, "뿐입니다"],
[/때문이다/g, "때문입니다"],
[/없어진다/g, "없어집니다"],
[/사라진다/g, "사라집니다"],
[/달라진다/g, "달라집니다"],
[/남는다/g, "남습니다"],
[/걸린다/g, "걸립니다"],
[/나온다/g, "나옵니다"],
[/들어간다/g, "들어갑니다"],
[/올라간다/g, "올라갑니다"],
[/떨어진다/g, "떨어집니다"],
[/무너진다/g, "무너집니다"],
[/바뀐다/g, "바뀝니다"],
[/이어진다/g, "이어집니다"],
[/생긴다/g, "생깁니다"],
[/보인다/g, "보입니다"],
[/읽힌다/g, "읽힙니다"],
[/잡힌다/g, "잡힙니다"],
[/쓰인다/g, "쓰입니다"],
[/맡긴다/g, "맡깁니다"],
[/만든다/g, "만듭니다"],
[/막는다/g, "막습니다"],
[/줄인다/g, "줄입니다"],
[/센다/g, "셉니다"],
[/짠다/g, "짭니다"],
[/뺀다/g, "뺍니다"],
[/둔다/g, "둡니다"],
[/한다/g, "합니다"],
[/된다/g, "됩니다"],
[/있다/g, "있습니다"],
[/없다/g, "없습니다"],
[/같다/g, "같습니다"],
[/좁다/g, "좁습니다"],
[/넓다/g, "넓습니다"],
[/맞다/g, "맞습니다"],
[/낫다/g, "낫습니다"],
[/멈춘다/g, "멈춥니다"],
[/이긴다/g, "이깁니다"],
[/모른다/g, "모릅니다"],
[/받는다/g, "받습니다"],
[/걸었다/g, "걸었습니다"],
[/바꿨다/g, "바꿨습니다"],
[/했다/g, "했습니다"],
[/였다/g, "였습니다"],
[/었다/g, "었습니다"],
[/았다/g, "았습니다"],
[/린다/g, "립니다"],
[/난다/g, "납니다"],
[/쓴다/g, "씁니다"],
[/본다/g, "봅니다"],
[/간다/g, "갑니다"],
[/온다/g, "옵니다"],
[/골라준다/g, "골라줍니다"],
[/고른다/g, "고릅니다"],
[/넘긴다/g, "넘깁니다"],
[/채운다/g, "채웁니다"],
[/압축한다/g, "압축합니다"],
[/판정한다/g, "판정합니다"],
[/정한다/g, "정합니다"],
[/확정한다/g, "확정합니다"],
[/구분한다/g, "구분합니다"],
[/합성한다/g, "합성합니다"],
[/내린다/g, "내립니다"],
[/열린다/g, "열립니다"],
[/붙는다/g, "붙습니다"],
[/넣는다/g, "넣습니다"],
[/놓는다/g, "놓습니다"],
[/찾는다/g, "찾습니다"],
[/맞춘다/g, "맞춥니다"],
[/갖춘다/g, "갖춥니다"],
[/낮춘다/g, "낮춥니다"],
[/높인다/g, "높입니다"],
[/모은다/g, "모읍니다"],
[/버린다/g, "버립니다"],
[/살린다/g, "살립니다"],
[/시킨다/g, "시킵니다"],
[/지킨다/g, "지킵니다"],
[/적는다/g, "적습니다"],
[/삼는다/g, "삼습니다"],
[/남긴다/g, "남깁니다"],
[/옮긴다/g, "옮깁니다"],
[/뒤집는다/g, "뒤집습니다"],
[/따른다/g, "따릅니다"],
[/부른다/g, "부릅니다"],
[/누른다/g, "누릅니다"],
[/자른다/g, "자릅니다"],
[/오른다/g, "오릅니다"],
[/미룬다/g, "미룹니다"],
[/켠다/g, "켭니다"],
[/편다/g, "폅니다"],
[/센다/g, "셉니다"],
[/건다/g, "겁니다"],
[/푼다/g, "풉니다"],
[/꾼다/g, "꿉니다"],
[/띈다/g, "띕니다"],
[/진다/g, "집니다"],
[/킨다/g, "킵니다"],
[/힌다/g, "힙니다"],
[/빈다/g, "빕니다"],
[/친다/g, "칩니다"],
[/잘라낸다/g, "잘라냅니다"],
[/잘린다/g, "잘립니다"],
[/뭉개진다/g, "뭉개집니다"],
[/건너뛴다/g, "건너뜁니다"],
[/깎는다/g, "깎습니다"],
[/죽는다/g, "죽습니다"],
[/잰다/g, "잽니다"],
[/낸다/g, "냅니다"],
[/긴다/g, "깁니다"],
[/없앴다/g, "없앴습니다"],
[/걷어냈다/g, "걷어냈습니다"],
[/남겼다/g, "남겼습니다"],
[/뿌렸다/g, "뿌렸습니다"],
[/넘쳤다/g, "넘쳤습니다"],
[/늘렸다/g, "늘렸습니다"],
[/신호다/g, "신호입니다"],
[/결과다/g, "결과입니다"],
[/길다/g, "깁니다"],
[/그대로였다/g, "그대로였습니다"],
[/보였다/g, "보였습니다"],
[/튀고 있었다/g, "튀고 있었습니다"],
[/어긋나 있었다/g, "어긋나 있었습니다"],
[/적었는데/g, "적었는데"],
];
function polite(text) {
let out = text;
for (const [re, to] of RULES) out = out.replace(re, to);
return out;
}
const src = fs.readFileSync(FILE, "utf8");
// STAGES_KO 는 `export const ko` 밖에 따로 선언돼 있다. 범위를 거기서 시작하지 않으면
// 파이프라인 여섯 단계의 설명이 통째로 빠진다 — 실측에서 37개가 반말로 남아 있었다.
const koStart = src.indexOf("const STAGES_KO");
const koEnd = src.indexOf("const STAGES_EN");
const koStart2 = src.indexOf("export const ko");
const koEnd2 = src.indexOf("export const en");
let ko = src.slice(koStart, koEnd);
let ko2 = src.slice(koStart2, koEnd2);
let changed = 0;
// `key: "..."` 와 `key:\n "..."` 를 모두 잡는다
ko = ko.replace(/(\b[a-zA-Z]+):(\s*)"((?:[^"\\]|\\.)*)"/g, (m, key, gap, val) => {
if (!TARGET_KEYS.has(key)) return m;
const next = polite(val);
if (next !== val) changed++;
return `${key}:${gap}"${next}"`;
});
fs.writeFileSync(FILE, src.slice(0, koStart) + ko + src.slice(koEnd), "utf8");
console.log(`존댓말 변환: ${changed}개 문자열`);

2206
apps/site/tools/site-e2e.mjs Normal file

File diff suppressed because it is too large Load diff