designpaca/packages/skill/references/reference-method.md
Yun Chan 8808c672dc designpaca 초기 구현 — 스킬 · 설치 CLI · 배포 파이프라인
웹 디자인 파이프라인 스킬과 이를 5개 에이전트에 설치하는 CLI 를 담은 모노레포.

스킬 (packages/skill)
- SKILL.md 261줄 + 참조 문서 16개 3,349줄. progressive disclosure 로
  본문은 절차와 인덱스만, 지식은 references/ 로 분리
- 0~6단계 파이프라인. 규모에 따라 전체·연장·국소 세 경로로 분기
- 하드 게이트 12개는 grep·카운트로 검증 가능한 것만. 취향 판단은 제외
- 미학 프리셋 5종, AI 슬롭 지문 목록, 한글 조판 규칙,
  SVG 필터·three.js·인터랙티브 모션·HTML-in-Canvas 실전 지침

설치 CLI (packages/cli, packages/core)
- npx designpaca 온보딩 TUI. Claude Code · Codex · Cursor · Windsurf · AGENTS.md
- 매니페스트에 설치 시점 해시를 기록해 사용자가 고친 파일은 update 가 건너뛴다
- 타깃별로 본문의 references/ 경로를 실제 설치 위치로 재작성
- AGENTS.md 는 항상 로드되므로 본문 대신 303자 포인터만 주입
- Windsurf 는 12,000자 상한 초과 시 설치를 차단

배포 (build/ci, .forgejo/workflows)
- 태그 v* → 검사·테스트·빌드 → npmjs 배포 + Forgejo 레지스트리 미러
  → draft 릴리스 → Cloudflare Pages. 재실행 멱등

근거 (research/)
- 약 250개 웹 소스 조사 결과와 도그푸딩 검증 2건. 스킬의 모든 수치는 여기서 나온다

테스트 22개 통과 (core 16 · cli 6)
2026-08-20 10:48:00 +09:00

16 KiB
Raw Blame History

reference-method.md — 레퍼런스 해체와 합성

레퍼런스를 원리로 분해해서 다른 결과물로 재조합하는 절차다.

원칙 한 줄: 레퍼런스를 언어로 설명했을 때 그 설명이 여러 결과물로 구현될 수 있으면 원리다. 하나로만 이어지면 표현이다. 원리만 가져간다.

가져간다 (원리) 안 가져간다 (표현)
그리드 비율 (5:7 비대칭) 섹션 배치 그대로
타입 스케일 비율 (1.333) 폰트 조합 그대로
색의 역할 배분 (60/30/10) 색상값
여백 리듬 (섹션 120 / 요소 24) 섹션 순서
모션의 의도 (스크롤=시선 유도) 애니메이션 시퀀스

1. 3-소스 합성 규칙

레퍼런스 1개는 모방, 2개는 절충, 3개부터 합성이다. 단 층위를 나눠서 고른다.

슬롯 가져올 것 어디서
R1 · 구조 그리드, 섹션 순서, 정보 위계, 여백 리듬 같은 업종·같은 목적
R2 · 톤 색 역할, 타입 페어링, 형태 언어, 질감 반드시 다른 업종 (또는 웹이 아닌 것)
R3 · 디테일 모션 1개, 호버 1개, 전환 1개 Codrops / Eyecandy / Design Spells

R2가 R1과 같은 업종이면 합성이 아니다. SaaS 구조 + SaaS 톤 = 또 하나의 SaaS 슬롭. SaaS 구조 + 독립 출판사 톤 = 차별화.

충돌 해결 순위

  1. 가독성·접근성이 언제나 이긴다. R2의 톤이 대비를 깨면 톤을 조정한다.
  2. R1 구조가 R3 모션을 이긴다. 모션 때문에 구조를 바꾸지 않는다.
  3. 브리프가 모든 레퍼런스를 이긴다. 충돌하면 레퍼런스를 버린다.
  4. 한 축은 한 소스에서만. 그리드는 R1에서만, 팔레트는 R2에서만. 섞으면 이유 없는 절충이 된다.

2. 6축 해체 프레임워크

각 레퍼런스를 6축으로 뜯는다. 형용사는 기록이 아니다. 숫자나 규칙으로 적는다.

R3 예외: R3는 사이트가 아니라 기법이다. 축 5(모션 언어) 하나만 채우고 나머지는 비운다. 대신 R3에는 구현 제약(브라우저 지원, 성능 비용, 보간 조건)을 반드시 적는다.

축 1 — 레이아웃 그리드

컬럼 수·거터 / 콘텐츠 최대폭과 뷰포트 비 / 대칭·비대칭(비율) / 그리드를 깨는 요소 / 본문 한 줄 글자 수(영문 4575, 국문 2540) → "무엇을 정렬시키고 무엇을 일부러 어긋나게 했나?"

축 2 — 타입 스케일

실제 사용된 크기 목록 / 그 사이 비율(1.25 또는 1.333이 표준) / 단계 수(35가 건강, 8+ 는 통제 실패) / 패밀리 수(2개 표준) / 굵기 대비 / line-height / letter-spacing → "최대 글자와 본문의 비는 몇 배인가?" (4배 미만이면 위계 약함)

축 3 — 컬러 역할

색상값이 아니라 역할로 적는다. surface 단계 수 / text 3단계 유무 / accent 면적 %(10% 이하가 정상) / border 존재 여부 / state 색 분리 여부 → "강조색을 지우면 여전히 읽히는가?" (읽혀야 정상)

축 4 — 여백 리듬

기본 단위(4 또는 8px) / 섹션 간 수직 여백(80160px) / 요소 간 : 섹션 간 비율(1:4~1:6) / 여백 값 종류 수(46종이 건강) / 좌우 패딩 → "가장 큰 빈 공간은 어디이고 왜 거기인가?"

축 5 — 모션 언어

트리거(로드/스크롤/호버/커서) / duration / easing / 정보 전달인가 장식인가 / reduced-motion 대응 → "모션을 전부 끄면 작동하는가?" (작동해야 정상)

어떤 CSS 속성을 애니메이션하는지 반드시 적어라. 하드 게이트 #8은 transform·opacity 만 허용한다. stroke-dashoffset·height·clip-path·filter 로 만든 기법을 R3로 골라놓고 4단계에서 발견하면 그때는 이미 늦다. 1단계에서 걸러라. 그 기법이 좋다면 motion.md 의 우회법 표에서 같은 인상을 transform/opacity 로 내는 방법을 찾은 뒤에 채택해라.

축 6 — 시선 흐름

첫 진입점 / 2·3번째 / 비즈니스 우선순위와 일치 여부 / Z·F·수직 낙하 중 무엇인가 / CTA가 경로 위에 있나

회색조 위계 서술 (에이전트용 squint test 대체) — 자기가 만들 화면에 대해 문장으로 쓴다:

색을 전부 제거했을 때 가장 강한 덩어리는 ___, 두 번째는 ___, 세 번째는 ___. 이 순서는 비즈니스 우선순위 [1위 ___ / 2위 ___ / 3위 ___]와 일치한다 / 하지 않는다.

일치하지 않으면 크기·굵기·대비를 조정한다. 색으로 해결하지 마라.


3. 실전 절차 체크리스트

  • 1. 브리프를 한 문장으로 압축한다. [대상]이 [행동]하게. 느낌은 [형용사 2개]. 이 문장 없이 수집을 시작하지 않는다.
  • 2. galleries.md 라우팅 표에서 R1/R2/R3 갤러리를 정한다. R2가 R1과 다른 업종인지 확인한다.
  • 3. 각 갤러리에서 원본 사이트 URL을 1개씩 확보한다. 하위 페이지가 필요하면 인덱스에서 href를 뽑아라. slug를 추측하지 마라 — 추측한 URL은 404가 난다.
  • 3-b. 그 URL이 정본인지 검증한다. 사용자가 특정 제품·브랜드를 지목했고 후보 도메인이 여럿이면 필수다. HTTP 200은 살아있다는 뜻이 아니다.
    • curl -sS -o /dev/null -w "%{url_effective} %{http_code} %{size_download}\n" -L <url>최종 URL·본문 크기를 본다. 본문이 1KB 미만이면 파킹 도메인을 의심하라 — 전형적 파킹 페이지는 /lander 로 보내는 JS 한 줄뿐이다.
    • 후보들의 본문이 바이트 단위로 동일하면 전부 같은 파킹 서비스다.
    • 정본 판정 근거는 콘텐츠 안에서 찾아라: 저장소 링크, 설치 명령에 적힌 패키지·스킬 이름, 소유자 계정, 문서 제목이 로컬 파일과 일치하는지.
  • 4. 원본 사이트 3개를 §4 템플릿으로 WebFetch 한다. 갤러리 페이지가 아니라 원본이다. → 축 6·정보 위계·카피 톤을 얻는다.
  • 5. §5로 computed style을 추출한다. → 축 1·2·3·4를 얻는다. 이 단계를 건너뛰면 6축의 4개가 빈칸으로 남는다.
  • 6. 텍스트 소스로 폰트 이름·무료 대체를 확정한다. (Typewolf / Happy Hues / Eyecandy — galleries.md §3)
  • 7. 6축 해체 시트를 작성한다. 숫자로. 6축에 안 들어가는 관측은 6축 밖의 관측에 적는다.
  • 8. 합성한다. 축마다 어느 소스에서 가져왔는지 명시.
  • 9. 의도적 변형을 각 레퍼런스마다 1개씩 넣고 이유를 적는다.
  • 10. antipatterns.md를 훑고 걸린 항목이 없는지 확인한다.

4. WebFetch 프롬프트 템플릿 (그대로 복사해서 쓴다)

4-A. 원본 사이트 구조 분석 — 가장 자주 쓴다

이 페이지의 구조를 분석해줘. 다음을 순서대로 답해라.

  1. 상단부터 순서대로 섹션 목록. 각 섹션의 역할을 한 단어로 붙여라.
  2. h1의 정확한 문장, 그리고 h2 전체 목록.
  3. CTA 문구 전부와 각각이 페이지 어디에 있는지.
  4. 내비게이션 항목 목록.
  5. 이 페이지가 방문자에게 시키려는 단 하나의 행동.
  6. 카피의 톤 — 평균 문장 길이, 1인칭/2인칭 사용, 전문용어 밀도.
  7. 숫자나 지표가 사용된 위치와 그 값. 추측하지 말고 페이지에 실제로 있는 것만 답해라. 없으면 "없음"이라고 써라.

4-B. 폰트 확인 (Typewolf 등)

이 페이지에서 언급된 폰트 이름을 전부 나열해라. 각각에 대해 헤드라인용인지 본문용인지, 유료인지 무료인지, 제시된 무료 대체 폰트가 있으면 그 이름까지 적어라. 페이지에 없는 정보는 지어내지 마라.

4-C. 모션 기법 확인 (Eyecandy · Codrops)

이 페이지에서 설명하는 애니메이션 기법의 이름, 그 기법이 시각적으로 무엇을 하는지, 구현에 쓰인 CSS 속성이나 JS 라이브러리를 나열해라. 코드 예시가 있으면 핵심 부분을 그대로 옮겨라.

4-D. 디자인 시스템 토큰 확보 (DesignSystems.one · 공개 시스템 문서)

이 문서에서 다음을 추출해라. 값이 명시된 것만 적고 없으면 "미공개"라고 써라.

  1. 컬러 토큰 이름과 값 (전체 스케일)
  2. 타입 스케일 — 크기 목록, line-height, 굵기
  3. 여백 스케일 — 기본 단위와 전체 단계
  4. border-radius 값 목록
  5. 그림자 정의
  6. 모션 duration과 easing

4-E. 리디자인 — 현행 사이트 감사

이 페이지를 감사해줘. 다음을 답해라.

  1. 섹션 순서와 각 섹션이 실제로 전달하는 정보
  2. 정보 위계상 가장 강조된 것과, 비즈니스적으로 가장 중요해 보이는 것 — 둘이 일치하는가
  3. 카피에서 구체적 사실(숫자, 고유명사, 조건)이 들어간 문장과 그렇지 않은 문장의 비율
  4. 내비게이션 구조와 항목 수
  5. 페이지에서 명백히 불필요하거나 중복인 섹션 추측하지 말고 페이지에 있는 것만 답해라.

5. computed style 추출 (축 1·2·3·4를 얻는 유일한 방법)

WebFetch는 마크다운으로 변환하면서 CSS를 버린다. 그리드·타입스케일·컬러·여백은 WebFetch로 절대 나오지 않는다. 브라우저 도구(Chrome DevTools MCP / Playwright)로 원본 사이트를 열고 아래를 실행한다.

() => {
  const cs = (el, p) => getComputedStyle(el).getPropertyValue(p);
  const vis = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
  const all = [...document.querySelectorAll('body *')].filter(vis);
  const textEls = all.filter(el => [...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim().length > 1));
  const tally = a => { const m={}; a.forEach(v=>{m[v]=(m[v]||0)+1;}); return Object.entries(m).sort((x,y)=>y[1]-x[1]).slice(0,12); };
  const h1 = document.querySelector('h1');
  const p  = [...document.querySelectorAll('p')].filter(vis).find(el => el.innerText.trim().length > 200);
  return {
    // 반드시 첫 줄에 둔다. 페이지가 실행 중 이탈하면 엉뚱한 사이트의 값이 돌아온다.
    url: location.href,
    title: document.title,
    viewport: innerWidth + 'x' + innerHeight,
    heading: h1 && { size: cs(h1,'font-size'), lh: cs(h1,'line-height'), ls: cs(h1,'letter-spacing'),
                     weight: cs(h1,'font-weight'), family: cs(h1,'font-family').split(',')[0] },
    body: p && { size: cs(p,'font-size'), lh: cs(p,'line-height'), color: cs(p,'color'),
                 family: cs(p,'font-family').split(',')[0], widthPx: Math.round(p.getBoundingClientRect().width) },
    fontSizes:  tally(textEls.map(el => cs(el,'font-size'))),
    families:   tally(textEls.map(el => cs(el,'font-family').split(',')[0].replace(/["']/g,''))),
    textColors: tally(textEls.map(el => cs(el,'color'))),
    backgrounds:tally(all.map(el => cs(el,'background-color')).filter(c => c !== 'rgba(0, 0, 0, 0)')),
    radii:      tally(all.map(el => cs(el,'border-radius')).filter(r => r !== '0px')),
    widths:     tally(all.filter(el => el.getBoundingClientRect().width > 500)
                         .map(el => Math.round(el.getBoundingClientRect().width) + 'px')),
    sectionPadding: [...new Set([...document.querySelectorAll('section, main > div')].filter(vis).slice(0,20)
                       .map(el => cs(el,'padding-top') + ' / ' + cs(el,'padding-bottom')))]
  };
}

결과 읽는 법

먼저 url 을 봐라. 열려고 한 사이트가 아니면 나머지 값은 전부 버려라. 페이지가 실행 중 이탈하거나 리다이렉트되면 스크립트는 다른 사이트의 값을 조용히 반환한다. 실측에서 2회 연속 발생했다. 이건 실패가 아니라 오염이다 — 숫자가 6축 시트에 들어가고, 3단계 토큰의 근거가 되고, design.md 에 남고, 어디서도 검출되지 않는다.

  • fontSizes 상위 항목 = 타입 스케일. 최다 크기가 본문이다. 최대÷본문이 4배 미만이면 위계가 약한 레퍼런스다.
  • textColors 상위 3~4개 = 텍스트 위계 단계. 3단계 이상이면 잘 설계된 것.
  • backgrounds 최다값 = 지배 배경. 등장 횟수가 적은 채도 높은 색이 강조색이고, 그 횟수가 곧 면적 감각이다.
  • radii 종류 수 = 형태 어휘. 1~3개면 통제된 것, 그 이상이면 우연히 결정된 것.
  • widths 최다값 = 컨테이너 폭. body.widthPx = 텍스트 컬럼 폭.

브라우저 도구를 못 쓰는 환경이면 6축 중 4개가 빈칸이라는 사실을 산출물에 명시하고 넘어간다. 채운 척하지 마라.


6. 하지 말아야 할 것

  • 갤러리 홈페이지만 fetch하고 "레퍼런스를 봤다"고 말하기. 갤러리 홈은 카드 이미지뿐이라 정보가 0이다. 반드시 원본 사이트를 연다.
  • 레퍼런스 이름만 나열하고 넘어가기. 6축 수치가 없으면 참고한 게 아니다.
  • WebFetch만 하고 6축을 채웠다고 하기. WebFetch는 축 6과 카피 톤만 준다. 나머지는 §5가 필요하다.
  • 하위 페이지 URL의 slug를 추측하기. 인덱스에서 href를 뽑아라.
  • R1·R2·R3를 같은 갤러리에서 고르기.
  • 6축 시트에 "세련됐다", "깔끔하다" 같은 형용사 적기.

6. 산출물 형식 (필수)

레퍼런스 조사 단계는 아래 블록을 출력하기 전까지 완료되지 않는다. 다음 단계로 넘어가지 마라.

## 브리프
[대상]이 [행동]하게. 느낌은 [형용사 2개].

## R1 · 구조 — <이름> <원본 URL>
- 가져올 축: 그리드, 여백 리듬
- 그리드: 최대폭 1200 / 비대칭 7:5 / 거터 32
- 여백: 8px 단위 / 섹션 간 120 / 요소 간 24 (1:5)
- 6축 밖의 관측: (있으면) 카피 규범, 증거 제시 방식, 반복 노출 규칙 등
- 변형: 최대폭을 1120으로 축소 — 본문 한 줄이 국문 40자를 넘었기 때문

## R2 · 톤 — <이름> <원본 URL>   [업종: R1과 다름 ✓]
- 가져올 축: 컬러 역할, 타입 스케일
- 컬러: 배경 1단계 / 텍스트 3단계 / 강조 1색 면적 6%
- 타입: 세리프 디스플레이 + 그로테스크 본문, 비율 1.333, 최대/본문 4.2배
- 6축 밖의 관측: (있으면) 신뢰를 만드는 방식, 제품을 보여주는 방식
- 변형: 비율을 1.25로 조정 — 정보량이 많아 중간 단계가 필요

## R3 · 디테일 — <이름> <출처 URL>
- 가져올 축: 모션 언어 (R3는 이 축만 채운다)
- 모션: 스크롤 진입 시 8px 상승 + 페이드, 320ms, ease-out
- 구현 제약: 브라우저 지원 / JS 비용 / 보간 조건
- 변형: 280ms로 단축 — 페이지가 길어 반복 노출이 잦음

## 회색조 위계
가장 강한 덩어리는 ___, 두 번째 ___, 세 번째 ___.
비즈니스 우선순위 [1 ___ / 2 ___ / 3 ___]와 일치한다.

## 안 할 것
- (antipatterns.md에서 이 브리프에 특히 위험한 항목 3개 이상 명시)

검증 조건 — 하나라도 빠지면 되돌아간다.

  • R1/R2/R3 각각 원본 사이트 URL이 있다 (갤러리 URL 아님)
  • R2의 업종이 R1과 다르다
  • 각 슬롯에 가져올 축이 명시되어 있다
  • 각 슬롯에 변형 1개와 그 이유가 있다
  • 6축 값에 숫자가 들어 있다 — §5를 실행했거나, 못 했다면 어느 축이 빈칸인지 명시했다
  • R3에 구현 제약(지원 범위·JS 비용)이 적혀 있다
  • 회색조 위계가 비즈니스 우선순위와 일치한다

근거: research/references/02-methodology.md (조사일 2026-08-20)