feat(site): redesign promotion experience
This commit is contained in:
parent
6930906583
commit
13dd160c16
39 changed files with 5764 additions and 5738 deletions
53
apps/site/tools/capture-site-og.mjs
Normal file
53
apps/site/tools/capture-site-og.mjs
Normal 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();
|
||||
}
|
||||
|
|
@ -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
2206
apps/site/tools/site-e2e.mjs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue