/*
  사이트 전체 문안. 레이아웃이 아니라 이 파일만 고치면 두 언어판이 함께 갱신된다.

  문장 규칙(2026-08-21 카피 전면 재작성 때 정한 것):
  - 본문은 존댓말로 통일한다. 제목·헤드라인은 명사구 또는 단형 종결.
  - 근거 없는 수치를 넣지 않는다. 재지 않은 값은 "미측정"으로 남긴다.
  - "—" 를 문장 부호로 쓰지 않는다. 쉼표·마침표로 끊는다.
  - 자기 검증 이야기(프리플라이트)는 사실 그대로. 포장하지 않고, 고친 것만 적는다.
*/

interface Stage {
  name: string;
  does: string;
  becomes: string;
  mono?: boolean;
}

interface Metric {
  label: string;
  value: string;
  note: string;
  feature?: boolean;
}

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[];
  };
  materials: {
    eyebrow: string;
    h2: string;
    lead: string;
    demos: {
      id: "scrim" | "cvd" | "goo" | "glass";
      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: "실제로 존재하는 사이트 세 곳을 서로 다른 층위에서 고릅니다. 구조, 톤, 디테일.",
    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, in that order. 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 등에 한 줄로 설치합니다.",
  },
  hero: {
    h1: "결정하지 않으면 기본값이 나온다",
    lead:
      "AI가 뽑은 페이지가 템플릿처럼 보이는 건 능력이 부족해서가 아니라 결정이 없어서입니다. designpaca는 레퍼런스 조사부터 셀프 감사까지 여섯 단계를 순서대로 통과시키는 웹 디자인 파이프라인 스킬입니다. 단계마다 통과 조건이 있어서, 기본값이 들어갈 자리에 근거가 들어갑니다.",
    install: "npx designpaca",
    installNote: "Node 20.11 이상 · 설치 대상은 자동으로 찾아 선택합니다",
    links: [
      { label: "사례 보기", href: "/showcase" },
      { label: "npm", href: "https://www.npmjs.com/package/designpaca" },
    ],
    stage: {
      alt: "유리판 여섯 장이 깊이 방향으로 나란히 서 있습니다. 스크롤하면 카메라가 판을 하나씩 통과하고, 통과한 판은 뒤로 물러납니다.",
      caption: "판 하나가 단계 하나입니다. 통과 조건을 채우지 못하면 다음 판으로 넘어가지 않습니다.",
      gate: "게이트",
      pass: "통과",
    },
  },
  pipeline: {
    eyebrow: "여섯 단계",
    h2: "브리프 한 줄이 검증된 페이지가 될 때까지",
    lead:
      "모든 단계에는 통과 조건이 있습니다. 통과하지 못하면 다음 단계로 넘어가지 않습니다. 아래는 같은 브리프가 여섯 단계를 지나며 무엇으로 바뀌는지입니다.",
    briefLabel: "들어온 브리프",
    brief: "수제 커피 로스터리의 원두 정기구독 랜딩 만들어줘",
    stages: STAGES_KO,
  },
  materials: {
    eyebrow: "SVG 필터",
    h2: "이미지 편집기를 열지 않고 고치는 네 가지",
    lead:
      "질감 필터 데모는 흔합니다. 중요한 건 손이 실제로 줄어드는 자리입니다. 아래 네 가지에서 왼쪽은 매번 손으로 맞추던 방식, 오른쪽은 같은 소재에 선언 하나를 얹은 결과입니다. 수치는 이 화면을 그대로 캡처해 잰 값입니다.",
    demos: [
      {
        id: "scrim",
        name: "글자 모양대로 생기는 스크림",
        problem:
          "사진 위 캡션이 대비 기준을 못 넘깁니다. 사진마다 밝기가 달라서 반투명 박스를 매번 손으로 맞추게 됩니다.",
        beforeLabel: "그냥 얹음",
        afterLabel: "filter: url(#scrim)",
        how: "글자 알파를 부풀려 그 모양대로만 색을 채웁니다. 박스가 아니라 글자를 따라가므로 사진을 덜 가립니다.",
        verdict: "흰 글자 대비 1.70:1 → 4.79:1",
      },
      {
        id: "cvd",
        name: "색각 이상 시뮬레이션",
        problem:
          "상태를 색으로만 구분하면 남성 12명 중 1명에게는 같은 배지로 보입니다. 내 화면만 보고는 잡을 수 없습니다.",
        beforeLabel: "내가 보는 화면",
        afterLabel: "적록색약(2형)",
        how: "feColorMatrix 한 줄입니다. 이것만은 linearRGB 로 둡니다. 색각 변환은 물리적인 빛의 혼합이기 때문입니다.",
        verdict: "배지 셋의 최소 색거리 82 → 2 · 주의와 실패가 같은 색",
      },
      {
        id: "goo",
        name: "떨어진 것이 하나로 뭉치기",
        problem:
          "조각이 녹아 붙는 표현에 캔버스나 물리 라이브러리를 켜게 됩니다. 로더와 메뉴 전환에서 반복됩니다.",
        beforeLabel: "필터 없음 · 원 3개",
        afterLabel: "filter: url(#goo)",
        how: "블러한 뒤 알파만 세워 잘라냅니다. 붙을지 말지는 취향이 아니라 간격과 블러 반경의 비율로 정해집니다.",
        verdict: "덩어리 3개 → 1개 · JS 0줄",
      },
      {
        id: "glass",
        name: "배경을 휘게 하는 오버레이",
        problem: "반투명 패널은 뒤를 흐리게만 만듭니다. 실제 유리는 빛을 휩니다.",
        beforeLabel: "backdrop-filter: blur",
        afterLabel: "blur + url(#lens)",
        how: "변위 지도를 backdrop-filter 에 넘깁니다. 노이즈로 만들면 값이 중립으로 수렴해서 아무 일도 일어나지 않습니다.",
        verdict: "가장자리에서 격자가 눌립니다 · Chromium 전용",
      },
    ],
  },
  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:
          "그리드 항목의 기본 min-width 는 auto 라서 내용이 부모를 밀어냅니다. min-width: 0 을 줘야만 overflow 가 제 역할을 합니다.",
      },
      {
        caught: "eyebrow 라벨이 6개였습니다. 규칙 상한은 3개였습니다",
        fixed: "셋을 남기고 나머지를 걷었습니다. 라벨이 없어도 섹션은 충분히 읽힙니다.",
      },
      {
        caught: "본문 컬럼이 960px 였습니다. 방금 토큰으로 정한 measure 는 561px 였습니다",
        fixed:
          "그리드의 main 컬럼을 토큰에 묶었습니다. 스킬의 예시 코드도 같은 문제를 갖고 있어서 함께 고쳤습니다.",
      },
      {
        caught: "방향을 바꿀 때 이 스킬의 1단계를 건너뛰었습니다",
        fixed:
          "재질을 유리로 바꾸는 것은 2단계 결정이고, 그러려면 레퍼런스로 돌아가야 합니다. 우리는 구현으로 바로 들어갔습니다. 결과는 이전 방향의 뼈대에 유리를 바른 페이지였고 design.md 는 옛 결정을 설명하고 있었습니다. 0단계부터 전체 경로를 다시 돌았습니다.",
      },
      {
        caught: "유리판이 카메라를 정면으로 보고 있어 프레넬이 구조적으로 0 이었습니다",
        fixed:
          "평면이 정면을 보면 법선과 시선의 내적이 1 이라 가장자리 반사가 계산되지 않습니다. 유리로 읽히게 하는 유일한 신호가 죽어 있어 여섯 판이 흐릿한 사각형으로 보였습니다. 법선을 살짝 굽히고 UV 가장자리에서 테두리 광을 따로 만들었습니다.",
      },
      {
        caught: "이 페이지의 뼈대인 파이프라인 섹션이 뷰포트의 43% 만 쓰고 있었습니다",
        fixed:
          "1440px 중 625px. 아홉 섹션 중 가장 좁으면서 높이는 1637px 로 가장 길었습니다. 각 단계를 2열로 열어 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 이 없으면 단어가 아무 데서나 잘립니다",
      "행간은 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/skills/designpaca/" },
      { name: "ZCode", path: "~/.zcode/skills/designpaca/" },
      { name: "중립 경로", path: "~/.agents/skills/designpaca/ · 어떤 하네스든" },
      { name: "Antigravity", path: "~/.gemini/antigravity-cli/skills/designpaca/" },
      { name: "Gemini CLI", path: "~/.gemini/skills/designpaca/" },
      { name: "Windsurf", path: "~/.codeium/windsurf/skills/designpaca/" },
      { name: "Copilot", path: "~/.copilot/skills/designpaca/" },
      { 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. Six stages from reference research to self-audit, each with a pass condition, so defaults get replaced with reasons. Installs into Claude Code, Codex, Cursor and more in one line.",
  },
  hero: {
    h1: "Undecided design ships as defaults",
    lead:
      "Pages built by AI look templated not because the model lacks taste, but because nobody made the decisions. designpaca is a web design pipeline skill for AI coding agents. Six stages from reference research to self-audit, each with a pass condition, so the slot where a default would land gets a reason instead.",
    install: "npx designpaca",
    installNote: "Node 20.11+ · install targets are detected and preselected",
    links: [
      { label: "See the work", href: "/showcase" },
      { label: "npm", href: "https://www.npmjs.com/package/designpaca" },
      { label: "Repository", href: "https://git.chanpaca.net/yunchan/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,
  },
  materials: {
    eyebrow: "SVG filters",
    h2: "Four things you fix without opening an image editor",
    lead:
      "Filter demos that make texture are everywhere. What matters is where filters actually save you work. In all four 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 the contrast threshold. 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",
      },
      {
        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, because colour-vision transforms are physical mixing. 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 with feColorMatrix. Whether they fuse is arithmetic, not taste: the ratio of gap to blur radius decides it.",
        verdict: "counted across the middle row: 3 blobs → 1 · 0 lines of JS",
      },
      {
        id: "glass",
        name: "An overlay that bends the backdrop",
        problem: "A translucent panel only blurs what is behind it. Real glass bends light.",
        beforeLabel: "backdrop-filter: blur",
        afterLabel: "blur + url(#lens)",
        how: "Hand a displacement map to backdrop-filter. Build the map with feTurbulence and every value collapses toward neutral, so nothing moves. The map has to be a gradient you draw, pushing inward from the edges.",
        verdict: "the grid compresses at the edges · Chromium only",
      },
    ],
  },
  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 this page publishes its own form metrics, and a bad number ships as it is. Leaving the audit in view is the core feature being sold here.",
    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 5.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 that Chrome drops backdrop-filter with SVG. It does not",
        fixed:
          "We blamed the browser after one failure. Re-measuring showed Chrome 151 keeps the computed value at url(#id) and genuinely refracts. The real cause was feImage width and height not matching the element, because backdrop-filter does not auto-size filter images. Write a failure down as impossible and that sentence becomes the next person's ceiling.",
      },
      {
        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 to 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. So every rule carries its reason. If the user asks for something 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:
      "Install 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/skills/designpaca/" },
      { name: "ZCode", path: "~/.zcode/skills/designpaca/" },
      { name: "Neutral path", path: "~/.agents/skills/designpaca/ · any harness" },
      { name: "Antigravity", path: "~/.gemini/antigravity-cli/skills/designpaca/" },
      { name: "Gemini CLI", path: "~/.gemini/skills/designpaca/" },
      { name: "Windsurf", path: "~/.codeium/windsurf/skills/designpaca/" },
      { name: "Copilot", path: "~/.copilot/skills/designpaca/" },
      { 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" },
};
