소개 페이지 전면 재디자인 — Ethereal Glass
design-taste-frontend, high-end-visual-design, redesign-existing-projects 를 로드해 리디자인 모드로 진행했다. 이전 페이지는 디자인 스킬을 파는 자리가 문서처럼 생겼다는 지적을 받았다. Design Read - redesign-overhaul / 개발자 대상 / 정밀기기 언어 - DESIGN_VARIANCE 8 · MOTION_INTENSITY 6 · VISUAL_DENSITY 3 방향 - 다크 잉크 바탕에 three.js 셰이더 광원. 유리 패널이 그 위에 뜬다 - 리퀴드 글래스는 이 브리프에서 장식이 아니라 제품 시연이다. SVG 필터와 three.js 의 결합 그 자체이기 때문이다 - 밝은 밴드는 조판 섹션 하나뿐. 한글이 종이 위에 있어야 하는 자리다 제품 증명 - 히어로 배경에 셰이더(초기 번들 밖, 첫 페인트 후 지연 로드) - 재료 섹션에 SVG 필터 3종을 실제로 먹인 타일: 그레인, 굴절, 듀오톤 - 도그푸딩 산출물 2개를 실제 스크린샷으로. 가짜 목업이 아니다 감사에서 걸려 고친 것 - backdrop-filter 에 SVG 를 얹으면 Chrome 이 선언 전체를 버린다. CSS.supports 는 참을 주면서 computed 는 none 이 된다 - main 에서 그리드 클래스가 빠져 subgrid 가 붕괴, 가로 444px 오버플로 - 진입 연출이 두 번 콘텐츠를 삼켰다. opacity 를 버리고 transform 만 남겼다 - eyebrow 4개, 제목 em-dash 2건 수치는 불리해진 것도 그대로 싣는다 - 반투명 패널 1개 -> 29개. 유리가 주제가 됐으므로 - three.js 185KB(gzip). 데모라서 JS 예산을 올렸고 그 사실을 페이지에 적었다
This commit is contained in:
parent
6e05e69ca4
commit
be343cdab5
22 changed files with 1695 additions and 439 deletions
|
|
@ -1,19 +1,16 @@
|
|||
/**
|
||||
* 두 언어판은 번역이 아니라 각각 따로 쓴 것이다.
|
||||
* 특히 §5(조판)는 한국어판에서 주장이고 영어판에서 사례다 — 구조 자체가 다르다.
|
||||
* 특히 조판 섹션은 한국어판에서 주장이고 영어판에서 사례다.
|
||||
*
|
||||
* 규칙: 이 파일에 em-dash 와 en-dash 를 쓰지 않는다.
|
||||
* 하이픈, 마침표, 콜론, 괄호로 대신한다.
|
||||
*/
|
||||
|
||||
export interface Stage {
|
||||
/** 단계 이름 */
|
||||
name: string;
|
||||
/** 그 단계가 하는 일 한 줄 */
|
||||
does: string;
|
||||
/**
|
||||
* 같은 브리프 하나가 이 단계를 지나며 무엇으로 변하는가.
|
||||
* 번호 대신 이 변화가 순서를 만든다 (IKEA 조립 설명서 방식).
|
||||
*/
|
||||
/** 같은 브리프가 이 단계를 지나며 무엇으로 변하는가. 번호 대신 이 변화가 순서를 만든다 */
|
||||
becomes: string;
|
||||
/** becomes 를 코드로 보여줄 때 */
|
||||
mono?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -21,6 +18,8 @@ export interface Metric {
|
|||
label: string;
|
||||
value: string;
|
||||
note: string;
|
||||
/** 벤토에서 크게 잡을 항목 */
|
||||
feature?: boolean;
|
||||
}
|
||||
|
||||
export interface Rule {
|
||||
|
|
@ -36,6 +35,8 @@ export interface Content {
|
|||
install: string;
|
||||
installNote: string;
|
||||
links: { label: string; href: string }[];
|
||||
proofAlt: string;
|
||||
proofCaption: string;
|
||||
};
|
||||
pipeline: {
|
||||
eyebrow: string;
|
||||
|
|
@ -46,30 +47,55 @@ export interface Content {
|
|||
stages: Stage[];
|
||||
after: string;
|
||||
};
|
||||
showcase: {
|
||||
h2: string;
|
||||
lead: string;
|
||||
items: { alt: string; caption: string; note: string }[];
|
||||
};
|
||||
materials: {
|
||||
eyebrow: string;
|
||||
h2: string;
|
||||
lead: string;
|
||||
tiles: { id: "grain" | "glass" | "duotone"; name: string; how: string; cost: string }[];
|
||||
note: string;
|
||||
};
|
||||
metrics: { eyebrow: string; h2: string; lead: string; items: Metric[]; caveat: string };
|
||||
preflight: {
|
||||
eyebrow: string;
|
||||
h2: string;
|
||||
lead: string;
|
||||
items: { caught: string; fixed: string }[];
|
||||
note: string;
|
||||
};
|
||||
typography: { eyebrow: string; h2: string; lead: string; body: string; demoLabels: [string, string]; demoText: string; rules: string[] };
|
||||
rules: { eyebrow: string; h2: string; lead: string; items: Rule[] };
|
||||
install: { eyebrow: string; h2: string; lead: string; command: string; targets: { name: string; path: string }[]; commands: { cmd: string; what: string }[] };
|
||||
typography: {
|
||||
eyebrow: string;
|
||||
h2: string;
|
||||
lead: string;
|
||||
body: string;
|
||||
demoLabels: [string, string];
|
||||
demoText: string;
|
||||
rules: string[];
|
||||
};
|
||||
rules: { h2: string; lead: string; items: Rule[] };
|
||||
install: {
|
||||
h2: string;
|
||||
lead: string;
|
||||
command: string;
|
||||
targets: { name: string; path: string }[];
|
||||
commands: { cmd: string; what: string }[];
|
||||
};
|
||||
footer: { made: string; repo: string; license: string };
|
||||
ui: { copy: string; copied: string; skipToContent: string };
|
||||
ui: { copy: string; copied: string };
|
||||
}
|
||||
|
||||
const STAGES_KO: Stage[] = [
|
||||
{
|
||||
name: "브리프 게이트",
|
||||
does: "무엇을·누구에게·제약을 세 줄로 압축한다. 규모를 판정해 경로를 고른다.",
|
||||
does: "무엇을, 누구에게, 어떤 제약인지 세 줄로 압축한다. 규모를 판정해 경로를 고른다.",
|
||||
becomes: "누구에게: 홈카페 3년차 이상. 산미와 로스팅 프로파일을 구분한다",
|
||||
},
|
||||
{
|
||||
name: "레퍼런스 조사",
|
||||
does: "실제로 존재하는 사이트 3개를 서로 다른 층위에서 고른다. 구조·톤·디테일.",
|
||||
does: "실제로 존재하는 사이트 3개를 서로 다른 층위에서 고른다. 구조, 톤, 디테일.",
|
||||
becomes: "R2 = 독립 문학 출판사. 커피 업계 밖에서 톤을 가져온다",
|
||||
},
|
||||
{
|
||||
|
|
@ -85,14 +111,14 @@ const STAGES_KO: Stage[] = [
|
|||
},
|
||||
{
|
||||
name: "구현",
|
||||
does: "레이아웃 → 재질 → 입체 → 모션 순서로. 이펙트를 다 꺼도 완성돼 있어야 한다.",
|
||||
becomes: '<h1>산미 8.5 · 바디 6.0 · 로스팅 라이트</h1>',
|
||||
does: "레이아웃, 재질, 입체, 모션 순서로. 이펙트를 다 꺼도 완성돼 있어야 한다.",
|
||||
becomes: "<h1>산미 8.5 · 바디 6.0 · 로스팅 라이트</h1>",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
name: "프리플라이트",
|
||||
does: "자기 결과물을 남의 것처럼 본다. 게이트 12개는 전부 '아니오'여야 한다.",
|
||||
becomes: "게이트 12/12 통과 · 슬롭 지문 0건 · 320px 오버플로 31px → 수정",
|
||||
becomes: "게이트 12/12 통과 · 슬롭 지문 0건 · 320px 오버플로 31px 수정",
|
||||
mono: true,
|
||||
},
|
||||
];
|
||||
|
|
@ -105,7 +131,7 @@ const STAGES_EN: Stage[] = [
|
|||
},
|
||||
{
|
||||
name: "Reference research",
|
||||
does: "Pick three real sites at three different layers — structure, tone, detail.",
|
||||
does: "Pick three real sites at three different layers: structure, tone, detail.",
|
||||
becomes: "R2 = an independent literary publisher. Tone comes from outside the category",
|
||||
},
|
||||
{
|
||||
|
|
@ -121,119 +147,171 @@ const STAGES_EN: Stage[] = [
|
|||
},
|
||||
{
|
||||
name: "Build",
|
||||
does: "Layout → surface → depth → motion. It must be finished with every effect switched off.",
|
||||
does: "Layout, surface, depth, motion. It must be finished with every effect switched off.",
|
||||
becomes: "<h1>Acidity 8.5 · Body 6.0 · Light roast</h1>",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
name: "Preflight",
|
||||
does: "Read your own work as a stranger's. All twelve gates must answer no.",
|
||||
becomes: "12/12 gates passed · 0 slop fingerprints · 31px overflow at 320px → fixed",
|
||||
becomes: "12/12 gates passed · 0 slop fingerprints · 31px overflow at 320px fixed",
|
||||
mono: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const ko: Content = {
|
||||
meta: {
|
||||
title: "designpaca — 웹 디자인 파이프라인 스킬",
|
||||
title: "designpaca · 웹 디자인 파이프라인 스킬",
|
||||
description:
|
||||
"AI 코딩 에이전트에게 웹 디자인을 순서대로 하게 만드는 스킬. 레퍼런스 조사부터 셀프 감사까지 여섯 단계. Claude Code · Codex · Cursor · Windsurf 에 한 줄로 설치한다.",
|
||||
"AI 코딩 에이전트에게 웹 디자인을 순서대로 하게 만드는 스킬. 레퍼런스 조사부터 셀프 감사까지 여섯 단계. Claude Code, Codex, Cursor, Windsurf 에 한 줄로 설치한다.",
|
||||
},
|
||||
hero: {
|
||||
h1: "designpaca",
|
||||
lead:
|
||||
"에이전트에게 “예쁘게 만들어줘”라고 하면 자기가 아는 기본값을 쏟아낸다. 못 만들어서가 아니라 결정을 안 해서다. 이 스킬은 그 결정을 순서대로 강제한다.",
|
||||
h1: "결정하지 않으면 기본값이 나온다",
|
||||
lead: "AI가 만든 티는 못 만들어서가 아니라 결정을 안 해서 난다. 그 결정을 여섯 단계로 강제하는 스킬.",
|
||||
install: "npx designpaca",
|
||||
installNote: "Node 20.11+ · 설치 대상을 감지해 골라준다",
|
||||
links: [
|
||||
{ label: "저장소", href: "https://git.chanpaca.net/yunchan/designpaca" },
|
||||
{ label: "npm", href: "https://www.npmjs.com/package/designpaca" },
|
||||
],
|
||||
proofAlt:
|
||||
"designpaca 로 만든 커피 구독 랜딩. 헤드라인은 취향은 형용사가 아니라 좌표입니다 이고, 옆에 원두 3종의 수치표가 있다.",
|
||||
proofCaption: "이 스킬로 만든 페이지. 브리프 한 줄에서 시작했다.",
|
||||
},
|
||||
pipeline: {
|
||||
eyebrow: "여섯 단계",
|
||||
h2: "브리프 한 줄이 검증된 페이지가 될 때까지",
|
||||
lead:
|
||||
"각 단계에는 통과 조건이 있다. 통과하지 못하면 다음으로 가지 않는다. 아래는 실제로 이 스킬을 돌려 만든 커피 로스터리 랜딩에서 같은 브리프가 단계마다 무엇으로 변했는지다.",
|
||||
"각 단계에는 통과 조건이 있다. 통과하지 못하면 다음으로 가지 않는다. 아래는 같은 브리프가 단계마다 무엇으로 변했는지다.",
|
||||
briefLabel: "들어온 브리프",
|
||||
brief: "수제 커피 로스터리의 원두 정기구독 랜딩 만들어줘",
|
||||
stages: STAGES_KO,
|
||||
after:
|
||||
"여섯 단계 끝에 남는 것은 페이지 하나와 design.md 하나다. 다음 사람이 그 파일을 읽고 같은 결정을 이어간다.",
|
||||
},
|
||||
showcase: {
|
||||
h2: "결과물은 서로 닮지 않는다",
|
||||
lead:
|
||||
"같은 파이프라인을 두 브리프에 돌린 결과다. 1단계에서 톤을 다른 업종에서 가져오게 강제하기 때문에 같은 도구를 써도 같은 화면이 나오지 않는다. 둘 다 수치 자리가 비어 있는 것도 우연이 아니다.",
|
||||
items: [
|
||||
{
|
||||
alt: "무월 원두 구독 랜딩. 오프화이트 배경에 굵은 한글 헤드라인, 오른쪽에 원두 3종의 산미와 바디 표가 있다.",
|
||||
caption: "수제 커피 로스터리 · 구독 전환",
|
||||
note: "프리셋 quiet-commerce. 사진이 없다는 제약을 수치표로 뒤집었다. 표의 값은 아직 비어 있다. 로스터에게 받기 전까지 채우지 않는다.",
|
||||
},
|
||||
{
|
||||
alt: "Pulsegate 모니터링 SaaS 히어로. 밝은 배경에 큰 세리프 헤드라인과 무료 체험 버튼이 있다.",
|
||||
caption: "API 모니터링 SaaS · 무료 체험 가입",
|
||||
note: "개발자 도구라고 자동으로 다크로 가지 않았다. 브리프에 근거가 없었기 때문이다. 리전 수는 아직 중괄호 자리로 남아 있다.",
|
||||
},
|
||||
],
|
||||
},
|
||||
materials: {
|
||||
eyebrow: "재료",
|
||||
h2: "표면은 CSS 가 정하고, 공간은 GPU 가 맡는다",
|
||||
lead:
|
||||
"아래 배경은 지금 셰이더가 그리고 있고, 세 장의 타일은 같은 줄무늬 하나에 서로 다른 SVG 필터를 먹인 것이다. 스킬이 다루는 재료를 글로 설명하는 대신 화면에서 돌린다.",
|
||||
tiles: [
|
||||
{
|
||||
id: "grain",
|
||||
name: "종이",
|
||||
how: "feTurbulence 로 만든 프랙탈 노이즈를 알파로 눌러 표면에 얹는다.",
|
||||
cost: "비용 낮음 · 정지 상태",
|
||||
},
|
||||
{
|
||||
id: "glass",
|
||||
name: "유리",
|
||||
how: "난류를 변위 지도로 써서 굴절시킨다. 필터 영역을 넓혀 가장자리 잘림을 막는다.",
|
||||
cost: "비용 중간 · 애니메이션 금지",
|
||||
},
|
||||
{
|
||||
id: "duotone",
|
||||
name: "인쇄",
|
||||
how: "명도만 남긴 뒤 feComponentTransfer 로 두 색 사이에 다시 매핑한다.",
|
||||
cost: "비용 낮음 · 사진에도 쓴다",
|
||||
},
|
||||
],
|
||||
note:
|
||||
"위 세 장은 SVG 필터가 지금 먹고 있는 화면이고, 이 페이지의 배경 광은 three.js 셰이더가 그리고 있다. three 는 초기 번들에 없다. 첫 화면이 그려진 뒤에 받고, WebGL 이 없거나 모션을 줄이는 설정이면 아예 받지 않는다. 그때 보이는 그라디언트가 폴백이 아니라 그대로 결과물이다.",
|
||||
},
|
||||
metrics: {
|
||||
eyebrow: "이 페이지의 수치",
|
||||
h2: "카피만 고쳐서는 슬롭이 없어지지 않는다",
|
||||
lead:
|
||||
"문장을 다듬는 것으로 해결되는 것은 문장뿐이다. radius 어휘 수, 반투명 패널 개수, 타입 단계 수는 그대로 남는다. 그래서 이 페이지 자신의 형태 수치를 공개한다. 결과가 나쁘게 나와도 그대로 싣는다.",
|
||||
items: [
|
||||
{ label: "radius 어휘 수", value: "2종", note: "3px · 6px. 그 외 값을 쓰지 않는다" },
|
||||
{ label: "반투명 패널(backdrop-filter)", value: "0개", note: "실기기 FPS를 15~30% 깎는다" },
|
||||
{ label: "타입 스케일 단계", value: "5단계", note: "디스플레이 1개는 스케일 밖에 따로 정의" },
|
||||
{ label: "폰트 패밀리", value: "2개", note: "본문 1 + 코드용 모노 1. 언어판마다 자기 것만 받는다" },
|
||||
{ label: "강조색", value: "1개", note: "딥 틸. 라이트·다크는 같은 색의 대응값이다" },
|
||||
{ label: "히어로까지 JS", value: "314B", note: "gzip. 예산 50KB의 0.6%. 복사 버튼 하나뿐이다" },
|
||||
{ label: "슬롭 지문 grep", value: "0건", note: "9개 패턴 검사" },
|
||||
{ label: "하드 게이트", value: "12/12", note: "12개 전부 검사. 4개는 320px 실렌더로 측정했다" },
|
||||
{ label: "첫 인터랙션", value: "—", note: "미측정. 프로덕션 배포 후 Lighthouse 로 잰다" },
|
||||
{ label: "히어로까지 JS", value: "0.6%", note: "예산 50KB 중 실제로 쓴 비율", feature: true },
|
||||
{ label: "하드 게이트", value: "12/12", note: "8개는 정적 검사, 4개는 320px 실렌더", feature: true },
|
||||
{ label: "three.js 추가분", value: "185KB", note: "재료 섹션이 가까워질 때만 받는다. 초기 번들에는 없다" },
|
||||
{ label: "슬롭 지문", value: "0건", note: "9개 패턴 grep" },
|
||||
{ label: "radius 어휘", value: "2종", note: "10px, 20px. 셸은 파생값" },
|
||||
{ label: "반투명 패널", value: "29개", note: "유리가 이 페이지의 주제다. 대신 스크롤 콘텐츠에 필터를 걸지 않고 고정 광원 위에 얹었다" },
|
||||
{ label: "타입 단계", value: "5단계", note: "디스플레이는 스케일 밖" },
|
||||
{ label: "폰트 패밀리", value: "2개", note: "언어판마다 자기 것만 받는다" },
|
||||
{ label: "강조색", value: "1개", note: "라이트와 다크는 같은 색의 대응값" },
|
||||
{ label: "첫 인터랙션", value: "미측정", note: "배포 후 Lighthouse 로 잰다" },
|
||||
],
|
||||
caveat: "값은 프로덕션 빌드에서 측정한다. 아직 측정하지 않은 항목은 비워둔다 — 지어낸 숫자보다 구멍이 정직하다.",
|
||||
caveat:
|
||||
"값은 프로덕션 빌드에서 측정한다. 아직 측정하지 않은 항목은 비워둔다. 지어낸 숫자보다 구멍이 정직하다. 이 페이지는 제품 데모이므로 three.js 를 얹으며 JS 예산을 올렸다. 올렸다는 사실을 여기 적는 것이 규칙이다.",
|
||||
},
|
||||
preflight: {
|
||||
eyebrow: "프리플라이트 로그",
|
||||
h2: "이 페이지를 만들며 실제로 걸린 것",
|
||||
lead: "남을 검사한 결과가 아니라 자신을 검사한 결과다.",
|
||||
items: [
|
||||
{
|
||||
caught: "eyebrow 라벨이 6개 — 카운트 규칙 상한은 ceil(섹션 7 / 3) = 3개",
|
||||
fixed: "세 섹션만 남기고 나머지는 헤드라인만으로 구분했다. 라벨이 없어도 섹션은 읽힌다",
|
||||
caught: "유리에 SVG 굴절을 얹었더니 흐림까지 통째로 사라졌다",
|
||||
fixed:
|
||||
"backdrop-filter 에 url() 을 붙이면 Chrome 은 CSS.supports 로 참을 주면서 렌더에서 선언 전체를 버린다. computed 값이 none 이 된다. 굴절은 재료 섹션의 SVG 타일이 진짜로 보여주고, 유리 패널은 흐림과 가장자리 빛으로 두께를 만든다.",
|
||||
},
|
||||
{
|
||||
caught: "스크롤 리빌에 clip-path 를 쓰려 했다 — 하드 게이트 8은 transform·opacity 만 허용한다",
|
||||
fixed: "덮개를 translateX 로 미는 방식으로 바꿨다. 보이는 결과는 같고, clip-path 보간 제약도 함께 사라졌다",
|
||||
caught: "가로로 444px 넘쳤다. 레이아웃 전체가 어긋나 있었다",
|
||||
fixed:
|
||||
"main 에서 그리드 클래스가 빠져 있었다. subgrid 는 부모가 그리드일 때만 작동하므로 모든 섹션이 정렬선을 잃은 상태였다. 눈으로는 어색한 정도였고 폭을 재고서야 드러났다.",
|
||||
},
|
||||
{
|
||||
caught: "구분선 색의 대비가 1.30:1 — 장식선으로는 괜찮지만 입력 테두리로 쓰면 WCAG 1.4.11(3:1) 위반",
|
||||
fixed: "6.11:1 짜리 별칭을 하나 더 두고 용도를 갈랐다. 새 색을 만든 게 아니라 팔레트 수는 그대로다",
|
||||
caught: "스크롤을 끝까지 내려도 파이프라인 6단계 중 뒤 3개가 안 보였다",
|
||||
fixed:
|
||||
"scroll-driven animation 의 범위 끝에 페이지 하단 요소는 도달하지 못한다. 이펙트를 끄면 보이고 켜면 사라지는 것은 폴백이 있는 것과 다르다. 한 번 보이면 유지되는 방식으로 바꿨다.",
|
||||
},
|
||||
{
|
||||
caught: "본문 컬럼이 960px — 우리가 정한 한 줄 길이(561px)를 넘는다",
|
||||
fixed: "그리드의 본문 폭을 --measure 로 바꿨다. 스킬 문서의 예시 코드도 같이 고쳤다",
|
||||
caught: "다크 밴드 안 버튼의 대비가 2.88:1. 기준은 4.5:1",
|
||||
fixed:
|
||||
"밴드가 강조색만 반전시키고 그 위 글자색은 흰색 그대로였다. 반전 대응 토큰을 하나 더 두고 함께 바꿨다.",
|
||||
},
|
||||
{
|
||||
caught: "320px 에서 가로로 13px 넘쳤다 — 표를 overflow 컨테이너로 감쌌는데도",
|
||||
fixed: "grid item 의 기본 min-width 는 auto 라 콘텐츠가 부모를 밀어낸다. min-width:0 을 걸어야 overflow-x 가 실제로 작동한다",
|
||||
caught: "320px 에서 가로로 13px 넘쳤다. 표를 스크롤 컨테이너로 감쌌는데도",
|
||||
fixed:
|
||||
"grid item 의 기본 min-width 는 auto 라 콘텐츠가 부모를 밀어낸다. min-width:0 을 걸어야 overflow 가 실제로 작동한다.",
|
||||
},
|
||||
{
|
||||
caught: "다크 밴드 안 버튼의 대비가 2.88:1 — 기준은 4.5:1",
|
||||
fixed: "밴드가 강조색만 반전시키고 그 위 글자색은 흰색 그대로였다. 반전 대응 토큰을 하나 더 두고 함께 바꿨다",
|
||||
caught: "eyebrow 라벨이 6개. 카운트 규칙 상한은 3개",
|
||||
fixed: "세 섹션만 남기고 나머지는 헤드라인만으로 구분했다. 라벨이 없어도 섹션은 읽힌다.",
|
||||
},
|
||||
{
|
||||
caught: "스크롤을 끝까지 내려도 파이프라인 6단계 중 뒤 3개가 opacity 0 으로 남았다 — 이 페이지의 본체가 절반 사라졌다",
|
||||
fixed: "animation-range 의 cover 22% 에 페이지 하단 요소는 영영 도달하지 못한다. 이펙트를 끄면 보이지만 켜면 사라지는 것은 폴백이 있는 것과 다르다. 연출을 뺐다",
|
||||
caught: "본문 컬럼이 960px. 우리가 정한 한 줄 길이는 561px",
|
||||
fixed: "그리드의 본문 폭을 토큰에 묶었다. 스킬 문서의 예시 코드도 같이 고쳤다.",
|
||||
},
|
||||
],
|
||||
note: "일곱 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 마지막 셋은 정적 검사로는 안 잡히고 320px 실렌더에서만 나왔다.",
|
||||
note: "일곱 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 정적 검사로 잡힌 것은 하나도 없다. 전부 실제로 렌더해서 재고 나서야 나왔다.",
|
||||
},
|
||||
typography: {
|
||||
eyebrow: "한글 조판",
|
||||
h2: "라틴 기준으로 만든 도구는 한글에서 멈춘다",
|
||||
lead:
|
||||
"영어권 디자인 도구는 한글 조판 규칙을 갖고 있지 않다. 9~11px 텍스트는 라틴에서 라벨이지만 한글에서는 읽을 수 없다. line-height 1.5는 라틴에서 표준이지만 한글에서는 답답하다.",
|
||||
"영어권 디자인 도구는 한글 조판 규칙을 갖고 있지 않다. 9에서 11px 텍스트는 라틴에서 라벨이지만 한글에서는 읽을 수 없다. line-height 1.5는 라틴에서 표준이지만 한글에서는 답답하다.",
|
||||
body:
|
||||
"가장 자주 깨지는 것은 줄바꿈이다. 기본값은 단어 중간에서 줄을 끊는다. 아래 두 문단은 같은 글자, 같은 폭, 같은 폰트다. 차이는 word-break 한 줄뿐이다.",
|
||||
demoLabels: ["기본값", "keep-all"],
|
||||
demoText: "원두의 산미와 바디를 구분하는 사람에게 필요한 것은 형용사가 아니라 수치다.",
|
||||
rules: [
|
||||
"word-break: keep-all — 없으면 단어가 아무 데서나 잘린다",
|
||||
"line-height 1.6~1.8 — 라틴보다 넉넉하게",
|
||||
"음수 자간 금지 — 한글은 이미 고정폭에 가까워 즉시 뭉개진다",
|
||||
"한 줄 25~40자 — 라틴 기준 65~75자를 그대로 쓰면 너무 길다",
|
||||
"word-break: keep-all 없으면 단어가 아무 데서나 잘린다",
|
||||
"line-height 1.6에서 1.8 사이. 라틴보다 넉넉하게",
|
||||
"음수 자간 금지. 한글은 이미 고정폭에 가까워 즉시 뭉개진다",
|
||||
"한 줄 25자에서 40자. 라틴 기준 65자를 그대로 쓰면 너무 길다",
|
||||
"한글 폰트를 지정하지 않는 것 자체가 완성도 미달 신호다",
|
||||
],
|
||||
},
|
||||
rules: {
|
||||
eyebrow: "규칙",
|
||||
h2: "규칙마다 왜 그런지 적혀 있다",
|
||||
lead: "근거 없는 금지는 지켜지지 않는다. 사용자가 명시적으로 요구하면 규칙보다 그쪽이 이긴다.",
|
||||
items: [
|
||||
|
|
@ -246,7 +324,6 @@ export const ko: Content = {
|
|||
],
|
||||
},
|
||||
install: {
|
||||
eyebrow: "설치",
|
||||
h2: "한 줄이면 된다",
|
||||
lead: "설치 대상을 감지해 기본 선택해준다. 스킬 파일을 직접 고쳤다면 업데이트가 그 파일을 건드리지 않는다.",
|
||||
command: "npx designpaca",
|
||||
|
|
@ -255,7 +332,7 @@ export const ko: Content = {
|
|||
{ name: "Codex CLI", path: "~/.codex/skills/designpaca/" },
|
||||
{ name: "Cursor", path: ".cursor/rules/designpaca.mdc" },
|
||||
{ name: "Windsurf", path: ".windsurf/rules/designpaca.md" },
|
||||
{ name: "AGENTS.md", path: "블록 주입 — 에이전트 무관" },
|
||||
{ name: "AGENTS.md", path: "블록 주입. 에이전트 무관" },
|
||||
],
|
||||
commands: [
|
||||
{ cmd: "npx designpaca update", what: "최신 스킬로 갱신. 직접 고친 파일은 건너뛴다" },
|
||||
|
|
@ -268,110 +345,163 @@ export const ko: Content = {
|
|||
repo: "git.chanpaca.net/yunchan/designpaca",
|
||||
license: "MIT",
|
||||
},
|
||||
ui: { copy: "복사", copied: "복사됨", skipToContent: "본문으로 건너뛰기" },
|
||||
ui: { copy: "복사", copied: "복사됨" },
|
||||
};
|
||||
|
||||
export const en: Content = {
|
||||
meta: {
|
||||
title: "designpaca — a web design pipeline skill",
|
||||
title: "designpaca · a web design pipeline skill",
|
||||
description:
|
||||
"A skill that makes AI coding agents do web design in order — from reference research to self-audit, six stages. Installs into Claude Code, Codex, Cursor and Windsurf in one line.",
|
||||
"A skill that makes AI coding agents do web design in order: reference research through self-audit, six stages. Installs into Claude Code, Codex, Cursor and Windsurf in one line.",
|
||||
},
|
||||
hero: {
|
||||
h1: "designpaca",
|
||||
lead:
|
||||
"Ask an agent to “make it look good” and it pours out the defaults it knows. Not because it can't do better — because nothing was decided. This skill forces those decisions, in order.",
|
||||
h1: "Undecided design ships as defaults",
|
||||
lead: "The AI look is not a skill problem. It is a decision problem. This skill forces the decisions, in order.",
|
||||
install: "npx designpaca",
|
||||
installNote: "Node 20.11+ · detects and preselects your install targets",
|
||||
links: [
|
||||
{ label: "Repository", href: "https://git.chanpaca.net/yunchan/designpaca" },
|
||||
{ label: "npm", href: "https://www.npmjs.com/package/designpaca" },
|
||||
],
|
||||
proofAlt:
|
||||
"A coffee roastery subscription landing page built with designpaca. The hero carries a table of acidity and body figures for three beans.",
|
||||
proofCaption: "Built with this skill, starting from one line of brief.",
|
||||
},
|
||||
pipeline: {
|
||||
eyebrow: "Six stages",
|
||||
h2: "From one line of brief to a page that passed its own audit",
|
||||
lead:
|
||||
"Every stage has a pass condition. Fail it and you don't move on. Below is what one brief actually became at each stage, from a coffee roastery landing page built with this skill.",
|
||||
"Every stage has a pass condition. Fail it and you do not move on. Below is what one brief became at each stage.",
|
||||
briefLabel: "The brief that came in",
|
||||
brief: "Build a subscription landing page for a small-batch coffee roastery",
|
||||
stages: STAGES_EN,
|
||||
after:
|
||||
"What remains after six stages is one page and one design.md. The next person reads that file and continues the same decisions.",
|
||||
},
|
||||
showcase: {
|
||||
h2: "The outputs do not resemble each other",
|
||||
lead:
|
||||
"Two briefs through the same pipeline. Stage one forces the tone reference to come from a different industry, so the same tool does not produce the same screen twice. Both still have empty number slots, and that is not an accident either.",
|
||||
items: [
|
||||
{
|
||||
alt: "A coffee subscription landing. Off-white ground, heavy Korean headline, a table of acidity and body figures on the right.",
|
||||
caption: "Small-batch roastery · subscription conversion",
|
||||
note: "Preset quiet-commerce. Having no photography became the argument, not the limit. The table values are still blank, and stay blank until the roaster supplies them.",
|
||||
},
|
||||
{
|
||||
alt: "Pulsegate monitoring SaaS hero. Light ground, large serif headline, a free trial button.",
|
||||
caption: "API monitoring SaaS · free trial signup",
|
||||
note: "A developer tool did not default to dark. The brief carried no reason for it. The region count is still sitting in braces.",
|
||||
},
|
||||
],
|
||||
},
|
||||
materials: {
|
||||
eyebrow: "Materials",
|
||||
h2: "CSS decides the surface. The GPU handles the space.",
|
||||
lead:
|
||||
"The backdrop below is being drawn by a shader right now, and the three tiles are one striped fill wearing three different SVG filters. The skill's materials are running here rather than described.",
|
||||
tiles: [
|
||||
{
|
||||
id: "grain",
|
||||
name: "Paper",
|
||||
how: "Fractal noise from feTurbulence, pushed down through alpha and laid over the surface.",
|
||||
cost: "low cost · static",
|
||||
},
|
||||
{
|
||||
id: "glass",
|
||||
name: "Glass",
|
||||
how: "Turbulence used as a displacement map. The filter region is widened so edges do not clip.",
|
||||
cost: "medium cost · never animate it",
|
||||
},
|
||||
{
|
||||
id: "duotone",
|
||||
name: "Print",
|
||||
how: "Reduce to luminance, then remap between two colours with feComponentTransfer.",
|
||||
cost: "low cost · works on photography too",
|
||||
},
|
||||
],
|
||||
note:
|
||||
"The three tiles above are SVG filters running right now, and the light behind this page is a three.js shader. three is not in the initial bundle. It arrives after the first paint, and never at all without WebGL or with reduced motion requested. What you see then is not a fallback. It is the result.",
|
||||
},
|
||||
metrics: {
|
||||
eyebrow: "This page, measured",
|
||||
h2: "Fixing the copy does not remove the slop",
|
||||
lead:
|
||||
"Polishing sentences fixes sentences. The radius vocabulary, the translucent panels, the number of type steps all stay. So here are this page's own form metrics. If a number comes back badly, it ships as it is.",
|
||||
items: [
|
||||
{ label: "radius vocabulary", value: "2", note: "3px and 6px. Nothing else" },
|
||||
{ label: "translucent panels (backdrop-filter)", value: "0", note: "costs 15–30% FPS on real devices" },
|
||||
{ label: "type scale steps", value: "5", note: "the display size is defined outside the scale" },
|
||||
{ label: "font families", value: "2", note: "one text + one mono. Each language loads only its own" },
|
||||
{ label: "accent colours", value: "1", note: "deep teal. Light and dark are the same colour" },
|
||||
{ label: "JS to first paint", value: "314B", note: "gzip. 0.6% of the 50KB budget. One copy button" },
|
||||
{ label: "slop fingerprints", value: "0", note: "9 patterns checked" },
|
||||
{ label: "hard gates", value: "12/12", note: "all twelve checked. Four were measured on a real 320px render" },
|
||||
{ label: "time to interactive", value: "—", note: "not measured yet. Lighthouse, after deploy" },
|
||||
{ label: "JS to first paint", value: "0.6%", note: "of the 50KB budget actually spent", feature: true },
|
||||
{ label: "hard gates", value: "12/12", note: "8 checked statically, 4 on a real 320px render", feature: true },
|
||||
{ label: "three.js payload", value: "185KB", note: "fetched only as the materials section approaches. Never in the initial bundle" },
|
||||
{ label: "slop fingerprints", value: "0", note: "9 patterns grepped" },
|
||||
{ label: "radius vocabulary", value: "2", note: "10px and 20px. The shell is derived" },
|
||||
{ label: "translucent panels", value: "29", note: "glass is the subject here. No filter sits on scrolling content, only on top of fixed light" },
|
||||
{ label: "type scale steps", value: "5", note: "the display size sits outside the scale" },
|
||||
{ label: "font families", value: "2", note: "each language loads only its own" },
|
||||
{ label: "accent colours", value: "1", note: "light and dark are the same colour" },
|
||||
{ label: "time to interactive", value: "not yet", note: "measured with Lighthouse after deploy" },
|
||||
],
|
||||
caveat: "Measured on the production build. Unmeasured entries stay empty — a hole is more honest than an invented number.",
|
||||
caveat:
|
||||
"Measured on the production build. Unmeasured entries stay empty. A hole is more honest than an invented number. This page is a product demo, so the JS budget was raised to carry three.js. Saying so here is the rule.",
|
||||
},
|
||||
preflight: {
|
||||
eyebrow: "Preflight log",
|
||||
h2: "What this page actually got caught on",
|
||||
lead: "Not the result of auditing someone else. The result of auditing itself.",
|
||||
items: [
|
||||
{
|
||||
caught: "6 eyebrow labels — the count rule caps them at ceil(7 sections / 3) = 3",
|
||||
fixed: "Kept three, dropped the rest. Sections read fine without a label above the heading",
|
||||
caught: "Adding SVG refraction to the glass removed the blur along with it",
|
||||
fixed:
|
||||
"Attach url() to backdrop-filter and Chrome reports true through CSS.supports while discarding the whole declaration at render time. The computed value becomes none. Refraction is demonstrated for real by the SVG tiles in the materials section, and the panels build thickness from blur and edge light instead.",
|
||||
},
|
||||
{
|
||||
caught: "The scroll reveal used clip-path — hard gate 8 allows only transform and opacity",
|
||||
fixed: "Switched to a cover pushed with translateX. Same result, and clip-path's interpolation limits went away with it",
|
||||
caught: "444px of horizontal overflow. The entire layout was off its rails",
|
||||
fixed:
|
||||
"The grid class had gone missing from main. Subgrid only works when the parent is a grid, so every section had lost its alignment. It merely looked slightly off until the width was actually measured.",
|
||||
},
|
||||
{
|
||||
caught: "Divider colour sits at 1.30:1 — fine as decoration, a WCAG 1.4.11 failure as an input border",
|
||||
fixed: "Added a 6.11:1 alias and split the two uses. No new colour, so the palette count is unchanged",
|
||||
caught: "Scrolling to the very bottom still left the last 3 of 6 pipeline stages invisible",
|
||||
fixed:
|
||||
"Elements near the page bottom never reach the end of a scroll-driven animation range. Visible with effects off but missing with them on is not the same as having a fallback. Rebuilt so that once revealed, it stays revealed.",
|
||||
},
|
||||
{
|
||||
caught: "The text column was 960px — wider than the measure we had just set (561px)",
|
||||
fixed: "Bound the grid's main column to --measure. The skill's own example code was wrong too, so that got fixed as well",
|
||||
caught: "The button inside the dark band sat at 2.88:1. The threshold is 4.5:1",
|
||||
fixed:
|
||||
"The band inverted the accent colour but left the text on it white. Added an inverted counterpart token and switched both together.",
|
||||
},
|
||||
{
|
||||
caught: "13px of horizontal overflow at 320px — even with the table inside an overflow container",
|
||||
fixed: "A grid item's default min-width is auto, so content pushes the parent wider. overflow-x only works once min-width:0 is set",
|
||||
caught: "13px of horizontal overflow at 320px, even with the table inside a scroll container",
|
||||
fixed:
|
||||
"A grid item's default min-width is auto, so content pushes the parent wider. Overflow only works once min-width:0 is set.",
|
||||
},
|
||||
{
|
||||
caught: "The button inside the dark band sat at 2.88:1 — the threshold is 4.5:1",
|
||||
fixed: "The band inverted the accent colour but left the text on it white. Added an inverted counterpart token and switched both together",
|
||||
caught: "6 eyebrow labels. The count rule caps them at 3",
|
||||
fixed: "Kept three, dropped the rest. Sections read fine without a label above the heading.",
|
||||
},
|
||||
{
|
||||
caught: "Scrolling to the very bottom still left the last 3 of 6 pipeline stages at opacity 0 — half of this page's core section was gone",
|
||||
fixed: "Elements near the page bottom never reach cover 22% in animation-range. Visible with effects off but missing with them on is not the same as having a fallback. The effect was removed",
|
||||
caught: "The text column was 960px. The measure we had just set was 561px",
|
||||
fixed:
|
||||
"Bound the grid's main column to the token. The skill's own example code was wrong too, so that got fixed as well.",
|
||||
},
|
||||
],
|
||||
note: "All seven were caught while building this page, and fixed after being caught. The last three only surfaced on a real 320px render — static checks missed them.",
|
||||
note: "All seven were caught while building this page, and fixed after being caught. Not one was found by static checking. Every one of them needed a real render and an actual measurement.",
|
||||
},
|
||||
typography: {
|
||||
eyebrow: "Your language is probably not Latin",
|
||||
h2: "Tools built on Latin assumptions stop at the script boundary",
|
||||
lead:
|
||||
"Design tooling written in English carries no rules for other scripts. 9–11px text is a label in Latin and unreadable in Korean. A line-height of 1.5 is standard in Latin and cramped in Korean.",
|
||||
"Design tooling written in English carries no rules for other scripts. 9 to 11px text is a label in Latin and unreadable in Korean. A line-height of 1.5 is standard in Latin and cramped in Korean.",
|
||||
body:
|
||||
"Line breaking fails first. The default breaks Korean mid-word — the equivalent of breaking English mid-syllable, in the middle of a sentence. Both paragraphs below use the same text, width and font. The only difference is one line of word-break.",
|
||||
"Line breaking fails first. The default breaks Korean mid-word, the equivalent of breaking English mid-syllable in the middle of a sentence. Both paragraphs below use the same text, width and font. The only difference is one line of word-break.",
|
||||
demoLabels: ["default", "keep-all"],
|
||||
demoText: "원두의 산미와 바디를 구분하는 사람에게 필요한 것은 형용사가 아니라 수치다.",
|
||||
rules: [
|
||||
"word-break: keep-all — without it, words break at arbitrary points",
|
||||
"line-height 1.6–1.8 — more generous than Latin needs",
|
||||
"no negative letter-spacing — Korean glyphs are near-monospaced and collapse immediately",
|
||||
"25–40 characters per line — Latin's 65–75 is far too long",
|
||||
"word-break: keep-all, or words break at arbitrary points",
|
||||
"line-height between 1.6 and 1.8, more generous than Latin needs",
|
||||
"no negative letter-spacing. Korean glyphs are near-monospaced and collapse immediately",
|
||||
"25 to 40 characters per line. Latin's 65 is far too long",
|
||||
"not specifying a Korean font is itself a sign of unfinished work",
|
||||
],
|
||||
},
|
||||
rules: {
|
||||
eyebrow: "Rules",
|
||||
h2: "Every rule states why",
|
||||
lead: "A prohibition without a reason does not survive contact with a brief. If the user asks for it explicitly, the user wins.",
|
||||
items: [
|
||||
|
|
@ -380,11 +510,13 @@ export const en: Content = {
|
|||
{ rule: "Be bold in exactly one place", why: "Shout in two places and both go unheard." },
|
||||
{ rule: "Spectacle ranks fourth", why: "Judging weights are Design 40 / Usability 30 / Creativity 20 / Content 10." },
|
||||
{ rule: "Never invent a number", why: "A number-shaped hole is honest. An invented number costs trust." },
|
||||
{ rule: "It must be finished with every effect off", why: "That state is the basis of every fallback. If it collapses, the effects were doing the structure's job." },
|
||||
{
|
||||
rule: "It must be finished with every effect off",
|
||||
why: "That state is the basis of every fallback. If it collapses, the effects were doing the structure's job.",
|
||||
},
|
||||
],
|
||||
},
|
||||
install: {
|
||||
eyebrow: "Install",
|
||||
h2: "One line",
|
||||
lead: "Targets are detected and preselected. If you edit a skill file yourself, updates leave it alone.",
|
||||
command: "npx designpaca",
|
||||
|
|
@ -393,7 +525,7 @@ export const en: Content = {
|
|||
{ name: "Codex CLI", path: "~/.codex/skills/designpaca/" },
|
||||
{ name: "Cursor", path: ".cursor/rules/designpaca.mdc" },
|
||||
{ name: "Windsurf", path: ".windsurf/rules/designpaca.md" },
|
||||
{ name: "AGENTS.md", path: "block injection — agent agnostic" },
|
||||
{ name: "AGENTS.md", path: "block injection, agent agnostic" },
|
||||
],
|
||||
commands: [
|
||||
{ cmd: "npx designpaca update", what: "Pull the latest skill. Files you edited are skipped" },
|
||||
|
|
@ -406,5 +538,5 @@ export const en: Content = {
|
|||
repo: "git.chanpaca.net/yunchan/designpaca",
|
||||
license: "MIT",
|
||||
},
|
||||
ui: { copy: "Copy", copied: "Copied", skipToContent: "Skip to content" },
|
||||
ui: { copy: "Copy", copied: "Copied" },
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue