웹 디자인 파이프라인 스킬과 이를 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)
10 KiB
tokens — 디자인 토큰과 성능 예산
3단계에서 읽는다. 코드를 쓰기 전에 숫자를 정한다. 구현하면서 색을 고르면 매번 다른 색이 나온다.
토큰은 취향이 아니라 계약이다. 한번 정하면 이후 모든 값은 여기서 나온다. 하드코딩된 값이 하나라도 있으면 수정 요청 한 번에 무너진다.
1. 타입 스케일
비율을 먼저 고른다
| 비율 | 값 | 인상 | 언제 |
|---|---|---|---|
| Minor Third | 1.200 | 차분, 조밀 | 정보 밀도가 높은 페이지, 문서, 대시보드 |
| Major Third | 1.250 | 안정 | 기본값. 대부분의 마케팅 사이트 |
| Perfect Fourth | 1.333 | 또렷한 위계 | 랜딩 페이지, 제품 소개 |
| Golden | 1.618 | 극적 | 포트폴리오, 에디토리얼. 중간 단계가 비어 본문이 외로워진다 |
한 페이지에 비율은 하나다. 헤드라인만 다른 비율을 쓰고 싶으면 그건 비율이 아니라 디스플레이 사이즈를 따로 정의하는 것이다.
실제 값으로 적는다
:root {
/* Perfect Fourth (1.333), 본문 16px 기준 */
--step--1: 0.75rem; /* 12 — 캡션, 레이블 */
--step-0: 1rem; /* 16 — 본문 */
--step-1: 1.333rem; /* 21 — 리드 문단, 소제목 */
--step-2: 1.777rem; /* 28 — h3 */
--step-3: 2.369rem; /* 38 — h2 */
--step-4: 3.157rem; /* 51 — h1 */
--step-5: 4.209rem; /* 67 — 디스플레이 */
}
반응형은 clamp 로, 미디어쿼리로 하지 마라
--step-4: clamp(2.25rem, 1.5rem + 3.75vw, 3.157rem);
clamp(최소, 기준+vw, 최대). 최소값은 모바일에서 읽히는 크기, 최대값은 데스크톱 기준. 중간이 매끄럽게 이어져 중간 뷰포트에서 깨지지 않는다.
함께 정해야 하는 것
| 토큰 | 규칙 |
|---|---|
--leading-tight |
1.1~1.2 — 디스플레이/헤드라인 |
--leading-normal |
1.5 |
--measure |
한 줄 길이. 라틴 60 |
| 자간 | 큰 글자에만 음수(-0.02em 정도). 한글에는 음수 자간 금지 |
폰트는 최대 2종. 세 번째 폰트를 넣고 싶다면 그건 위계를 웨이트로 못 만들고 있다는 신호다.
2. 색
역할로 정의한다. 팔레트로 정의하지 마라
"파랑 5단계"가 아니라 무엇에 쓰이는 색인지로 정의한다.
:root {
--surface: /* 페이지 바탕 */
--surface-raised: /* 카드·패널 (바탕과 구분되되 튀지 않게) */
--ink: /* 본문 텍스트 */
--ink-muted: /* 보조 텍스트 — 대비 4.5:1 유지 */
--line: /* 경계선 */
--accent: /* 강조 — 페이지당 하나 */
--accent-ink: /* 강조 위에 올라가는 글자색 */
}
규칙
- 강조색은 하나다. 두 개가 필요하다고 느끼면 위계 설계가 실패한 것이다. 예외: 상태색(성공/경고/오류)은 강조색이 아니라 기능색이다
- 채도가 높은 색은 면적을 좁게. 넓은 면적에 쓰면 눈이 피로하고 싸구려로 보인다
- 중성색도 색이다. 순수 회색(
#808080) 대신 강조색 쪽으로 약간 기운 중성색을 쓰면 화면 전체가 하나로 묶인다 - 대비를 측정해라. 본문 4.5:1, 큰 글자 3:1. 눈으로 판단하지 마라
다크 모드
다크가 슬롭인 게 아니라 고르지 않은 다크가 슬롭이다. (antipatterns.md §1 — bun.sh 는 다크인데도 슬롭 항목을 거의 전부 회피한다)
다크를 기본으로 하려면 근거를 한 줄로 대라. 정당화되는 이유의 목록은 presets/dark-instrument.md 에 있다(야간 운영 환경, 밝은 데이터 시각화의 대비, 제품 자체가 어두움). 댈 수 없으면 라이트로 간다.
어느 쪽을 기본으로 하든 두 테마를 동등하게 정의한다. 다크만 만들고 라이트를 빼는 것은 사용자 선택권을 뺏는 것이다.
지원할 때는 색을 뒤집는 게 아니라 역할별로 다시 정의한다. 다크에서 순수 검정(#000)은 대비가 너무 세서 눈이 아프고, 순수 흰색 텍스트도 마찬가지다.
:root { --surface: #fbfaf8; --ink: #1a1917; }
@media (prefers-color-scheme: dark) {
:root { --surface: #14130f; --ink: #e8e4dc; }
}
3. 간격
하나의 리듬에서 파생시킨다
:root {
--space-1: 0.25rem; /* 4 */
--space-2: 0.5rem; /* 8 */
--space-3: 1rem; /* 16 */
--space-4: 1.5rem; /* 24 */
--space-5: 2.5rem; /* 40 */
--space-6: 4rem; /* 64 */
--space-7: 6rem; /* 96 */
--space-8: 10rem; /* 160 — 섹션 간격 */
}
중간 값을 즉석에서 만들지 마라. --space-4 와 --space-5 사이가 필요하다면 스케일이 잘못된 것이다.
여백이 위계를 만든다
- 관련 있는 것끼리는 가깝게, 다른 그룹과는 확실히 멀게. 애매한 중간 간격이 가장 나쁘다
- 섹션 간격은 본문 간격의 4배 이상. 좁으면 페이지가 뭉개진다
- 요소를 정렬할 때 간격이 아니라 정렬선을 먼저 맞춰라
3-b. 형태 (radius·선)
하드 게이트 #3 이 검사하는 대상이다. 정의하지 않으면 검사할 수 없다.
:root {
--radius-sm: 2px; /* 입력, 배지 */
--radius-md: 8px; /* 카드, 패널 */
--radius-pill: 999px;
--line-width: 1px;
}
서로 다른 radius 값은 3종 미만으로. 어휘가 많을수록 우연히 결정된 것으로 보인다.
1종만 쓰는 것도 정당한 선택이다 — swiss-minimal·anti-grid 에서는 오히려 그쪽이 맞다.
4. 모션 토큰
:root {
--dur-instant: 100ms; /* 상태 변화 — 호버, 포커스 */
--dur-quick: 200ms; /* 작은 요소 등장/퇴장 */
--dur-normal: 350ms; /* 패널, 모달 */
--dur-slow: 600ms; /* 페이지 전환, 큰 이동 */
--ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* 들어오는 것 — 기본값 */
--ease-in: cubic-bezier(0.64, 0, 0.78, 0); /* 나가는 것 */
--ease-soft: cubic-bezier(0.4, 0, 0.2, 1); /* 위치 이동 */
}
상세는 references/motion.md. 여기서는 값을 고정하는 것이 목적이다. 컴포넌트마다 다른 duration 을 쓰면 페이지가 불안해 보인다.
5. 성능 예산
여기서 정한다. 구현 후에 재면 이미 늦었다.
이 표가 성능 예산의 유일한 원본이다. SKILL.md 도 preflight.md 도 여기를 가리킨다. 다른 문서에 예산 표를 만들면 값이 갈라지고, 갈라진 순간 아무도 어느 쪽이 맞는지 모른다.
| 항목 | 기본 | 데모/포트폴리오 | 5단계에서 |
|---|---|---|---|
| 히어로까지 JS (gzip) | 150KB | 400KB | 측정 |
| WebGL/3D 추가분 | +200KB | +600KB | 측정 |
| 총 전송량 (첫 화면) | 1MB | 2MB | 측정 |
| 첫 인터랙션 (모바일 4G) | 3초 | 5초 | 측정 |
| LCP | 2.5초 | 3.5초 | 측정 |
| CLS | 0.1 | 0.1 | 측정 |
| 폰트 패밀리 | 2개 이하 | 3개 | 개수 확인 |
| 폰트 전송량(첫 화면) | 100KB | 200KB | 측정 |
| 애니메이션 속성 | transform/opacity 만 | 동일 (타협 없음) | grep |
예산을 올렸다면 올렸다는 사실과 이유를 명시해라. 조용히 넘기는 것이 가장 나쁘다.
폰트는 파일 개수가 아니라 패밀리 수와 전송량으로 센다. 한글 웹폰트를 유니코드 범위별로 수십 개 파일로 쪼개는 것은 올바른 최적화다(Pretendard
dynamic-subset은 14개 이상). 브라우저는 페이지에 실제로 쓰인 글자 범위만 받는다. 파일 개수를 줄이라고 요구하면 한글 프로젝트를 단일 대용량 파일이라는 잘못된 방향으로 몬다.
LCP·CLS 를 어떻게 재나
preflight.md 가 이 값을 요구한다. 재는 법이 없으면 "미측정"으로 남고, 미측정은 통과가 아니다.
<!-- 페이지에 임시로 넣고 콘솔을 본다. 측정 후 반드시 제거한다 -->
<script>
new PerformanceObserver((l) => {
const e = l.getEntries().at(-1);
console.log('LCP', Math.round(e.startTime), e.element);
}).observe({ type: 'largest-contentful-paint', buffered: true });
let cls = 0;
new PerformanceObserver((l) => {
for (const e of l.getEntries()) if (!e.hadRecentInput) cls += e.value;
console.log('CLS', cls.toFixed(3));
}).observe({ type: 'layout-shift', buffered: true });
</script>
프로덕션 빌드에 돌려라. 개발 서버는 번들이 다르고 HMR 스크립트가 섞여 값이 의미 없다. Lighthouse 를 쓸 수 있으면 그쪽이 더 정확하다 — 단 모바일 프로파일로.
예산을 지키는 기본 수단
- 폰트:
font-display: swap, 서브셋(한글은 필수 — 전체 한글 폰트는 수 MB다),preload는 실제로 첫 화면에 쓰는 것만 - 이미지: 실제 표시 크기의 2배까지만, AVIF/WebP, 첫 화면 밖은
loading="lazy" - JS: 첫 화면에 필요 없는 것은 전부 지연 로드. 3D는 뷰포트 진입 시 동적 import
- 측정하지 않은 최적화는 하지 마라. 대신 예산을 넘겼는지는 반드시 측정해라
6. 산출물 형식
3단계를 마치면 아래가 실제 값으로 채워져 있어야 한다. 이것이 4단계의 입력이다.
:root {
/* 타입 — 비율 ____ , 디스플레이는 스케일 밖 별도 정의 여부 ____ */
--step--1 ~ --step-5, --leading-*, --measure
/* 색 — 역할별. 다크 테마도 함께 */
--surface, --surface-raised, --ink, --ink-muted, --line, --accent, --accent-ink
/* 간격 */
--space-1 ~ --space-8
/* 형태 — 하드 게이트 #3 이 이걸 검사한다 */
--radius-*, --line-width
/* 모션 */
--dur-*, --ease-*
}
그리고 한 줄로: 성능 예산 = JS ___KB / 첫 인터랙션 ___초 / 폰트 패밀리 ___개
정의하지 않은 토큰은 5단계에서 검사할 수 없다. 쓰지 않을 항목은 "쓰지 않음"이라고 적어라 — 빈칸과 "없음"은 다르다.
근거: research/references/03-trends-2026.md, 04-ai-slop-signatures.md (조사일 2026-08-20)