/**
 * 두 언어판은 번역이 아니라 각각 따로 쓴 것이다.
 * 특히 조판 섹션은 한국어판에서 주장이고 영어판에서 사례다.
 *
 * 규칙: 이 파일에 em-dash 와 en-dash 를 쓰지 않는다.
 * 하이픈, 마침표, 콜론, 괄호로 대신한다.
 */

export interface Stage {
  name: string;
  does: string;
  /** 같은 브리프가 이 단계를 지나며 무엇으로 변하는가. 번호 대신 이 변화가 순서를 만든다 */
  becomes: string;
  mono?: boolean;
}

export interface Metric {
  label: string;
  value: string;
  note: string;
  /** 벤토에서 크게 잡을 항목 */
  feature?: boolean;
}

export interface Rule {
  rule: string;
  why: string;
}

export interface Content {
  meta: { title: string; description: string };
  hero: {
    h1: string;
    lead: string;
    install: string;
    installNote: string;
    links: { label: string; href: string }[];
    stage: {
      alt: string;
      caption: string;
      gate: string;
      pass: string;
    };
  };
  pipeline: {
    eyebrow: string;
    h2: string;
    lead: string;
    briefLabel: string;
    brief: string;
    stages: Stage[];
    after: string;
  };
  showcase: {
    h2: string;
    lead: string;
    items: { alt: string; caption: string; note: string }[];
  };
  materials: {
    eyebrow: string;
    h2: string;
    lead: string;
    demos: {
      id: "scrim" | "cvd" | "goo";
      name: string;
      problem: string;
      beforeLabel: string;
      afterLabel: string;
      how: string;
      verdict: string;
    }[];
  };
  metrics: { eyebrow: string; h2: string; lead: string; items: Metric[]; caveat: string };
  preflight: {
    h2: string;
    lead: string;
    items: { caught: string; fixed: string }[];
    note: string;
  };
  typography: {
    eyebrow: string;
    h2: string;
    lead: string;
    sample: string;
    demos: {
      id: "break" | "leading" | "size";
      title: string;
      badLabel: string;
      goodLabel: string;
      note: 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 }[];
  };
  /* 저장소 링크는 푸터가 아니라 헤더에 둔다.
     푸터를 없애면 소스로 가는 길이 사라지고, 개발 도구 소개 페이지에서
     그 링크는 장식이 아니라 목적지다. */
  nav: { items: { label: string; href: string }[]; menu: string; repo: string; repoLabel: string };
  ui: { copy: string; copied: string };
}

const STAGES_KO: Stage[] = [
  {
    name: "브리프 게이트",
    does: "무엇을, 누구에게, 어떤 제약인지 세 줄로 압축한다. 규모를 판정해 경로를 고른다.",
    becomes: "누구에게: 홈카페 3년차 이상. 산미와 로스팅 프로파일을 구분한다",
  },
  {
    name: "레퍼런스 조사",
    does: "실제로 존재하는 사이트 3개를 서로 다른 층위에서 고른다. 구조, 톤, 디테일.",
    becomes: "R2 = 독립 문학 출판사. 커피 업계 밖에서 톤을 가져온다",
  },
  {
    name: "방향 결정",
    does: "레퍼런스를 하나의 축으로 합성한다. 감수할 리스크를 하나 고른다.",
    becomes: "감상은 이름에만, 화면에는 수치만",
  },
  {
    name: "디자인 토큰",
    does: "코드를 쓰기 전에 숫자를 정한다. 성능 예산도 여기서 확정한다.",
    becomes: "--measure: 33em  /  --leading-normal: 1.7  /  JS 예산 0KB",
    mono: true,
  },
  {
    name: "구현",
    does: "레이아웃, 재질, 입체, 모션 순서로. 이펙트를 다 꺼도 완성돼 있어야 한다.",
    becomes: "<h1>산미 8.5 · 바디 6.0 · 로스팅 라이트</h1>",
    mono: true,
  },
  {
    name: "프리플라이트",
    does: "자기 결과물을 남의 것처럼 본다. 게이트 12개는 전부 '아니오'여야 한다.",
    becomes: "게이트 12/12 통과 · 슬롭 지문 0건 · 320px 오버플로 31px 수정",
    mono: true,
  },
];

const STAGES_EN: Stage[] = [
  {
    name: "Brief gate",
    does: "Compress the brief into three lines: what, for whom, constraints. Pick a path by scope.",
    becomes: "For whom: home baristas of 3+ years who can taste the roast profile",
  },
  {
    name: "Reference research",
    does: "Pick three real sites at three different layers: structure, tone, detail.",
    becomes: "R2 = an independent literary publisher. Tone comes from outside the category",
  },
  {
    name: "Direction",
    does: "Synthesize the references into one axis. Choose exactly one risk worth taking.",
    becomes: "Sentiment stays in the name. The screen carries numbers.",
  },
  {
    name: "Design tokens",
    does: "Decide the numbers before writing code. The performance budget is fixed here too.",
    becomes: "--measure: 65ch  /  --leading-normal: 1.55  /  JS budget 0KB",
    mono: true,
  },
  {
    name: "Build",
    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",
    mono: true,
  },
];

export const ko: Content = {
  meta: {
    title: "designpaca · 웹 디자인 파이프라인 스킬",
    description:
      "AI 코딩 에이전트에게 웹 디자인을 순서대로 하게 만드는 스킬. 레퍼런스 조사부터 셀프 감사까지 여섯 단계. Claude Code, Codex, Cursor, Windsurf 에 한 줄로 설치한다.",
  },
  hero: {
    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" },
    ],
    stage: {
      alt: "유리판 여섯 장이 깊이 방향으로 나란히 서 있다. 스크롤하면 카메라가 판을 하나씩 통과하고, 통과한 판은 뒤로 물러난다.",
      caption: "판 하나가 단계 하나다. 통과 조건을 채우지 못하면 다음 판으로 가지 않는다.",
      gate: "게이트",
      pass: "통과",
    },
  },
  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: "SVG 필터",
    h2: "이미지 편집기를 열지 않고 고치는 세 가지",
    lead:
      "질감을 만드는 필터 데모는 많다. 손이 실제로 줄어드는 자리는 따로 있다. 아래 셋은 왼쪽이 매번 손으로 맞추던 것, 오른쪽이 같은 소재에 선언 하나만 얹은 것이다. 아래 수치는 이 화면을 그대로 캡처해 잰 값이다.",
    demos: [
      {
        id: "scrim",
        name: "글자 모양대로 생기는 스크림",
        problem: "사진 위 캡션이 대비 4.5:1 을 못 넘는다. 사진마다 밝기가 달라 반투명 박스를 손으로 맞춰야 한다.",
        beforeLabel: "그냥 얹음",
        afterLabel: "filter: url(#scrim)",
        how: "feMorphology 로 글자 알파를 부풀리고 블러한 뒤, 그 모양대로만 feFlood 를 채운다. 박스가 아니라 글자를 따라가므로 사진을 가리지 않는다.",
        verdict: "흰 글자 대비 1.70:1 → 4.79:1 · 사진을 바꿔도 다시 맞출 것이 없다",
      },
      {
        id: "cvd",
        name: "색각 이상 시뮬레이션",
        problem: "상태를 색으로만 구분하면 남성 12명 중 1명에게 같은 배지가 된다. 눈으로는 절대 못 잡는다.",
        beforeLabel: "내가 보는 화면",
        afterLabel: "적록색약(2형)",
        how: "feColorMatrix 하나다. 이것만은 sRGB 로 두면 안 된다 — 색각 변환은 물리적 혼합이라 linearRGB 가 맞다. 필터 전체에서 유일한 예외다.",
        verdict: "세 배지의 최소 색거리 82 → 2 · 주의와 실패가 같은 색이 된다",
      },
      {
        id: "goo",
        name: "떨어진 것이 하나로 뭉치기",
        problem: "조각이 서로 녹아 붙는 표현을 하려고 캔버스나 물리 라이브러리를 켠다. 로더, 메뉴 병합, 인디케이터 전환에서 반복된다.",
        beforeLabel: "필터 없음 · 원 3개",
        afterLabel: "filter: url(#goo)",
        how: "크게 블러한 뒤 feColorMatrix 로 알파만 18배 세워 잘라낸다. 붙을지 말지는 눈대중이 아니라 산수다 — 두 원 사이 중간점의 블러 알파가 문턱(7÷18 ≒ 0.39)을 넘어야 한다. 간격 18px 에 블러 7 로는 0.2 근처라 못 넘었고, 12px 에 10 으로 넘겼다.",
        verdict: "가로로 세어 덩어리 3개 → 1개 · JS 0줄",
      },
    ],
  },
  metrics: {
    eyebrow: "이 페이지의 수치",
    h2: "카피만 고쳐서는 슬롭이 없어지지 않는다",
    lead:
      "문장을 다듬는 것으로 해결되는 것은 문장뿐이다. radius 어휘 수, 반투명 패널 개수, 타입 단계 수는 그대로 남는다. 그래서 이 페이지 자신의 형태 수치를 공개한다. 결과가 나쁘게 나와도 그대로 싣는다.",
    items: [
      { label: "히어로까지 JS", value: "0.6%", note: "예산 50KB 중 실제로 쓴 비율", feature: true },
      { label: "하드 게이트", value: "12/12", note: "정적 검사와 320px · 1440px 실렌더 양쪽", feature: true },
      { label: "three.js 추가분", value: "184KB", note: "검사대가 화면에 들어올 때만 받는다. 초기 번들에는 없다" },
      { label: "슬롭 지문", value: "0건", note: "9개 패턴 grep" },
      { label: "radius 어휘", value: "2종", note: "3px 과 14px. 알약 모양을 없앴다" },
      { label: "반투명 패널", value: "2개", note: "29개까지 늘렸다가 걷어냈다. 유리는 무언가를 담는 액자에만 남겼다" },
      { label: "타입 단계", value: "6단계", note: "토큰에 정의된 수. 디스플레이는 스케일 밖이고, 최대 글자는 본문의 5.6배" },
      { label: "폰트 패밀리", value: "2개", note: "언어판마다 자기 것만 받는다" },
      { label: "강조색", value: "1개", note: "라이트와 다크는 같은 색의 대응값" },
      { label: "첫 인터랙션", value: "미측정", note: "배포 후 Lighthouse 로 잰다" },
    ],
    caveat:
      "값은 프로덕션 빌드에서 측정한다. 아직 측정하지 않은 항목은 비워둔다. 지어낸 숫자보다 구멍이 정직하다. 이 페이지는 제품 데모이므로 three.js 를 얹으며 JS 예산을 올렸다. 올렸다는 사실을 여기 적는 것이 규칙이다.",
  },
  preflight: {
    h2: "이 페이지를 만들며 실제로 걸린 것",
    lead: "남을 검사한 결과가 아니라 자신을 검사한 결과다.",
    items: [
      {
        caught: "폰트가 교체되는 순간 글자 폭이 13% 튀고 있었다",
        fixed:
          "웹폰트가 오기 전에는 시스템 폰트로 그려진다. 맑은 고딕은 Pretendard 보다 13% 넓어서 교체되는 순간 줄바꿈이 다시 잡힌다. 폴백 폰트에 size-adjust 로 치수를 맞췄다. 실측 차이 0.0%. OS 마다 시스템 폰트가 다르므로 폴백도 따로 정의했다.",
      },
      {
        caught: "유리 카드를 29개 뿌렸다. 재질만 바뀌었을 뿐 구조는 카드 나열 그대로였다",
        fixed:
          "우리 규칙이 '카드는 elevation 이 진짜 위계를 나를 때만'이라고 정해두고 있었다. 수치는 조판으로, 규칙은 어긋난 목록으로, 감사 기록은 로그로 바꿨다. 유리는 5개만 남았다.",
      },
      {
        caught: "“backdrop-filter 에 SVG 를 얹으면 Chrome 이 버린다” 고 적었는데 사실이 아니었다",
        fixed:
          "한 번 실패하고 브라우저 탓으로 적어뒀다. 다시 재보니 Chrome 151 에서 computed 값이 none 이 아니라 url(#id) 로 살아 있고 배경도 실제로 굴절된다. 진짜 원인은 feImage 의 width/height 를 요소 크기와 맞추지 않은 것이었다 — backdrop-filter 는 필터 이미지를 자동으로 조정하지 않는다. 한 번 실패한 것을 ‘안 되는 것’ 으로 적으면 그 문장이 다음 사람의 상한선이 된다.",
      },
      {
        caught: "가로로 444px 넘쳤다. 레이아웃 전체가 어긋나 있었다",
        fixed:
          "main 에서 그리드 클래스가 빠져 있었다. subgrid 는 부모가 그리드일 때만 작동하므로 모든 섹션이 정렬선을 잃은 상태였다. 눈으로는 어색한 정도였고 폭을 재고서야 드러났다.",
      },
      {
        caught: "스크롤을 끝까지 내려도 파이프라인 6단계 중 뒤 3개가 안 보였다",
        fixed:
          "scroll-driven animation 의 범위 끝에 페이지 하단 요소는 도달하지 못한다. 이펙트를 끄면 보이고 켜면 사라지는 것은 폴백이 있는 것과 다르다. 한 번 보이면 유지되는 방식으로 바꿨다.",
      },
      {
        caught: "다크 밴드 안 버튼의 대비가 2.88:1. 기준은 4.5:1",
        fixed:
          "밴드가 강조색만 반전시키고 그 위 글자색은 흰색 그대로였다. 반전 대응 토큰을 하나 더 두고 함께 바꿨다.",
      },
      {
        caught: "320px 에서 가로로 13px 넘쳤다. 표를 스크롤 컨테이너로 감쌌는데도",
        fixed:
          "grid item 의 기본 min-width 는 auto 라 콘텐츠가 부모를 밀어낸다. min-width:0 을 걸어야 overflow 가 실제로 작동한다.",
      },
      {
        caught: "eyebrow 라벨이 6개. 카운트 규칙 상한은 3개",
        fixed: "세 섹션만 남기고 나머지는 헤드라인만으로 구분했다. 라벨이 없어도 섹션은 읽힌다.",
      },
      {
        caught: "본문 컬럼이 960px. 우리가 정한 한 줄 길이는 561px",
        fixed: "그리드의 본문 폭을 토큰에 묶었다. 스킬 문서의 예시 코드도 같이 고쳤다.",
      },
      {
        caught: "방향을 바꾸면서 이 스킬의 1단계를 건너뛰었다",
        fixed:
          "재질을 유리로 바꾸라는 요청은 2단계 방향 재결정이고, 그때는 전체 경로로 올려 레퍼런스부터 다시 봐야 한다. 그러지 않고 구현으로 바로 들어갔다. 결과는 이전 방향의 뼈대에 유리를 바른 페이지였고 design.md 는 옛 결정을 그대로 담고 있었다. 0단계부터 다시 돌았다.",
      },
      {
        caught: "유리판이 정면으로 서 있어 프레넬이 구조적으로 0이었다",
        fixed:
          "평면이 카메라를 정면으로 보면 법선과 시선의 내적이 1이라 가장자리 반사가 계산되지 않는다. 유리로 읽히게 하는 유일한 신호가 죽어 있었고, 그래서 판 여섯 장이 흐린 사각형으로 보였다. 법선을 미세하게 굽히고 테두리 광을 UV 경계에서 따로 만들었다.",
      },
      {
        caught: "페이지의 본체인 파이프라인이 화면의 43%만 쓰고 있었다",
        fixed:
          "1440px 중 625px. 아홉 섹션에서 가장 좁으면서 높이는 1637px 로 가장 길었다. 단계 설명과 산출물을 두 열로 열어 1113px 로 넓혔다.",
      },
      {
        caught: "대비를 재는 스크립트가 반투명 배경을 불투명으로 계산했다",
        fixed:
          "\"English 1.15:1\" 이라는 틀린 값이 나왔다. 알파를 아래에서 위로 합성하도록 고치자 실패 0건이었다. 검사 도구가 틀리면 통과도 실패도 의미가 없다.",
      },
    ],
    note: "열세 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 사람이 잡은 것이 셋이다. 카드가 전형적이라는 말, 폰트를 제대로 다루지 못한다는 말, 그리고 우리 스킬을 진짜로 쓴 게 맞냐는 말.",
  },
  typography: {
    eyebrow: "한글 조판",
    h2: "라틴 기본값은 한글에서 세 군데가 깨진다",
    lead:
      "영어권 디자인 도구에는 한글 조판 규칙이 없다. 그래서 기본값을 그대로 쓰면 아래 셋이 동시에 무너진다. 왼쪽이 기본값, 오른쪽이 이 스킬이 정한 값이다. 글자도 폭도 폰트도 같다.",
    sample: "스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.",
    demos: [
      {
        id: "break",
        title: "줄바꿈",
        badLabel: "기본값",
        goodLabel: "word-break: keep-all",
        note: "기본값은 어절 한가운데를 끊는다. 왼쪽 마지막 줄을 보라 — 한 단어가 두 줄에 걸쳐 있다.",
      },
      {
        id: "leading",
        title: "행간",
        badLabel: "line-height 1.45",
        goodLabel: "line-height 1.75",
        note: "라틴 표준 1.4~1.5 는 받침이 있는 글자에 좁다. 줄이 서로 붙어 보인다.",
      },
      {
        id: "size",
        title: "크기",
        badLabel: "11px",
        goodLabel: "15px",
        note: "라틴에서 11px 은 라벨로 쓴다. 한글은 획이 많아 같은 크기에서 뭉갠다.",
      },
    ],
    rules: [
      "word-break: keep-all 없으면 단어가 아무 데서나 잘린다",
      "line-height 1.6에서 1.8 사이. 라틴보다 넉넉하게",
      "음수 자간 금지. 한글은 이미 고정폭에 가까워 즉시 뭉개진다",
      "한 줄 25자에서 40자. 라틴 기준 65자를 그대로 쓰면 너무 길다",
      "한글 폰트를 지정하지 않는 것 자체가 완성도 미달 신호다",
    ],
  },
  rules: {
    h2: "규칙마다 왜 그런지 적혀 있다",
    lead: "근거 없는 금지는 지켜지지 않는다. 사용자가 명시적으로 요구하면 규칙보다 그쪽이 이긴다.",
    items: [
      { rule: "레퍼런스 없이 시작하지 않는다", why: "머릿속 기본값에서 출발하면 결과는 그 카테고리의 평균이 된다." },
      { rule: "강조색은 하나다", why: "두 개가 필요하다고 느끼면 위계 설계가 실패한 것이다." },
      { rule: "대담함은 한 곳에만", why: "두 곳에서 소리치면 둘 다 죽는다." },
      { rule: "화려함은 4순위다", why: "심사 배점이 Design 40 / Usability 30 / Creativity 20 / Content 10이다." },
      { rule: "지어낸 수치를 쓰지 않는다", why: "숫자 모양의 구멍은 정직하고, 지어낸 숫자는 신뢰를 깎는다." },
      { rule: "이펙트를 다 꺼도 완성돼 있어야 한다", why: "그 상태가 모든 폴백의 기반이다. 무너지면 이펙트가 구조를 대신하고 있는 것이다." },
    ],
  },
  install: {
    h2: "한 줄이면 된다",
    lead: "설치 대상을 감지해 기본 선택해준다. 스킬 파일을 직접 고쳤다면 업데이트가 그 파일을 건드리지 않는다.",
    command: "npx designpaca",
    targets: [
      { name: "Claude Code", path: "~/.claude/skills/designpaca/" },
      { 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: "블록 주입. 에이전트 무관" },
    ],
    commands: [
      { cmd: "npx designpaca update", what: "최신 스킬로 갱신. 직접 고친 파일은 건너뛴다" },
      { cmd: "npx designpaca doctor", what: "설치 상태와 드리프트 진단" },
      { cmd: "npx designpaca uninstall", what: "매니페스트 기반 정확한 제거" },
    ],
  },
  nav: {
    items: [
      { label: "파이프라인", href: "#pipeline" },
      { label: "사례", href: "#showcase" },
      { label: "수치", href: "#metrics" },
      { label: "설치", href: "#install" },
    ],
    menu: "메뉴",
    repo: "https://git.chanpaca.net/yunchan/designpaca",
    repoLabel: "저장소",
  },
  ui: { copy: "복사", copied: "복사됨" },
};

export const en: Content = {
  meta: {
    title: "designpaca · a web design pipeline skill",
    description:
      "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: "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" },
    ],
    stage: {
      alt: "Six glass panes stand in a row along the depth axis. Scrolling moves the camera through them one at a time, and each pane recedes once passed.",
      caption: "One pane is one stage. Fail its condition and you do not reach the next.",
      gate: "GATE",
      pass: "PASS",
    },
  },
  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 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: "SVG filters",
    h2: "Three things you fix without opening an image editor",
    lead:
      "There is no shortage of filter demos that make texture. The places where filters actually save you work are elsewhere. In all three below, the left is what you used to hand-tune every time and the right is the same material with one declaration added. The numbers come from capturing this screen and measuring it.",
    demos: [
      {
        id: "scrim",
        name: "A scrim shaped like the letters",
        problem: "A caption over a photo fails 4.5:1. Every photo has a different brightness, so the translucent box gets hand-tuned each time.",
        beforeLabel: "just placed on top",
        afterLabel: "filter: url(#scrim)",
        how: "feMorphology fattens the glyph alpha, a blur softens it, and feFlood fills only that shape. It follows the letters instead of covering the photo with a box.",
        verdict: "white-on-photo contrast 1.70:1 → 4.79:1 · swap the photo, nothing to re-tune",
      },
      {
        id: "cvd",
        name: "Colour-vision simulation",
        problem: "State encoded in colour alone becomes one badge for 1 in 12 men. No amount of looking at your own screen catches it.",
        beforeLabel: "what you see",
        afterLabel: "deuteranopia",
        how: "A single feColorMatrix. This is the one filter that must not run in sRGB — colour-vision transforms are physical mixing, so linearRGB is correct here.",
        verdict: "minimum colour distance across the three badges 82 → 2 · warning and failure become one colour",
      },
      {
        id: "goo",
        name: "Separate shapes fusing into one",
        problem: "Getting pieces to melt together usually means reaching for canvas or a physics library. It comes up in loaders, merging menus, indicator transitions.",
        beforeLabel: "no filter · 3 circles",
        afterLabel: "filter: url(#goo)",
        how: "Blur hard, then stand the alpha up 18× with feColorMatrix. Whether they fuse is arithmetic, not taste — the blurred alpha midway between two circles has to clear the threshold (7÷18 ≒ 0.39). At 18px apart with a blur of 7 it sat near 0.2 and never merged; 12px and 10 clears it.",
        verdict: "counted across the middle row: 3 blobs → 1 · 0 lines of JS",
      },
    ],
  },
  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: "JS to first paint", value: "0.6%", note: "of the 50KB budget actually spent", feature: true },
      { label: "hard gates", value: "12/12", note: "static checks plus real renders at 320px and 1440px", feature: true },
      { label: "three.js payload", value: "184KB", note: "fetched only when the inspection frame enters view. Never in the initial bundle" },
      { label: "slop fingerprints", value: "0", note: "9 patterns grepped" },
      { label: "radius vocabulary", value: "2", note: "3px and 14px. The pill shape is gone" },
      { label: "translucent panels", value: "2", note: "grew to 29 before being pulled back. Glass stayed only on frames that hold something" },
      { label: "type scale steps", value: "6", note: "as defined in tokens. Display sits outside the scale, and the largest type is 6x the body" },
      { 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. This page is a product demo, so the JS budget was raised to carry three.js. Saying so here is the rule.",
  },
  preflight: {
    h2: "What this page actually got caught on",
    lead: "Not the result of auditing someone else. The result of auditing itself.",
    items: [
      {
        caught: "Text width jumped 13% at the moment the font swapped",
        fixed:
          "Before the web font arrives the system font draws instead, and Malgun Gothic runs 13% wider than Pretendard, so line breaks are recalculated on swap. Matched the fallback with size-adjust. Measured difference is now 0.0%. System fonts differ per OS, so each gets its own fallback.",
      },
      {
        caught: "29 glass cards. Only the material had changed. The structure was still a row of cards",
        fixed:
          "Our own rule says cards are for when elevation carries real hierarchy. Figures became typography, rules became an offset list, the audit became a log. Five panels of glass remain.",
      },
      {
        caught: "We wrote down that Chrome drops backdrop-filter with SVG. It does not",
        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: "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: "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 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 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: "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: "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.",
      },
      {
        caught: "We skipped this skill's own stage 1 when the direction changed",
        fixed:
          "Switching the material to glass is a stage-2 decision, and that means going back to references first. We jumped straight to implementation instead. The result was glass painted onto the previous direction's skeleton, while design.md still described the old decisions. We re-ran the whole path from stage 0.",
      },
      {
        caught: "The glass panes faced the camera head-on, so Fresnel was structurally zero",
        fixed:
          "When a flat plane faces the camera, the dot product of normal and view is 1, so no edge reflection is computed. The one signal that makes a surface read as glass was dead, which is why six panes looked like blurry rectangles. We bend the normal slightly and derive the rim light from the UV edge instead.",
      },
      {
        caught: "The pipeline, the spine of this page, used only 43% of the viewport",
        fixed:
          "625px out of 1440. Narrowest of the nine sections while being the tallest at 1637px. Opening each stage into two columns brought it to 1113px.",
      },
      {
        caught: "The contrast script treated translucent backgrounds as opaque",
        fixed:
          "It reported \"English 1.15:1\", which was wrong. Compositing alpha bottom-up gave zero failures. A checker that is wrong makes both its passes and its failures meaningless.",
      },
    ],
    note: "All thirteen were caught while building this page, and fixed after being caught. Three came from a person rather than a checker: the cards look generic, the typography is not handled well, and did we actually use our own skill.",
  },
  typography: {
    eyebrow: "Your language is probably not Latin",
    h2: "Latin defaults break Korean in three places",
    lead:
      "Western design tools carry no Korean typesetting rules. Leave the defaults in place and all three of these fail at once. Left is the default, right is what this skill sets. Same text, same width, same font.",
    sample: "스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.",
    demos: [
      {
        id: "break",
        title: "Line breaking",
        badLabel: "default",
        goodLabel: "word-break: keep-all",
        note: "The default breaks mid-word. Look at the last line on the left — one word straddles two lines.",
      },
      {
        id: "leading",
        title: "Leading",
        badLabel: "line-height 1.45",
        goodLabel: "line-height 1.75",
        note: "The Latin standard of 1.4-1.5 is tight for syllable blocks with a final consonant.",
      },
      {
        id: "size",
        title: "Size",
        badLabel: "11px",
        goodLabel: "15px",
        note: "11px is a label size in Latin. Korean glyphs carry more strokes and smear at it.",
      },
    ],
    rules: [
      "Without word-break: keep-all, words break at arbitrary points",
      "line-height between 1.6 and 1.8 — looser than Latin",
      "Never use negative tracking. Korean is already near-monospaced",
      "25 to 40 characters per line. The Latin 65 is far too long",
      "Shipping without a Korean font stack is itself a tell",
    ],
  },
  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: [
      { rule: "Never start without references", why: "Starting from memory produces the category average." },
      { rule: "One accent colour", why: "Wanting a second one means the hierarchy failed." },
      { 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.",
      },
    ],
  },
  install: {
    h2: "One line",
    lead: "Targets are detected and preselected. If you edit a skill file yourself, updates leave it alone.",
    command: "npx designpaca",
    targets: [
      { name: "Claude Code", path: "~/.claude/skills/designpaca/" },
      { 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" },
    ],
    commands: [
      { cmd: "npx designpaca update", what: "Pull the latest skill. Files you edited are skipped" },
      { cmd: "npx designpaca doctor", what: "Diagnose install state and drift" },
      { cmd: "npx designpaca uninstall", what: "Exact removal, driven by the manifest" },
    ],
  },
  nav: {
    items: [
      { label: "Pipeline", href: "#pipeline" },
      { label: "Work", href: "#showcase" },
      { label: "Numbers", href: "#metrics" },
      { label: "Install", href: "#install" },
    ],
    menu: "Menu",
    repo: "https://git.chanpaca.net/yunchan/designpaca",
    repoLabel: "Source",
  },
  ui: { copy: "Copy", copied: "Copied" },
};
