@askewly/design 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- [{"id":"adjacency-level-lod","title":"Adjacency-Level LOD","description":"Choosing resolution tiers by grid topology distance (Chebyshev ring from the focused cell) instead of screen distance in pixels, so the sharp region keeps the same cell-count shape at any zoom, cell size, or viewport; includes the neighbor-count identity, tier atlas ratios, and the speed-gated promotion rule","tags":["lod","grid","navigation","performance","canvas"],"source_path":"knowledge/adjacency-level-lod.md","body":"# Adjacency-Level LOD — 해상도 등급을 화면 거리가 아니라 격자 위상 거리로 정한다\r\n\r\nDate: 2026-08-17\r\nMilestone: M60 (plan: `plans/2026-08-17-m60-round6-prep-and-maintenance.md`, D3)\r\n지위: 살아있는 정본. 관측 근거는 `research/m59-round5/p2-nothing-to-watch.json` 행 `r5-06`(동결 기록) — 이 문서는 판정 규칙만 담고 수치의 출처 서술을 중복하지 않는다.\r\n출처: There's nothing to watch (`https://nothing-to-watch.port80.ch/`) — 자체 WebGL2 엔진 voroforce. 회차 5 소진형 심층에서 0.5층 정적 직독으로 파라미터 전 구간 확보.\r\n\r\nAudience: 격자·타일·목록을 다루는 탐색 UI를 만드는 에이전트. \"어느 셀을 몇 배 해상도로 그릴 것인가\"를 판정하는 데 쓴다.\r\n\r\n## 왜 데모가 아니라 지식으로 착지했나\r\n\r\n이 기법은 **값어치가 화면에 나타나지 않는다.** 재현 데모를 만들어 사용자 관측에 올렸으나 \"아름답지 않다 + 흡수할 의미를 찾아야 하나\"로 보류됐다(M59 회차 5). 절차 생성 타일로는 등급이 갈리는 것만 보이고 **왜 이 방식이 옳은지**가 보이지 않기 때문이다. 규칙 자체의 이식 가치는 높으므로 계약 §1 「시각이 아니라 규칙이 값어치인 기법」 경로를 따라 지식으로 착지시켰다. 구현(`code_asset`)은 없다 — recipe 를 신설하지 않은 이유다.\r\n\r\n## 규칙\r\n\r\n**초점 셀에서 격자상 몇 칸 떨어졌는가로 등급을 정한다. 화면상 몇 픽셀 떨어졌는가로 정하지 않는다.**\r\n\r\n열 방향 거리와 행 방향 거리를 각각 임계와 비교하고, 둘 다 임계 안이면 그 등급이다. 이것은 유클리드 원이 아니라 **체비쇼프 사각 링**이다.\r\n\r\n| 등급 | 판정 | 관측 임계 (원본) |\r\n|---|---|---|\r\n| v3 (최고 해상도) | 열 거리 ≤ 임계 **그리고** 행 거리 ≤ 임계 | 열 1 · 행 1 — \"초점 바로 옆\" |\r\n| v2 (중간) | v3 밖이면서 같은 방식으로 임계 안 | 열 18 · 행 18 |\r\n| v1 (기본) | 그 밖 전부 | — |\r\n\r\n**이웃 수 상한이 판정 방식의 방증이다**: 관측된 `MAX_NEIGHBORS_LEVEL_1 8` · `LEVEL_2 24` · `LEVEL_3 48` 은 각각 (2k+1)²−1 (k=1,2,3)과 정확히 일치한다. 체비쇼프 반경 k의 사각 블록에서 자기 자신을 뺀 수다. 유클리드 거리였다면 이 수가 나오지 않는다.\r\n\r\n## 무엇을 얻는가\r\n\r\n- **선명 영역의 모양이 불변이다.** 셀이 크든 작든, 뷰포트가 넓든 좁든, 줌이 어떻든 선명한 곳은 항상 같은 **칸 수**의 사각 블록이고 커서를 옮기면 그 블록이 통째로 따라간다. 픽셀 기준이면 줌·리사이즈마다 선명 영역의 셀 개수가 변해 사용자가 \"몇 개까지 또렷한가\"를 학습할 수 없다.\r\n- **로딩 예산이 해상도와 무관하게 고정된다.** 등급별 셀 수가 격자 위상만으로 결정되므로 고해상도 자산의 동시 상주 개수 상한이 화면 크기와 분리된다.\r\n\r\n## 등급별 자산 배율 (관측)\r\n\r\n원본은 3단 아틀라스를 미리 굽는다 — low `512×104` 타일(타일당 4×6px) · mid `90×60`(22×33px) · high `18×12`(110×165px). **타일 픽셀비가 대략 4 : 22 : 110**이고, 등급 간 한 단계가 5배 안팎이다. 이식할 때 이 절대 수치를 옮길 필요는 없다 — 옮길 것은 **등급이 3단이고 단계 간 배율이 5배 안팎**이라는 구조다. 아틀라스 생성 파이프라인 자체는 빌드 시점 자산이라 이식 대상이 아니다(장부 판정 \"축소이식\").\r\n\r\n## 승격 보류 — 빠르게 훑는 동안은 등급을 올리지 않는다\r\n\r\n관측: `handleMediaSpeedLimits` 가 preview 모드에서는 켜지고 select 모드에서는 꺼진다. **포인터가 빠르게 움직이는 동안에는 고해상도 승격을 미루고, 멈춘 뒤에 올린다.** 훑는 중에는 어차피 읽히지 않으므로 승격 비용이 낭비되고, 되돌리는 강등까지 겹치면 진동한다.\r\n\r\n⚠ **속도 임계의 실제 수치는 미확인이다** — 존재와 동작 방향만 관측됐다. 이식 시 직접 정해야 한다.\r\n\r\n## 언제 쓰지 않나\r\n\r\n- 격자가 작아 전부 최고 해상도로 그려도 예산이 남으면 등급 자체가 불필요하다.\r\n- 항목이 격자에 놓이지 않는 배치(자유 배치·힘 기반 레이아웃 중 정렬되지 않은 상태)에서는 \"몇 칸\"이 정의되지 않는다. 이때는 픽셀 거리로 돌아가는 것이 맞다.\r\n- 사용자가 초점을 옮기지 않고 훑기만 하는 표면(스크롤 목록)에서는 초점 셀이라는 개념이 약해 이득이 작다.\r\n\r\n## 관련\r\n\r\n`knowledge/dashboard-density.md`(밀도 판정) · `knowledge/expressive-stack.md`(렌더 티어) · 계약 `docs/design-system/reverse-engineering-contract.md` §1(규칙형 기법의 착지 판정 — 이 문서가 그 첫 사례다).\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 — M59 회차 5 보류분 `adjacency-level-lod` 의 지식 착지(D3 사용자 확정). 데모는 제거하고 규칙만 남긴다."},{"id":"checkout-flow","title":"Checkout Flow","description":"Checkout & conversion-flow rules (Stripe): friction is re-entry not step count — wallet one-click path above the form, never block autofill, address autocomplete as validation; inline validate-while-typing with descriptive localized errors and preemptive typo highlights; trust framing inside the surface (policy links, statement descriptor notice); payment methods/currency/language chosen dynamically by context; upsell restraint (one inline add-on, no forked pages)","tags":["commerce","checkout","forms","stripe"],"source_path":"knowledge/checkout-flow.md","body":"# Checkout Flow — 결제·전환 플로우 판정 규칙\n\nDate: 2026-08-01\nMilestone: M9 (plan: `plans/2026-08-01-m9-stripe-checkout-absorption.md`)\n지위: 결제·체크아웃처럼 전환이 걸린 플로우 표면의 판정 정본. 근거 원문은 `research/2026-08-01-m9-stripe-checkout-capture.md` 에 동결(Stripe Elements·Checkout, 접근 2026-08-01). 확인 모달·다이얼로그 규칙은 [[mobile-navigation]] §3 소관(액션 ≤2·구체 동사) — 이 문서는 플로우 고유 규칙만 담는다. 폼 일반 밀도·위계는 [[dashboard-density]] 소관.\n\nAudience: 에이전트(체크아웃·결제·가입 등 전환 플로우 생성 시 필수 참조) + 사람.\n\n## 0. 대원칙 — 마찰의 본체는 재입력이다\n\n단계 수를 줄이는 것보다 **입력을 없애는 것**이 우선이다. 같은 정보를 두 번 치게 하는 순간이 이탈 지점이고, 단일/다단 레이아웃 선택은 그 다음 문제(기기·태스크 무게로 결정)다.\n\n## 1. 입력 마찰 제거 (우선순위 순)\n\n1. **건너뛰기 경로 먼저** — 원클릭 지불(월렛·저장 결제) 버튼을 수동 폼 **위에** 병렬로 배치하고 \"다른 방법으로 결제\" 구분선으로 폼과 나눈다. 폼은 폴백이다.\n2. **저장·재사용** — 반환 사용자는 지불·배송 정보 자동채움 + 블록별 \"변경\" 진입점. 새 기기 검증은 일회용 코드로.\n3. **자동채움을 막지 않는다** — 브라우저·서드파티·인증 서비스 자동채움 호환은 설계 요건이다(autocomplete 속성·표준 필드 시맨틱).\n4. **주소는 자동완성 + 확정 블록** — 타이핑 절감이자 오배송·세금 오적용 방지 검증.\n5. 모바일: 숫자 입력엔 동적 숫자 키패드, 카드 스캔 경로 제공.\n\n## 2. 검증·에러 계약\n\n1. **타이핑 중 인라인 검증** — 제출 시 일괄 에러 금지. 에러는 발생 필드 옆, 서술형(\"무엇을 어떻게\")·로컬라이즈.\n2. **예측 가능한 실수는 선제 하이라이트** — 이메일 도메인 오타, 카드 브랜드 불일치.\n3. **입력 보조 = 신뢰 신호** — 카드 첫 자리로 브랜드 아이콘 표시, 브랜드별 CVC 힌트, 마스킹. 사용자가 \"시스템이 내 카드를 안다\"고 느끼게.\n4. 결제 폼 접근성(ARIA·포커스)은 기본 탑재 전제 — 포커스·키보드 규율 상세는 focus-keyboard 정본(M10) 참조.\n\n## 3. 신뢰 프레이밍\n\n전환 플로우 표면 **안에** 신뢰 신호를 심는다:\n- 반품 정책·지원 연락처·약관 링크 (별도 페이지로 쫓아내지 않는다)\n- **명세서 표기명 안내** — \"결제 내역에 X 로 표기됩니다\" (분쟁·챠지백 예방)\n- 결제 후 확인 화면에도 같은 표기명 반복.\n\n## 4. 국제화 = 동적 선택\n\n결제수단·통화·언어는 고정 목록이 아니다 — 사용자 위치·선호로 **동적으로 고른다**(로컬 수단 노출이 전환을 올린다는 것이 실측 근거). 가격은 로컬 통화로 표시.\n\n## 5. 플로우 구조 규칙\n\n1. **통합 깊이는 명시적 스펙트럼으로** — hosted 전체 페이지 ↔ embedded 폼(단일/다단) ↔ 컴포저블 요소 ↔ 노코드 링크. 통제 수준과 구축 비용을 트레이드오프로 제시하고 하나를 고른다.\n2. **업셀 절제** — cross-sell 은 제안 1개 인라인, 수량 조정 인라인. 업셀이 체크아웃을 별도 페이지로 분기시키면 FAIL.\n3. 확인·취소 모달 규칙은 [[mobile-navigation]] §3 을 그대로 따른다.\n\n## 판정 절차 (에이전트 의무)\n\n1. 전환 플로우를 만들기 전에 §0 을 선언 — 이 플로우에서 사용자가 새로 입력해야 하는 필드 수를 세고, 하나씩 \"없앨 수 있나(§1)\"를 먼저 묻는다.\n2. 모든 입력 필드에 §2 검증 계약(인라인·서술형·선제 하이라이트) 적용 여부를 확인한다.\n3. 신뢰 신호(§3) 3종 중 최소 2종이 표면 안에 있는지 확인한다.\n4. 수치 주장(전환율 %)은 재인용하지 않는다 — 원리만 쓰고 우리 실측으로만 주장한다.\n\n## Changelog\n\n- 2026-08-01: 초판 (M9) — Stripe Elements·Checkout 실브라우저 캡처 기반."},{"id":"commerce-conventions","title":"Commerce Conventions","description":"What three large commerce services agree on and where they split: sort-order defaults, filter-rail density, whether the cart is a page or a drawer, the sub-44px stepper convention that should not be copied, and the full-bleed breakout trick; each rule tagged as convention signal or option signal","tags":["commerce","cart","filter","sort","density","accessibility"],"source_path":"knowledge/commerce-conventions.md","body":"# 커머스 관습 — 세 곳이 합의한 것과 갈린 것\r\n\r\nDate: 2026-08-17\r\nMilestone: M62 (plan: `plans/2026-08-17-m62-round7-product-commerce.md`, 처분표 클러스터 C8·C9·C10·C12)\r\n지위: 살아있는 정본. 관측 근거는 `research/m62-round7/p2-ledger.json`(63행 동결) + `research/m62-round7/disposition.json` — 이 문서는 **판정 규칙만** 담고 수치의 출처 서술을 중복하지 않는다.\r\n출처: 쿠팡(`https://www.coupang.com/`) · 이마트몰(`https://emart.ssg.com/`) · 아마존(`https://www.amazon.com/`) 대조 심층 + 오픈소스 스토어프론트 직독(`Shopify/dawn@258f00f643` MIT · `vercel/commerce@3761e52e60` MIT · `medusajs/nextjs-starter-medusa@9818886f06` MIT). 2026-08-17 관측.\r\n\r\n여기 담긴 것은 **데모로 만들 값이 없거나 기본기라서 자산이 되지 못한 판정 규칙**이다. 데모가 된 것은 recipe 로 갔다(`price-hierarchy-stack` · `cart-line-item-anatomy` · `add-to-cart-feedback-modes` · `popover-anatomy-variants` · `mobile-buy-bar-sheet` · `product-grid-density-presets` · `button-size-scale`).\r\n\r\n읽는 법: **관습 신호**는 세 곳이 수렴한 것이고 이식 가치가 높다. **선택지 신호**는 갈린 것이고 어느 쪽을 쓸지가 제품 판단이다. 수렴이 곧 올바름은 아니다 (아래 §3 이 그 반례다).\r\n\r\n## 1. 정렬 옵션의 순서 — 관습 신호\r\n\r\n기본값은 **추천·랭킹순**이고 그다음이 가격 오름 → 가격 내림 → 판매량 → 신상품이다. 쿠팡 5종, 이마트몰 7종(할인율순·리뷰많은순이 더 붙는다), 아마존도 같은 축을 네이티브 select 로 쓴다.\r\n\r\n- 가격순을 기본값으로 두지 않는다. 목록의 첫 화면이 최저가로 채워지면 그 화면이 카탈로그가 아니라 할인 코너로 읽힌다.\r\n- 오름차순이 내림차순보다 먼저 온다. 순서를 뒤집으면 사용자가 매번 두 번 읽는다.\r\n- 페이지당 개수 프리셋을 함께 두는 경우가 있다(이마트몰 40/60/80/100, 기본 80).\r\n\r\n## 2. 필터 레일의 밀도 — 관습 신호\r\n\r\n필터 영역은 본문보다 촘촘하다. 아마존 실측으로 레일 폭 291px, 체크박스 16×16px, 리스트 항목 높이 20px, 라벨 14px 이다. 이 밀도는 **선택지를 한 화면에 최대한 담는 것이 목적**이므로 본문의 여백 리듬을 그대로 가져오면 레일이 화면을 다 먹는다.\r\n\r\n- 필터를 팝오버로 감출 것인가는 갈린다. `vercel/commerce` 는 할인코드·필터를 **팝오버 대신 인라인 라디오 그룹**으로 펼쳐 둔다. 항목 수가 적으면 인라인이 낫다.\r\n\r\n## 3. 수량 스테퍼의 32px 은 베끼지 않는다 — 나쁜 수렴\r\n\r\n수량 조절 컨트롤이 아마존 32px, Dawn 3.2rem(32px), `vercel/commerce` h-9(36px)로 **세 곳 전부 WCAG 최소 터치 타깃 44px 미달**이다. 같은 `vercel/commerce` 의 닫기 버튼만 h-11 w-11(44px)로 정확히 충족한다.\r\n\r\n**규칙: 이 값은 관습으로 인용하되 이식할 때 44px 로 교정한다.** 수렴은 「많이들 그렇게 한다」는 사실이고 「그것이 옳다」는 근거가 아니다. 교정 사실을 자산에 드러내 두면 다음 사람이 다시 32px 로 되돌리지 않는다(`cart-line-item-anatomy` 가 그 방식을 쓴다).\r\n\r\n## 4. 장바구니를 드로어로 만들 것인가 — 선택지 신호, 그리고 규모의 함수\r\n\r\n- 대형 커머스 3곳은 장바구니를 **전체 페이지**로 준다. 쿠팡은 그 페이지에 01~04 진행 스텝퍼를 노출하고, 이마트몰은 `pay.ssg.com` 결제 서브도메인으로 아예 이동하며, 아마존은 `/gp/cart/view.html` 이다.\r\n- 드로어는 **오픈소스 스토어프론트의 관습**이다(Dawn 40rem 불투명 · `vercel/commerce` 390px 반투명).\r\n\r\n**규칙: 드로어를 고를 때 그 선택이 규모 판단임을 안다.** 담을 품목이 적고 구매 결정이 짧으면 드로어가 맥락을 지켜 준다. 품목이 늘고 배송·쿠폰·수량 조정이 붙기 시작하면 드로어의 폭이 곧 병목이 되고, 큰 서비스가 페이지로 간 이유가 그것이다.\r\n\r\n체크아웃 단계도 같은 축에서 갈린다. medusa 는 사이트 안에서 4단계 아코디언(Addresses → Shipping → Payment → Review, 스텝 간 32px)을 돌리고, Dawn·`vercel/commerce` 는 자체 스텝 0개로 호스티드 체크아웃에 위임한다. **자체 스텝을 만들 것인가는 결제 주체가 누구인가에 따라 정해진다.**\r\n\r\n## 5. 구현 팁 — 컨테이너 패딩을 상쇄하는 풀블리드 breakout\r\n\r\n모바일에서 섹션 하나만 화면 폭 전체로 빼려면 `width: 100vw` 에 **컨테이너 좌우 패딩만큼의 음수 마진**을 더한다(쿠팡 실측: `w-[100vw]` + `ml-[-16px]`, 컨테이너 패딩 16px). 부모의 패딩을 지우거나 레이아웃을 다시 짜지 않고 그 한 섹션만 빼내는 방법이다.\r\n\r\n주의: `100vw` 는 세로 스크롤바 폭을 포함하므로 데스크톱에서 가로 스크롤이 생길 수 있다. 이 트릭은 스크롤바가 폭을 차지하지 않는 모바일 구간에만 걸어라.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17 (M62 step-8): 신설. 회차 7 처분표의 지식 착지 3건(C8·C9·C10) + 구현 팁 1건(C12) + 나쁜 수렴 1건(C3 파생)."},{"id":"css-reset","title":"CSS Reset & Base Layer","description":"Reset and base-layer contract for non-Tailwind outputs: @layer isolation, mandatory restoration list after all:unset (focus ring first), lineage selection table, the modern sr-only pattern, percentage-based root font sizing — plus one rule that applies to every web surface including Tailwind: always reserve the scrollbar gutter (overflow-y:scroll AND scrollbar-gutter:stable, since the latter is inert while overflow is visible)","tags":["css","reset","base-layer","sr-only","accessibility"],"source_path":"knowledge/css-reset.md","body":"# CSS Reset & Base Layer — 리셋·베이스 레이어 계약\n\nDate: 2026-08-10\nMilestone: BK2 (plan: `plans/2026-08-10-bk2-batch-promotion.md` step-3)\n지위: 살아있는 정본. **Tailwind 가 없는 산출물**(단일 HTML 아티팩트·지면·이식 블록·손 CSS 데모)에서 리셋·베이스 레이어를 어떻게 까는가의 계약. Tailwind 표면에서는 Preflight 와 `sr-only` 유틸이 이 계약을 대신 충족한다 — 이 문서는 그 바깥의 사각지대용.\n근거: 『인터랙티브 웹 애니메이션』 3·6장 실측(`github.com/Youngjin-com/interactive_web@a2dfc627`, 접근 2026-08-08) + The New CSS Reset(`github.com/elad2412/the-new-css-reset`, MIT — gh api 확인 2026-08-08) + Scott O'Hara \"The anatomy of visually-hidden\"(`https://www.scottohara.me/blog/2023/03/21/visually-hidden-hack.html`, 접근 2026-08-10).\n\nAudience: 에이전트(Tailwind 밖 화면·지면 산출물 생성 시 필수 참조) + 사람.\n\n## 1. 리셋은 `@layer` 로 격리한다\n\n```css\n@layer base, components; /* 순서 선언이 먼저 */\n@layer base { /* 리셋 + 베이스 */ }\n```\n\n`@layer` 는 특정성보다 **한 층 위**에서 작동한다 — 레이어에 든 규칙은 특정성이 아무리 높아도 뒤 레이어·레이어 밖 규칙에 진다. 리셋을 `base` 레이어에 넣으면 **내 스타일이 리셋을 항상 이긴다**는 보장이 특정성 경쟁 없이 성립한다. (Tailwind Preflight 가 같은 구조다.)\n\n## 2. 리셋이 지우는 것 — 복구 목록은 의무다\n\n`all: unset` 계열(The New CSS Reset 등)은 User-Agent 스타일을 통째로 지운다. **같이 지워지는 것들은 되돌려 놓아야 한다:**\n\n| 지워지는 것 | 복구 의무 |\n|---|---|\n| **포커스 링**(`outline`) | **필수** — `:focus-visible { outline: ... }`. 배경색 변경만으로는 포커스 표시가 아니다([[focus-keyboard]] 시각 최소선). 반례 실측: 리셋 후 `:focus { background-color }` 만 복구(3장) |\n| `cursor: pointer` | 리셋이 revert 해 주는지 확인(The New CSS Reset 은 a·button 에 revert 포함) |\n| 리스트 마커·테이블 간격 | 콘텐츠 문서(지면·아티클)면 복구, 앱 UI 면 생략 가능 |\n| 폼 컨트롤 외형 | 커스텀하지 않을 컨트롤은 `revert` |\n\n**판정 질문: \"이 리셋을 깐 페이지에서 Tab 을 눌렀을 때 지금 어디인지 보이는가?\"** — 안 보이면 미완성이다.\n\n## 3. 계보 선택\n\n| 계보 | 성격 | 쓰는 경우 |\n|---|---|---|\n| Eric Meyer Reset | 요소별 0 초기화(2007 계보) | 신규 채택 안 함 — 역사 참조 |\n| **The New CSS Reset** (MIT) | `all: unset` + `display: revert` 전면 | 손 CSS 산출물의 기본 후보 — 단 §2 복구 목록 필수 |\n| Tailwind Preflight | modern-normalize 기반 + `@layer` 격리 | Tailwind 표면이면 자동 — 이 문서 비적용 |\n| 최소 리셋(box-sizing + margin 0) | `*{box-sizing:border-box}` 수준 | 지면·단일 아티팩트처럼 수명 짧은 산출물 — 전면 리셋이 과할 때 |\n\n기준: **산출물 수명과 커스텀 범위.** 오래 살고 컴포넌트가 많으면 전면 리셋+복구, 한 장짜리면 최소 리셋.\n\n## 4. `sr-only` — 현대 판본, 한 곳에서만\n\n```css\n.sr-only:not(:focus):not(:active) {\n clip-path: inset(50%);\n height: 1px; width: 1px;\n overflow: hidden;\n position: absolute;\n white-space: nowrap;\n}\n```\n\n- `clip: rect(0,0,0,0)` 은 폐기 속성 — `clip-path: inset(50%)` 로. **`white-space: nowrap` 이 필수다** — 없으면 1px 폭에서 단어마다 줄바꿈되어 일부 스크린리더 낭독이 끊긴다. `:not(:focus):not(:active)` 가드는 skip-link 처럼 포커스 시 나타나야 하는 요소를 겸용할 때를 위한 것. (Scott O'Hara, 접근 2026-08-10)\n- **정적 콘텐츠 전용이다** — 인터랙티브 컨트롤을 이걸로 숨기지 않는다(1px 터치 타깃이 되어 터치 탐색 사용자가 못 찾는다. 같은 출처).\n- **파일마다 손 복제 금지** — 공용 베이스 한 곳에. 반례 실측: 같은 블록이 파일 3곳에 복제(3장), head 손 복제와 같은 병([[head-meta 반례]] — `docs/design-system/head-meta.md`).\n\n## 5. 루트 폰트 크기 — px 고정은 사용자 설정을 덮는다\n\n```css\n/* 금지 */ html { font-size: 16px; }\n/* 계약 */ html { font-size: 100%; } /* + 치수는 rem */\n```\n\n브라우저 기본 글꼴을 키워 둔 사용자(저시력·고령)의 설정은 `font-size: 16px` 로 **덮인다.** `100%` 기준 + `rem` 치수면 사용자 설정이 배수로 존중되면서 같은 스케일링이 된다. 반응형 축소도 px 재지정(`14px`·`12px`)이 아니라 **% 로**(`87.5%`) — px 로 내리면 확대 사용자에게 원래 설정의 절반이 될 수 있다. 반례 실측: 16→14→12px 3단 고정(6장).\n\n## 6. 스크롤바 거터 — 이 문서에서 유일하게 Tailwind 표면에도 걸린다\n\n> ⚠ **범위 예외.** 이 문서의 나머지는 Tailwind 가 없는 산출물용이고 Tailwind 표면은 Preflight 가 대신 충족한다. **이 절은 아니다** — Preflight 는 스크롤바 거터를 다루지 않으므로 Tailwind 프로젝트도 직접 걸어야 한다.\n\n**웹 산출물의 기본값으로 항상 넣는다** (사용자 확정 2026-08-12 — \"매번 반복되는 문제라 애초에 만들 때 고려하도록\").\n\n```css\nhtml {\n overflow-y: scroll;\n scrollbar-gutter: stable;\n}\n```\n\n**두 줄이 함께여야 한다.** `scrollbar-gutter: stable` 만 쓰면 아무 일도 일어나지 않는다 — 그 속성은 overflow 가 `visible` 일 때 효과가 없고, `visible` 이 루트 요소의 기본값이다. 선언은 있는데 거터는 안 잡히는 상태가 되고, 그게 제일 찾기 어렵다.\n\n**무엇을 막는가.** 세로 스크롤바는 콘텐츠 높이가 뷰포트를 넘을 때만 생긴다. 행이 펼쳐지거나, 필터가 목록을 줄이거나, 모달이 스크롤을 잠그면 그 경계를 넘나들고, 그때마다 **화면 전체가 스크롤바 너비만큼 옆으로 밀린다**(고전 스크롤바 기준 15~17px).\n\n**왜 이 정도로 규칙화하는가.** 밀림은 원인이 화면 밖에 있어서 **자산 결함으로 오진된다.** 실측 근거: M44 자산 관측(2026-08-12)에서 사용자가 서로 다른 자산 3종에 \"누를 때마다 사이트가 좌우로 흔들린다\"를 적었고, 셋 다 자산은 멀쩡했다. 같은 창에서 페이지 길이만 바꿔 재니 `documentElement.clientWidth` 가 2545 ↔ 2560 으로 뒤집혔다(장부 `research/2026-08-12-m44-inventory-verdicts.md` §1-1).\n\n**예외.** 세로 스크롤이 구조적으로 불가능한 전체화면 앱 셸(자체 스크롤 컨테이너만 쓰는 캔버스·키오스크)에서는 빈 거터가 낭비다. 그때는 스크롤을 소유한 컨테이너 쪽에 같은 두 줄을 건다 — 규칙은 \"루트에 걸어라\"가 아니라 **\"스크롤 주체에 거터를 예약해라\"** 다.\n\n## Changelog\n\n- 2026-08-12: M44 — §6 스크롤바 거터 신설(웹 산출물 기본값, 사용자 확정). 이 문서에서 유일하게 Tailwind 표면에도 걸리는 절이며, 두 줄이 함께여야 하는 이유와 자산 결함 오진 실측을 근거로 단다.\n- 2026-08-10: 초판 — BK2 V04(+V06 sr-only·V17 루트 폰트 합병). 근거 장부 `research/2026-08-10-bk2-promotion-verdicts.md`."},{"id":"customizer-and-packaging","title":"Customizer and Packaging","description":"Customizer and packaging rules for handing the system to someone else: which axes to expose (tone, accent, type direction) vs freeze (semantic token names, spacing scale, state strengths, contrast floor), why a tone preset carries radius rather than color alone, what makes generated output actually portable (one editable file, both light and dark faces, variable definitions shipped together with their utility mapping), and the four-part package claim (stack, assets, verification, round-trip)","tags":["packaging","customizer","portability","tokens"],"source_path":"knowledge/customizer-and-packaging.md","body":"# Customizer & Packaging — 시스템을 남에게 넘길 때의 규칙\n\n> 이식 가능한 제품(북극성)의 두 표면에 대한 규칙: **받는 사람이 무엇을 만질 수 있게 하는가**(customizer)와\n> **받는 사람에게 무엇을 약속하는가**(packaging). 근거 레퍼런스는 `research/around-template-system-capture.md`\n> (Around/Createx 실사 2026-07-04)와 그 재판정 `research/2026-08-06-m34-around-reverdict.md` — 원문을 여기 옮기지 않는다.\n\n## 1. 컨트롤로 노출할 것 / 고정할 것\n\n디자인 시스템을 남의 레포에 입힐 때 \"전부 조절 가능\"은 시스템이 아니라 설정 화면이다. 반대로 전부 고정하면\n그건 우리 브랜드지 그들의 제품이 아니다. 경계는 **정체성 축은 열고, 일관성 축은 닫는다**로 긋는다.\n\n**연다 (받는 사람의 정체성)**\n\n- **톤** — 지면의 성격(차분/따뜻/기술적). 색 하나가 아니라 배경·전경·카드·muted·border 가 **한 벌로** 움직인다.\n- **강조색** — 브랜드가 가장 먼저 드러나는 자리.\n- **타이포 방향** — 서체 *한 가족*. 위계는 굵기·크기로 만들고, 가족을 늘려 만들지 않는다.\n\n**닫는다 (시스템의 일관성)**\n\n- 의미 토큰의 **이름과 역할**(`--background`·`--muted-foreground`·`--scrim` …). 이름이 흔들리면 이식 계약이 깨진다.\n- 간격 스케일, 상태(hover/focus/disabled)의 강도 규칙, 대비 하한.\n- 컴포넌트가 **의미 토큰만** 참조한다는 규칙(원시값·리터럴 금지).\n\n## 2. 프리셋은 색만 묶지 않는다 — radius 를 함께 묶는다\n\n톤을 고르면 **모서리 반경도 함께 정해진다.** 색만 바꾸고 radius 를 고정하면 \"차분한 톤\"과 \"기술적인 톤\"이\n같은 얼굴을 갖는다 — 톤은 색의 문제가 아니라 *형태의 온도*까지 포함하기 때문이다.\n같은 이유로 그림자 강도·테두리 대비도 톤 프리셋의 구성원이지 별도 질문이 아니다.\n\n> 우리 구현: `packages/cli/src/kickstart.ts` 의 `CANVASES` 3종이 light/dark 쌍 + `radius` 를 한 덩어리로 갖는다.\n> 질문은 3개(톤·강조색·타이포)이고, 그 3개가 브랜드 CSS 변수 전체를 결정한다.\n\n## 3. 생성 결과는 \"복사 가능\"해야 한다\n\ncustomizer 의 값어치는 미리보기가 아니라 **가져갈 수 있는 산출물**에 있다. 규칙:\n\n- 산출은 **읽고 고칠 수 있는 파일 하나**로 나온다(우리: `src/askewly-brand.css`). 설정 UI 안에만 사는 상태는 이식물이 아니다.\n- 산출에는 **라이트·다크 두 면이 함께** 들어간다. 한 면만 주면 받는 쪽에서 반드시 다른 면을 손으로 채우게 되고,\n 그 순간 시스템이 갈라진다.\n- 변수 **정의**와 유틸 **매핑**은 한 몸으로 나간다 — 정의만 주면 유틸이 조용히 사라지고 빌드는 통과한다\n (M31 실증: `@theme inline` 누락 → `bg-scrim` 소멸). 이식 계약을 기계가 대조한다(`block-contract.md` §4).\n\n## 4. 아직 없는 것 (의도적 미보유 — 상한 표시)\n\nAround 는 갖고 우리는 없는 것 둘. 필요해지면 여기부터 검토한다.\n\n- **실시간 시각 프리뷰** — 우리는 질문에 답한 뒤 파일이 나온다. 고르는 동안 화면이 바뀌지 않는다.\n- **타이포 스케일 배수 조절** — 우리는 서체 스택만 고르고 배수는 고정이다(Around 는 `.75`~`1.375` 선택).\n 배수를 열면 대비·오버플로 검증을 그 축으로도 돌려야 한다 — 그 검증 없이 열지 않는다.\n\n## 5. 패키지 클레임 — 받는 사람에게 하는 약속\n\n\"이 시스템을 받으면 무엇이 따라오는가\"를 **한 자리에** 적는다. 절차 안내(next steps)는 약속이 아니다.\n최소 네 항목: **스택**(무엇 위에서 도는가) · **자산**(무엇이 실제로 들어오는가) · **검증**(무엇이 기계로 보장되는가) ·\n**왕복**(디자인 도구와 어떻게 오가는가). 우리 제품 문서의 해당 절은 `docs/PRD.md` 「제품 약속」이다.\n\n약속에 넣지 않는 것: 아직 실증되지 않은 능력, 결제·라이선스 강제(공개 탐색 경험과 에셋 모델이 정합해지기 전에는 붙이지 않는다)."},{"id":"dark-mode","title":"Dark Mode","description":"Dark mode judgment rules — definition (a redesigned palette, NOT an inversion), boundary vs night shift / forced-colors / inverted colors, design gates (no pure black, elevation-by-brightness, desaturation, semantic-token-only color), and web implementation gates (3-state model, FOUC head inline script, color-scheme, theme-color media split)","tags":["dark-mode","color","tokens","theming"],"source_path":"knowledge/dark-mode.md","body":"# Dark Mode — 정의·경계·판정 규칙\n\nDate: 2026-07-31\nMilestone: DM1 (plan: `plans/2026-07-31-dm1-dark-mode-knowledge.md`)\n지위: 살아있는 정본. \"다크모드가 무엇이고 무엇이 아닌가, 어떤 다크가 좋은 다크인가\"를 판정한다 — 특정 사이트에 어떻게 심는가(치환·배선 절차)는 각 구현 계획서 소관.\n근거: `research/2026-07-31-dark-mode-goal-dark-mode.md` (Apple HIG·MDN·Material 3·Primer·Tailwind 웹 리서치, 출처 URL+접근일 전건).\n\n## 1. 정의 — 다크모드는 \"반전\"이 아니라 별도 팔레트다\n\n다크모드는 **시스템 또는 사이트 전역의 외형 스킴**으로, 저조도 환경의 편안한 시야를 위해 **어두운 팔레트로 재설계된 별도 테마**다. 세 요소가 함께 있어야 다크모드다:\n\n1. **전역성** — 화면·뷰·메뉴·컨트롤 전반에 일관 적용되는 전역 상태 (Apple HIG는 systemwide appearance 로 정의).\n2. **목적** — 저조도 편의·눈부심 감소. 사용자 다수가 이를 기본값으로 선택하며, 앱이 이 선호를 존중하길 기대한다.\n3. **재설계** — 색만 뒤집는 게 아니라 대비 관계·표면 위계(elevation)·채도까지 다시 설계한 팔레트. \"배경을 검게, 글자를 희게\"의 기계적 반전은 다크모드가 아니다.\n\n(Apple HIG https://developers.apple.com/design/human-interface-guidelines/foundations/dark-mode/ · MDN https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme · Material 3 https://m3.material.io/blog/android-dark-theme-tutorial, 접근일 2026-07-31)\n\n## 2. 경계 — 다크모드가 아닌 것들\n\n혼동 빈도가 높은 인접 개념 4종. 축이 다르면 다른 물건이다.\n\n| 개념 | 축 | 다크모드와의 차이 |\n|---|---|---|\n| **Night Shift (색온도)** | 스펙트럼 | 색온도를 웜톤으로 이동해 블루라이트를 줄인다. 다크모드는 총 휘도를 낮추지만 스펙트럼은 안 바꾸고, Night Shift 는 스펙트럼을 옮기되 밝기는 안 낮춘다 — 독립 축, 동시 사용 가능 |\n| **High contrast / forced-colors** | 접근성 강제 | 사용자 지정 제한 팔레트를 페이지 전체에 **강제**하는 렌더링 모드. 저자 CSS 색을 대부분 무시한다. 다크모드 대응이 이걸 커버한다고 가정하면 안 된다 — `@media (forced-colors: active)` 별도 축 |\n| **Inverted colors (Smart/Classic Invert)** | 사후 반전 | OS 가 앱 지원 여부와 무관하게 색을 뒤집는 범용 오버라이드. 다크모드 = **개발자가 의도 설계한 테마**, Invert = 설계 없는 사후 처리 — 완성도 차이가 본질 |\n| **Dark theme** | 어휘 | 사실상 동의어. 굳이 가르면 mode=전역 상태 전환, theme=그 상태에 대응하는 팔레트·토큰 산출물 (추정 — 공식 표준이 엄격히 구분하지 않음) |\n\n(MDN forced-colors https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors · Apple https://support.apple.com/en-us/102191 · Smart Invert https://useyourloaf.com/blog/accessibility-smart-invert/, 접근일 2026-07-31)\n\n**따름 정리**: 다크모드는 선호(preference) 기능이지 접근성 기능이 아니다. WCAG 대비 검증은 라이트/다크 **각 테마에서 독립적으로** 통과해야 한다 — 라이트 통과가 다크 통과를 의미하지 않는다. (https://www.boia.org/blog/offering-a-dark-mode-doesnt-satisfy-wcag-color-contrast-requirements, 접근일 2026-07-31)\n\n**일반화 (2026-08-10 BK2)**: 이 규칙은 다크모드 전용이 아니다 — **팔레트를 스왑하는 모든 테마 시스템**(멀티 브랜드 테마·섹션별 컬러 테마 포함)에서 테마마다 독립 대비 검증이 의무다. 실물 반례 2건: ① 다크 팔레트에서 `--progress-color #172f35` 가 배경 `--section-color #162f35` 위에 놓여 사실상 비가시(토큰 2계층 구조는 옳았는데 **값을 테마별로 검증하지 않아** 무너짐) ② **라이트 테마 5종**에서 같은 실패 — `banana` 테마가 `#fcc000` 텍스트를 `#fff2d4` 배경에 + `opacity: .5` 중첩. (『인터랙티브 웹 애니메이션』 8·9장, `github.com/Youngjin-com/interactive_web@a2dfc627` 접근 2026-08-08 — 장부 `research/2026-08-10-bk2-promotion-verdicts.md` V25·V31)\n\n## 3. 좋은 다크의 판정 규칙 (디자인)\n\n1. **순수 검정 금지** — 배경 기본값은 `#000` 이 아니라 짙은 회색(Material 기준 `#121212`). 극단 대비는 halation(빛번짐)을 악화시키고, 비점수차 사용자(인구 약 47%가 어떤 형태로든 보유)에게 흰 텍스트가 번져 보인다. 텍스트도 순백 대신 오프화이트.\n2. **Elevation 은 그림자가 아니라 표면 밝기** — 라이트에서 그림자로 표현하던 높이를, 다크에서는 표면에 밝은 오버레이를 합성해 \"높을수록 밝게\"로 표현한다 (Material: 0dp 0% → 24dp 16% 선형).\n3. **채도를 낮춘다** — 어두운 배경 위 고채도 원색은 진동·번짐. 브랜드 색을 그대로 옮기지 말고 다크 전용으로 재조정한 값을 semantic 계층에 매핑한다.\n4. **모든 색은 semantic 토큰을 거친다** — 원시 hex·리터럴 팔레트 클래스를 컴포넌트에 직접 쓰면 다크는 구조적으로 불가능해진다. Primer 원칙: primitive → semantic 2계층 참조 강제, `:root` 스코프 스왑만으로 전체 전환. 본 레포 3-tier 토큰([[theme-token]] 참조 구조)과 동일 패턴.\n5. **이미지·일러스트는 별도 판정** — 라이트 전제 에셋(흰 배경 스크린샷·검정 텍스트 투명 PNG)은 다크에서 깨진다. 다크 전용 에셋 또는 SVG+`currentColor`.\n\n(Material 3 https://m3.material.io/blog/android-dark-theme-tutorial · Primer https://primer.style/product/getting-started/foundations/color-usage/ · astigmatism https://www.boia.org/blog/dark-mode-can-improve-text-readability-but-not-for-everyone, 접근일 2026-07-31)\n\n## 3-1. 재설계를 손으로 하지 않는다 — 생성 규칙 (M48 보강)\n\n§1-3 이 \"색만 뒤집는 게 아니라 재설계한 팔레트\"라고 못박았다. 이 절은 그 재설계를 **어떻게** 하느냐를 한 단계 위에서 정한다: 테마마다 값을 손으로 정의하지 말고, **소수의 입력값에서 나머지를 계산한다.**\n\n1. **테마 수에 비례해 정의가 늘면 그것은 값 문제가 아니라 생성 규칙 부재의 신호다.** semantic 층을 더 쪼개는 것은 곱셈의 밑을 키우는 대응이라 문제를 키운다. Linear 는 테마당 98개 변수 정의를 3개(base·accent·contrast)로 줄였다 — \"Instead of having to define 98 specific variables for each theme, we defined three: base color, accent color, and contrast.\" (https://linear.app/now/how-we-redesigned-the-linear-ui, 접근일 2026-08-12)\n2. **계산으로 옮기려면 색공간부터 지각 균일한 것으로 바꾼다.** sRGB·HSL 에는 \"같은 밝기 값 = 같아 보이는 밝기\"가 성립하지 않아, 계산 결과를 색상마다 손보다 결국 손 정의로 되돌아온다. Linear 는 LCH 로, Stripe 는 Lab 으로 갔다 — \"At the same level of mathematical lightness, yellow appears lighter than blue.\" · \"By translating our color scales into the Lab color space, we can adjust our colors based on their perceptual contrast\" (https://stripe.com/blog/accessible-color-systems, 접근일 2026-08-12)\n3. **대비는 검사가 아니라 구성으로 보장한다.** 스케일에 대비 규칙을 심으면 조합마다 재검사할 필요가 없다 — \"Any two colors are guaranteed to have sufficient contrast for small text if they are at least five levels apart.\" (Stripe, 같은 글)\n4. **접근성 축(대비)은 별도 테마가 아니라 생성 입력값으로 둔다.** 그래야 테마가 늘어도 고대비 대응이 따라 늘지 않는다 — \"The theme generation system also supports a contrast variable … This allows us to automatically include super high-contrast themes for users who need it for accessibility reasons.\" (Linear, 같은 글). Apple 도 대비 증가 설정까지를 색 정의 범위로 본다: \"Make sure all your app's colors work well in light, dark, and increased contrast contexts.\" (Apple HIG Color, 접근일 2026-08-12)\n5. **색은 의미 채널이다** — 같은 색에 두 뜻을 싣지 않는다. \"Avoid using the same color to mean different things.\" (Apple HIG Color)\n6. **탐색 단계에서는 색을 나중에 얹는다.** 구조가 흑백 불투명도만으로 읽히면 색은 계산이 채운다 (Linear 탐색 절차, 같은 글).\n\n> 이 절은 **채택 지시가 아니라 판정 규칙**이다. 색공간 전환·기존 토큰 마이그레이션 비용은 프로젝트가 따로 판단한다. 배경 지식은 kg 노드 `theme-variable-collapse-via-generative-color`.\n> 근거 장부: `research/2026-08-12-visual-benchmark-references.md` §5-1·§5-2. 소비처 = M44 자산 전수 점검 — **미실증**.\n\n## 4. 웹 구현 판정 규칙\n\n1. **상태 모델은 3-상태가 표준** — 라이트/다크/시스템. 명시 선택은 `localStorage` 저장, \"시스템\"은 키 제거 + `matchMedia(\"(prefers-color-scheme: dark)\")` 추종(+change 리스너). Tailwind 는 class/selector 전략(`@custom-variant dark`)과 이 패턴을 공식 레퍼런스로 문서화.\n2. **FOUC 는 head 인라인 스크립트로만 막힌다** — SSG/prerender 사이트에서 hydration 후 테마를 적용하면 첫 페인트에 잘못된 테마가 번쩍인다(FOUC, \"FART\"). `<head>` 안 동기 인라인 스크립트가 렌더 전에 `<html>` 클래스를 결정해야 한다 — 컴포넌트 코드로는 구조적으로 불가능.\n3. **`color-scheme: light dark` 선언** — 스크롤바·폼 컨트롤·시스템 색을 브라우저가 자동 대응(공짜 다크). 커스텀 컴포넌트는 커버 안 됨.\n4. **`meta theme-color` 는 media 분기** — `media=\"(prefers-color-scheme: dark)\"` 이중 meta 로 모바일 브라우저 크롬까지 테마 대응.\n5. **전환 transition 은 토글 시에만** — 상시 transition 은 초기 로드(FOUC 스크립트가 클래스를 붙이는 순간)에 불필요한 페이드를 만든다 (관행, 추정).\n6. **수동 토글과 OS 감지의 정합** — `<picture media=\"(prefers-color-scheme: dark)\">` 류 CSS/HTML 분기는 OS 설정만 본다. 사이트가 자체 토글로 테마를 강제하면 어긋난다 — 이미지 분기도 사이트 테마 상태를 따라야 한다.\n\n(Tailwind https://tailwindcss.com/docs/dark-mode · FOUC https://css-tricks.com/flash-of-inaccurate-color-theme-fart/ · color-scheme https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme · theme-color https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/theme-color · 이미지 https://rhyslloyd.me/serve-dark-mode-images-natively/, 접근일 2026-07-31)\n\n## 5. 참조 구현\n\n- **GitHub Primer** — semantic 토큰 강제·`:root` 스왑 전환의 정본. https://primer.style/product/getting-started/foundations/color-usage/\n- **Linear** — dark-first: 다크에서 elevation·표면 위계를 먼저 확정하고 라이트를 파생 — \"라이트 먼저, 다크는 나중에 끼워 맞추기\"의 실패를 구조적으로 회피. https://chyshkala.com/blog/why-linear-design-systems-break-in-dark-mode-and-how-to-fix-them\n- **Stripe Connect embedded** — 임베드 컴포넌트에 호스트 테마를 파라미터로 명시 주입·동적 동기화 — 격리된 콘텐츠 표면(데모 카드·iframe)에 테마를 전달하는 패턴. https://docs.stripe.com/connect/embedded-appearance-support-dark-mode\n- **Tailwind 공식 문서** — 3전략·3-상태 토글·FOUC 스니펫의 웹 구현 정본. https://tailwindcss.com/docs/dark-mode\n\n(전건 접근일 2026-07-31)\n\n## Changelog\n\n- 2026-08-12: M48 — §3-1 신설(생성 규칙으로의 재설계: 소수 입력값 파생·지각 균일 색공간·대비를 구성으로 보장·대비를 접근성 입력값으로). 근거 Linear·Stripe 공개 문서 + Apple HIG Color.\n- 2026-08-10: BK2 — §2 따름정리에 일반화(팔레트 스왑 전체) + 실물 반례 2건. V25·V31."},{"id":"dashboard-density","title":"Dashboard Density","description":"Dashboard density & low-noise hierarchy rules (Linear 2026 refresh x Geist): density is not noise — keep information, cut borders/icon excess/brightness competition; attention budget (nav recedes after arrival, compact chrome), whitespace-before-borders order, two-tier page background cap, hover/active from adjacent scale steps (never new hues), and the two-step text hierarchy rule","tags":["dashboard","density","hierarchy","application-ui"],"source_path":"knowledge/dashboard-density.md","body":"# Dashboard Density & Low-Noise Hierarchy — 밀도·소음 판정 규칙\n\nDate: 2026-08-01\nMilestone: M8 (plan: `plans/2026-08-01-m8-saas-exemplar-absorption.md`)\n지위: 대시보드·운영 도구처럼 정보 밀도가 높은 화면의 밀도·위계·소음 판정 정본. 근거 원문은 `research/2026-08-01-m8-dashboard-density-capture.md` 에 동결(Linear 2026-03 리프레시 원문 · Geist Colors) — 이 문서는 판정 규칙만 담는다. 모바일 내비게이션·모달 선택은 [[mobile-navigation]](`knowledge/mobile-navigation.md`) 소관 — 이 문서는 화면 안 밀도·위계만 다루고 컨테이너 선택은 다루지 않는다.\n\nAudience: 에이전트(대시보드·데이터 밀집 화면 생성 시 필수 참조) + 사람.\n\n## 0. 대원칙 — 밀도 ≠ 소음\n\n밀도가 높다는 이유로 정보를 빼지 않는다. 줄이는 것은 **소음**(보더·아이콘 과잉·밝기 경쟁)이고 지키는 것은 **정보 밀도**다. \"조밀해서 어수선하다\" 진단에 정보 삭제로 답하는 것은 오답이다.\n\n## 1. 주의 예산 (attention budget)\n\n| 요소 | 규칙 |\n|---|---|\n| 본문 작업 영역 | 화면에서 가장 높은 시각적 우선순위 — 항상 |\n| 사이드바·내비 | 도착 후에는 후퇴 — 본문보다 어둡게/저대비로 (\"earned attention\" — 주의는 얻은 만큼만) |\n| 앱 셸 탭·크롬 | 콤팩트하게 — 전폭 점유 대신 축소·라운드, 콘텐츠 밀도는 유지하고 크롬 밀도만 감축 |\n| 아이콘 | 엔티티 식별(프로젝트·상태 스캔)에만 — 장식 컬러 배경 금지·개수와 크기 절제 |\n\n## 2. 구조 표현 (structure felt, not seen)\n\n1. **구분의 기본값 = 여백.** 보더·구분선은 이유 없이 증식한다 — 여백 → 배경 미차 → 라운딩 순으로 먼저 시도하고 선은 최후 수단.\n - 이 순서는 우리 관측만의 것이 아니다. Apple 도 그룹 표현 수단을 같은 순서로 열거한다 — \"you might use **negative space, background shapes, colors, materials, or separator lines** to show when elements are related\" (Apple HIG Layout, 접근일 2026-08-12). 여백이 첫째고 선이 마지막이다.\n2. 선을 쓸 때는 저대비 + 그룹 경계에만(항목마다 긋지 않는다).\n3. 페이지 배경은 **2단 상한** — 2단째는 미묘한 구분이 필요할 때만 아껴서 (Geist Background 1/2).\n\n### 2-가. 저대비를 만드는 수단 — 색이 아니라 알파 (M48 보강)\n\n보더를 회색 **값**으로 잡으면 그 값은 특정 배경 위에서만 맞는다. 배경이 바뀌면 대비 관계가 깨지고, 다크 테마에서 다시 정의해야 한다. 검정·흰색의 **낮은 불투명도**로 잡으면 관계가 배경을 따라간다.\n\n실측: Attio 는 보더 색 최빈이 `rgba(0,0,0,0.05)`·`rgba(0,0,0,0.06)` 로 회색 값이 아니라 검정의 5~6% 다(https://attio.com, 접근일 2026-08-12). 반대로 Stripe 문서 표면은 저대비 청회색 3단으로 같은 목적을 값으로 달성한다(https://docs.stripe.com/payments, 접근일 2026-08-12) — 둘 다 성립하며, **알파 쪽이 테마가 늘 때 유지 비용이 낮다.**\n\n> 두께는 변수가 아니다. 관측된 저작값은 두 표면 모두 1px 단일이었고, 「subtle」을 만든 것은 두께가 아니라 대비다.\n\n### 2-나. 보더·반경·그림자를 따로 정하지 않는다 — 고도 프리셋 (M48 보강)\n\n반경과 보더를 각각 독립 축으로 고르면 조합이 폭발하고 \"이 카드는 왜 저 카드와 다른가\"에 답할 수 없게 된다. Vercel Geist 는 이 넷을 **하나의 고도(elevation) 역할**로 묶는다.\n\n> \"Presets for radii, fills, strokes, and shadows.\" · \"Use Material instead of hand-rolling radii, fills, strokes, and shadows on a surface; **the type encodes the elevation role.**\" (https://vercel.com/geist/materials, 접근일 2026-08-12)\n\n- **반경은 값이 아니라 단계다.** Geist 의 사다리는 3단이다 — 바닥·낮은 부상·툴팁이 한 단, 메뉴·모달이 한 단, 전체화면이 한 단. 값 자체는 프로젝트 소유이고, 규율은 **단계 수를 셀 수 있게 유지하는 것**이다.\n- **고도는 낮을수록 좋다.** \"Favor the lowest elevation that still reads as elevated against its background; **over-elevating is a common source of visual noise.**\" (같은 문서) — §0 의 \"소음을 줄인다\"와 같은 말이다.\n- **한 요소에 두 고도를 겹치지 않는다.** 자식에 더 높은 고도가 필요하면 자식을 자기 표면으로 올린다(같은 문서).\n- **반경의 절대값은 규범이 아니다.** 실측 최빈이 2px에서 12px까지 흩어졌다(§M48 장부 §7-4). Apple 도 값이 아니라 관계로 말한다 — \"make sure it harmonizes with the curvature of the hardware and aligns with adjacent safe areas\" (Apple HIG Layout, 접근일 2026-08-12). 톤 프리셋이 반경을 함께 묶는 우리 쪽 선례는 [[customizer-and-packaging]] §2.\n\n> 근거 장부: `research/2026-08-12-visual-benchmark-references.md` §5-3·§6·§8. 소비처 = M44 자산 전수 점검 — **미실증**.\n\n## 3. 상태·위계의 색 규칙\n\n1. **hover/active 는 새 색이 아니다** — 같은 중립 스케일의 인접 단계로 표현한다 (Geist: 배경 1–3 밴드·보더 4–6 밴드). 우리 3-tier 토큰에서는 semantic 단계 인접값 참조로 구현.\n2. **텍스트 위계는 2단** — primary/secondary. 세 번째 회색이 필요해 보이면 위계 설계 실패 신호로 읽고 구조를 다시 본다.\n3. 저소음 밀집 UI 의 중립색은 저채도로 — 채도는 상태·강조 채널에 아껴 쓴다.\n\n## 판정 절차 (에이전트 의무)\n\n1. 대시보드·밀집 화면을 만들기 전에 §0 을 선언한다 — 정보를 줄일지 소음을 줄일지 구분.\n2. 크롬(내비·탭·툴바)과 콘텐츠를 나눠 §1 주의 예산을 배분한다 — 크롬이 콘텐츠와 밝기 경쟁하면 FAIL.\n3. 구분선을 넣기 전에 §2-1 순서(여백→배경→라운딩→선)를 거쳤는지 확인한다.\n4. hover/active/보더 색이 스케일 밖 새 색이면 §3-1 위반 — 토큰 스케일 인접 단계로 교체.\n5. 스타일 수치(Linear 그레이 값·Geist 색 값)는 흡수 대상이 아니다 — look 은 프로젝트 토큰 소유.\n6. 보더를 넣기로 했으면 §2-가 대로 **알파를 먼저 검토**하고, 반경·그림자를 함께 정하고 있으면 §2-나 대로 **고도 단계 하나**로 묶었는지 확인한다. 화면의 고도 단계를 셀 수 없으면 FAIL.\n7. 여백·정렬 판정은 [[spacing-and-alignment]], 글자 위계는 [[type-hierarchy]] 가 소유한다 — 이 문서는 밀집 화면의 소음만 다룬다.\n\n## Changelog\n\n- 2026-08-12: M48 — §2-가(저대비는 색이 아니라 알파)·§2-나(보더·반경·그림자를 고도 프리셋 하나로) 신설, §2 구분 순서에 Apple HIG 교차 근거 추가, 판정 절차 6·7 추가.\n- 2026-08-01: 초판 (M8) — Linear 리프레시 원문(2026-03-12)·Geist Colors 실브라우저 캡처 기반."},{"id":"deck-layout-vocabulary","title":"Deck Layout Vocabulary","description":"Slide layout vocabulary harvested from the /pt production skill: 19 layouts classified by density (low/medium/high/portfolio) and kind (static/interactive), with selection rules — pick by information density first, split when over budget, interactive only when the interaction itself is the evidence","tags":["slides","deck","layout","vocabulary"],"source_path":"knowledge/deck-layout-vocabulary.md","body":"# Deck Layout Vocabulary — 슬라이드 레이아웃 어휘\n\nDate: 2026-08-10\nMilestone: M43 (plan: `archive/plans/2026-08-10-m43-pt-surface-audit.md`)\n지위: 덱을 만들 때 **어떤 슬라이드에 어떤 레이아웃을 쓰는가**의 어휘 정본. 콘텐츠·구조 원칙은\n[[slide-principles]], 캔버스·대비 게이트는 slide-spec(`docs/design-system/slide-spec.md`) 소관 —\n이 문서는 그 사이, \"구조가 정해진 메시지를 어떤 틀에 앉히는가\"를 답한다.\n출처: `/pt` 스킬 `templates/layout-meta.json` + 렌더러 실사(2026-08-10, M43 판정표\n`research/2026-08-10-m43-pt-surface-verdict.md`). **구현 정본은 /pt 스킬** — 여기서는 어휘만.\nharvest: 자기 산출물(custom-skills /pt), 실전 덱 다수 통과 운영 코드.\n\n## 분류표 — 밀도 × 종류\n\n밀도(density)는 한 슬라이드에 앉힐 수 있는 정보량의 예산이다. 레이아웃마다 항목 상한이 있고,\n**상한을 넘으면 레이아웃을 바꾸는 게 아니라 슬라이드를 쪼갠다**([[slide-principles]] 원칙 1 —\n메시지가 둘이면 슬라이드도 둘).\n\n| 밀도 | 레이아웃 | 항목 상한 | 언제 쓰나 |\n|---|---|---|---|\n| low | cover | 3 | 표지 — 제목·부제·소속. 진입 효과는 초점(focus) |\n| low | closing | 3 | 맺음 — 요약 한 줄·연락처. 페이드 |\n| low | hero-cards | 3 | 핵심 주장 1~3개를 카드로 크게 |\n| low | hero-motion | — | 모션이 곧 메시지인 히어로(효과 없음 선언 — 모션 자체가 효과) |\n| low | svg-filter-scene | — | SVG 필터 질감 씬 — 분위기 전환 |\n| medium | comparison-2col | 4 | 전/후·A/B 두 축 대조 |\n| medium | step-flow | 5 | 순서 있는 단계 흐름 |\n| medium | diagram-box | 7 | 박스·화살표 구조도 |\n| medium | summary-grid | 6 | 병렬 요약 격자 |\n| high | bento-grid | 8+ | 고밀도 벤토 — 크기 위계로 주(主)·부(副)를 가른다 |\n| portfolio | case-map | — | 사례를 지도·좌표로 배치 |\n| portfolio | split-screen | — | 좌우 분할 — 이미지 절반·텍스트 절반 |\n| portfolio | timeline-cards | — | 시간축 카드 나열 |\n| portfolio | pipeline-lanes | — | 레인별 파이프라인(역할·단계 교차) |\n| portfolio | result-transitions | — | 결과 장면 전환 시퀀스 |\n\n인터랙티브 4종은 **상호작용 자체가 증거일 때만** 쓴다([[slide-principles]] 원칙 3의 연장 —\n시각 증거의 강화형이지 장식이 아니다):\n\n| kind | 레이아웃 | 언제 쓰나 |\n|---|---|---|\n| interactive·data | chart-interactive | 실데이터 차트(Chart.js) — 숫자가 주장을 증명할 때 |\n| interactive·visual | three-scene | 3D 씬 — 공간·형태가 내용일 때 |\n| interactive·visual | before-after | 전/후 슬라이더 — 차이 자체가 메시지일 때 |\n| interactive·action | qr-embed | QR — 청중을 즉석에서 행동시킬 때 |\n\n## 선택 규칙\n\n1. **밀도 먼저** — 메시지의 항목 수를 세고 그 예산에 맞는 밀도 군에서 고른다. 항목이 상한을\n 넘으면 슬라이드를 쪼갠다(레이아웃 승급이 아니라 분할).\n2. **static 이 기본값** — interactive 는 조작이 증거일 때만. 발표장에서 조작이 실패하면\n static 폴백이 있는가를 먼저 묻는다.\n3. **진입 효과는 레이아웃이 이미 안다** — cover=focus·closing=fade·대부분=rise. 슬라이드마다\n 효과를 새로 고르지 않는다(리듬 일관 — [[motion-principles]] 시퀀싱 원칙과 같은 이유).\n4. **portfolio 군은 서사 슬라이드다** — 사례·경과·결과처럼 \"보여주며 말하는\" 자리에만. 논증\n 슬라이드(주장+증거)에 쓰면 밀도가 흩어진다.\n\n## Changelog\n\n- 2026-08-10: 초판 — M43, /pt layout-meta 19종 회수 (그릇 판정 A안: knowledge 1편, 사용자 확정)."},{"id":"dictionary-site-ia","title":"Dictionary Site IA","description":"Dictionary-site IA: dual YAML source-of-truth (content vs navigation), build-time cross-validation gate, reduced core schema, in-memory weighted search — harvested from a production dictionary site","tags":["ia","navigation","search","yaml"],"source_path":"knowledge/dictionary-site-ia.md","body":"# Dictionary-Site IA — 사전형 사이트 정보 구조\n\n> 지위: 용어·개념 사전형 사이트의 데이터→화면 구조 판정 규칙. 출처 = development-dictionary(dev.askewly.com) 실구현 채굴(harvest, 2026-08-04 — `research/2026-08-04-m22-harvest-mining-ledger.md` ⑩ 카드). 코드 벤더링이 아니라 구조 계약을 이식한다.\n\n## 핵심 계약 — 이중 SoT + 빌드 타임 교차검증\n\n| 층 | 정본 | 규칙 |\n|---|---|---|\n| 콘텐츠 | `terms.yml` (항목당 1레코드) | 화면이 아니라 데이터가 정본. 코어 필드 = `id`·`name(ko/en)`·`one_liner`·`category`·`related` — 그 외(설명 섹션·visual model·출처)는 선택 확장. 15필드 전체 복사는 과잉(원 구현 실측 소견) |\n| 내비 | `navigation.yml` (그룹·배치 별도 파일) | 콘텐츠와 **분리** — 같은 항목을 다른 축으로 재배치할 수 있어야 한다 |\n| 검증 | 빌드 스크립트 2본 | ① 전 필드·enum·출처 id 런타임 단언 ② **교차검증**: 모든 term 이 nav 에 배치됐는가 + 모든 nav 참조가 실존하는가 — 하나라도 어긋나면 빌드 FAIL (조용한 누락 불가) |\n\n## 화면 규칙\n\n- 상세 렌더는 데이터 분기: 설명 섹션이 있으면 커스텀 아티클, 없으면 제네릭 섹션 — 템플릿 강제가 아니라 데이터가 형태를 고른다. visual model(노드 배열)은 데이터에서 다이어그램을 직접 렌더.\n- 검색은 빌드 인덱스 없이 인메모리 가중 필드 스코어링(name > alias > category > one_liner)이면 수백 항목까지 충분. 한국어 자연어 질의는 수기 부스트 표로 보정.\n- 빈 검색 결과는 회복 UX(되돌리기 액션 + 추천 질의)를 붙인다 — 막다른 화면 금지.\n\n## 판정 절차\n\n새 사전형 사이트 의뢰 시: ① 코어 5필드로 terms 정의 ② 내비를 별도 파일로 ③ 교차검증 빌드 게이트 먼저 배선 ④ 확장 필드는 실수요 발생 시. 본 레포 실구현 선례 = `docs/ui-vocabulary/`(terms.yml·groups.yml) — 같은 사상.\n\n## Changelog\n\n- 2026-08-04: 초판 — M23 harvest 배치 2 (B2). development-dictionary 실구현 채굴 착지."},{"id":"empty-and-error-states","title":"Empty & Error States","description":"Empty-state and error-recovery judgment rules: an empty state must say why it is empty and what to do next (never just 'no data'), empty and error are different states and must not be collapsed, error copy names the cause in user language with a recoverable action, input is preserved across failures, and retry is offered only when retry can actually succeed","tags":["empty-state","error","recovery","states","forms"],"source_path":"knowledge/empty-and-error-states.md","body":"# Empty & Error States, 빈 상태와 오류 복구 판정 규칙\r\n\r\nDate: 2026-08-17\r\nMilestone: M57 (plan: `plans/2026-08-17-m57-ux-dictionary-absorb.md`)\r\n지위: 화면에 보여줄 것이 없거나 동작이 실패했을 때의 판정 정본. 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #5·#6 에 동결(원본 `ux-dictionary@60cbab3` `docs/ux-dictionary/methods.yml` 의 `empty-state-ux`·`error-recovery-ux`). 결제 플로우 고유의 실패 처리는 [[checkout-flow]] 소관이고, 폼 필드 자체의 계약은 [[form-fields]] 소관이다. 이 문서는 두 곳에 걸치지 않는 일반 규칙만 담는다.\r\n\r\nAudience: 에이전트(빈 목록·검색 결과 없음·실패 상태를 만들 때 필수 참조) + 사람.\r\n\r\n## 0. 대원칙, 빈 상태는 정보가 아니라 다음 행동이다\r\n\r\n\"데이터가 없습니다\"는 사용자가 이미 아는 사실을 반복하는 문장이다. 빈 상태가 답해야 하는 것은 셋이다. 왜 비었는가, 지금 무엇을 할 수 있는가, 어떻게 되돌리는가.\r\n\r\n이 레포의 `terms.yml` 에는 빈 상태 계열 컴포넌트가 29종 있다(`empty-state`, `empty-search-result`, `empty-table`, `mobile-empty-feed`, `location-permission-empty` 등). 물건은 충분하고 규칙이 없었다.\r\n\r\n## 1. 빈 상태와 오류를 같은 화면으로 처리하지 않는다\r\n\r\n가장 흔한 결함이다. 서버가 500 을 냈는데 \"결과가 없습니다\"를 띄우면 사용자는 조건을 바꾸며 시간을 버린다.\r\n\r\n| 원인 | 사용자에게 말할 것 | 주는 행동 |\r\n|---|---|---|\r\n| 아직 만든 것이 없다(최초 사용) | 이 화면이 무엇으로 채워지는지 | 첫 항목 만들기 |\r\n| 필터·검색이 좁다 | 어떤 조건이 걸려 있는지 | 조건 해제, 조건 완화 |\r\n| 권한이 없다 | 무엇을 허용해야 보이는지 | 설정으로 이동 ([[permission-and-notification]] §3) |\r\n| 로드 실패 | 실패했다는 사실 | 재시도 |\r\n| 정상적으로 0건이다(예: 읽지 않은 알림 없음) | 정상 상태임 | 없음, 축소 표시로 충분 |\r\n\r\n마지막 행이 중요하다. 모든 0건이 문제인 것은 아니다. 정상 0건에 큰 일러스트와 CTA 를 붙이면 화면이 사용자를 다그친다.\r\n\r\n## 2. 빈 상태의 시각 무게는 원인에 비례한다\r\n\r\n- **최초 사용**은 화면 전체를 써도 된다. 그 시점에는 그것이 유일한 콘텐츠다.\r\n- **필터 결과 0건**은 목록 자리 안에서 인라인으로 처리한다. 필터 UI 를 밀어내거나 가리면 되돌릴 수단을 뺏는 것이다.\r\n- **일러스트가 행동을 가리면 뺀다.** 장식이 CTA 보다 눈에 먼저 들어오는 배치는 실패다.\r\n\r\n## 3. 오류 문구는 원인을 사용자 언어로 지목한다\r\n\r\n- **기술 코드를 그대로 노출하지 않는다.** `ERR_CONN_RESET` 은 사용자가 할 수 있는 일을 알려주지 않는다. 지원 문의용 식별자가 필요하면 본문이 아니라 접힌 보조 영역에 둔다.\r\n- **사용자가 할 수 없는 행동을 CTA 로 주지 않는다.** 서버 장애에 \"다시 시도\"만 있고 실제로는 5분간 계속 실패한다면 그 버튼은 거짓말이다. 재시도로 풀리지 않는 실패에는 대체 경로나 예상 복구 시점을 준다.\r\n- **비난하지 않는다.** \"잘못된 입력입니다\"보다 \"전화번호는 숫자만 넣어 주세요\"가 짧고 정확하다.\r\n\r\n## 4. 실패는 입력을 삼키지 않는다\r\n\r\n제출이 실패했을 때 사용자가 친 값이 사라지면, 그것은 오류 하나가 아니라 오류 더하기 재입력이다. [[checkout-flow]] §0 이 \"마찰의 본체는 재입력\"이라고 말하는 것과 같은 규칙이 실패 경로에도 적용된다.\r\n\r\n- 실패 후에도 입력값을 유지한다. 비밀번호처럼 유지하면 안 되는 필드만 예외로 두고, 그 사실을 화면에서 알린다.\r\n- 실패 지점으로 포커스를 옮긴다. 사용자가 어디가 문제인지 스크롤로 찾게 하지 않는다. 포커스 이동 계약은 [[focus-keyboard]] 소관이다.\r\n- 부분 성공은 부분 성공이라고 말한다. 10건 중 3건이 실패했으면 어느 3건인지 지목한다.\r\n\r\n## 5. 판정 절차\r\n\r\n빈 상태나 실패 화면을 만들거나 리뷰할 때 차례로 묻는다.\r\n\r\n1. 이 화면의 0건 원인이 §1 표의 어느 행인가. 두 행 이상이 같은 화면으로 렌더되고 있지 않은가.\r\n2. 원인 설명과 다음 행동이 둘 다 있는가. 하나라도 없으면 미완이다.\r\n3. 그 다음 행동을 사용자가 실제로 수행할 수 있는가.\r\n4. 실패 경로에서 입력이 보존되는가.\r\n5. 정상 0건에 과한 무게를 주고 있지 않은가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 (M57). `ux-dictionary@60cbab3` 의 `empty-state-ux`·`error-recovery-ux` 두 항목을 합본으로 흡수. 착지 게이트 통과 근거는 판정 장부 §3-1 참조. 원문은 항목 나열 수준이라 그대로 옮기지 않고 판정 규칙으로 재작성했으며, 원문의 2차 출처(`material-search`·`nng-heuristics`)는 1차 확인 전이라 인용하지 않는다."},{"id":"expressive-stack","title":"Expressive Stack","description":"Expressive stack tier map: which rendering tier (CSS/SVG, JS motion orchestration, Canvas 2D + physics, WebGL/three.js) a visual effect belongs to, with a technique-to-tier decision table and mandatory judgment procedure (lower tier first, reduced-motion gating, lazy-load for GPU)","tags":["motion","rendering","webgl","canvas","tiers"],"source_path":"knowledge/expressive-stack.md","body":"# Expressive Stack — 표현 기술 티어 지도\r\n\r\nDate: 2026-07-17\r\nMilestone: VI1 (plan: `archive/plans/2026-07-17-vi1-expressive-stack-map.md`)\r\n지위: 살아있는 정본. 근거·출처 전체는 `research/2026-07-17-expressive-stack-genealogy.md`(동결 기록, 전 항목 출처 URL+접근일) — 이 문서는 판정 규칙만 담고 근거를 중복 서술하지 않는다.\r\n\r\nAudience: 코드를 생성하는 에이전트(Codex, Claude Code). \"이 시각 효과는 무엇으로 만드는가\"를 판정하는 데 쓴다.\r\n\r\n## 4개 렌더링 티어\r\n\r\n| 티어 | 정의 | 판별 신호 |\r\n|---|---|---|\r\n| **① CSS·SVG** (선언) | 브라우저가 선언만으로 렌더·애니메이션. JS 개입 없음 | 상태 A→B 전환, 정적/반복 시각, 스크롤 진행률 매핑 |\r\n| **② 모션 오케스트레이션** (JS 지휘 + CSS/DOM 렌더) | JS가 입력·측정·시퀀스를 계산하고 최종 렌더는 CSS transform/opacity | 커서 좌표·요소 측정·동적 시퀀스가 필요, 그러나 그리는 대상은 여전히 DOM |\r\n| **③ Canvas 2D·물리** | JS가 픽셀 버퍼에 직접 그림. 물리 엔진 포함 | 수백 개 이상 개체, 경로 이력, 상호 충돌 — DOM 개체로는 성능 불가 |\r\n| **④ WebGL·three.js** (GPU) | 셰이더 파이프라인. 3D·프레임 단위 절차적 픽셀 계산 | 조명·깊이·유기적 노이즈 애니메이션·유체 — CPU 픽셀 루프로 불가 |\r\n\r\n## 기법→티어 결정 표\r\n\r\n| 원하는 효과 | 티어 | 도구·기법 (근거: research doc 해당 절) |\r\n|---|---|---|\r\n| hover 시 색·그림자·크기 전환 | ① | CSS `transition` — JS 불필요 |\r\n| 유리 질감(글래스모피즘)·배경 블러 | ① | `backdrop-filter` + `mix-blend-mode` |\r\n| 메시/원뿔형 그라디언트 배경 | ① | 다중 radial-gradient 레이어링, `conic-gradient`, `@property`로 색 애니메이션 |\r\n| 형태 잘라내기·리빌 | ① | `clip-path: path()`, `mask-image` |\r\n| 스크롤 진행률에 매핑된 애니메이션 | ① | CSS scroll-driven animations — **두 갈래를 구분**: `animation-timeline: scroll()`(익명 — 컨테이너 전체 진행률) vs `view-timeline: --name`(요소가 뷰포트를 지나는 진행률 — 섹션·항목별 독립 타임라인은 이쪽). 단순 매핑이면 JS 불필요 (BK2 9장 실측) |\r\n| 그레인/필름 노이즈 텍스처 | ① | SVG `feTurbulence` 오버레이 (CSS filter 단독으론 노이즈 생성 불가) |\r\n| 텍스트·이미지 왜곡 | ① | SVG `feTurbulence`+`feDisplacementMap` |\r\n| 커서 추종·마그네틱 hover | ② | 포인터 좌표→거리 계산→CSS transform 갱신 (CSS `:hover`는 커서 위치를 모름) |\r\n| 스프링·관성 있는 UI 모션 | ② | Framer Motion / react-spring (물리 파라미터 보간은 JS 런타임) |\r\n| 핀 고정·스크럽·스크롤 스토리텔링 | ② | GSAP ScrollTrigger — 사유는 둘로 갈린다: (a) CSS scroll-driven 보다 세밀한 제어(*정도*), (b) **애니메이션 대상이 CSS 속성이 아닐 때(WebGL 씬 그래프·Canvas 상태·JS 객체 프로퍼티)는 CSS scroll-driven 이 애초에 대체재가 아니다(*종류* — 티어 비교 자체가 성립 안 함)** (BK2 11장 실측) |\r\n| 리스트 재정렬·카드 확장 전환 | ② | FLIP 기법 (측정은 JS, 재생은 CSS transform) |\r\n| 페이지·상태 전환 모핑 | ①+② | View Transitions API (트리거 JS 1줄 + `::view-transition-*` CSS) |\r\n| 벡터 일러스트 애니메이션 | ② | Lottie (AE 제작 → lottie-web 재생) |\r\n| 다요소 등장 안무(stagger) | ①/② | 정적이면 CSS `animation-delay`, 동적 개수·인터럽트면 JS stagger |\r\n| 수백 개 파티클·커서 트레일 | ③ | Canvas 2D + rAF (DOM 개체로는 레이아웃 비용 폭발) |\r\n| 개체 간 충돌·낙하·물리 | ③ | matter-js (트윈으론 상호작용 불가) |\r\n| 제너러티브 아트·노이즈 드로잉 | ③ | p5.js / Canvas API |\r\n| 3D 씬·조명·제품 뷰어 | ④ | three.js / R3F + drei (CSS `perspective`는 조명·깊이버퍼 없음 — 유사 흉내만) |\r\n| 애니메이팅되는 유기적 셰이더 그라디언트 | ④ | GLSL / Paper Shaders (CSS 그라디언트는 프레임 단위 노이즈 불가) |\r\n| 유체 시뮬레이션 | ④ | WebGL-Fluid-Simulation (Navier-Stokes는 GPU 병렬 필수) |\r\n| 화면 전역 글로우·색수차 | ④ | @react-three/postprocessing |\r\n| 높이가 내재적인 콘텐츠 열림/닫힘(아코디언·필터 패널·\"더 보기\") | ① | 1-row grid `grid-template-rows` 0fr↔1fr 보간 + 내부 `overflow: hidden`(+가벼운 opacity) — 취약한 height:auto/명시 height 전환 회피. 부적합: 대영역(지연 유발)·가상화 콘텐츠(높이 불안정). (KG `css-disclosure-transition-pattern` · WAI-ARIA APG accordion, 접근일 2026-07-28) |\r\n| 무한히 이어지는 절차적 지형·배경의 결 | ③/④ | **다층 해상도 노이즈** — 층마다 `resolution += upresFactor*(층수)` 를 누적해 성긴 굴곡 위에 잔결을 얹는다(예: `resolution 5·upresFactor 2·depth 3` → `[5,7,11]`). 기준선(도로·바닥) 주변은 임계값 아래에서 높이를 완충(`temper`)해 정합을 지킨다. 단일 노이즈 한 겹으로는 「층이 겹쳐 보이는」 결이 안 나온다. 부적합: 결정론적 재현이 필요 없고 텍스처 한 장으로 되는 정적 배경. (출처 `research/2026-08-17-m68-ledger.md` SLW-05·SLW-04 — slowroads.io, 접근일 2026-08-17 · **소비처**: 데모 `layered-noise-terrain`) |\r\n| 수천~수만 개 식생·군중이 바람에 흔들리는 배경 | ④ | **인스턴싱 + 정점 셰이더 스웨이** — 한 드로우로 깔고 정점 단계에서 흔든다. x·z 축에 **다른 주파수와 위상 오프셋**을 줘야 전체가 한 덩어리로 밀리지 않고(관측: x `.05` / z `.1`+phase), 흔들림 세기는 정점 높이에 비례시켜 뿌리는 고정한다. 포인터·캐릭터 위치를 uniform 으로 넘기면 「밟고 지나간 자리가 눕는」 상호작용이 같은 셰이더 안에서 난다. 오브젝트군마다 인스턴스 상한·LOD 전환 거리를 따로 잡는다. 부적합: 개체가 수십 개 이하(그때는 ②로 충분). (출처 SMA-12·SMA-11 — summer-afternoon.vlucendo.com, 접근일 2026-08-17 · **소비처**: 데모 `interactive-grass-sway`) |\r\n| 실시간 3D 비용을 감당 못 하는 자리에서 3D 인상만 필요할 때 | ①/② | **오프라인 베이킹** — 런타임 렌더 대신 미리 구운 이미지를 넘긴다. 회전이 필요하면 임포스터 스프라이트 아틀라스(관측: 16회전각 × diffuse/normal 2채널 webp), 한 축 스크럽이면 프레임 시퀀스. 비용이 GPU 에서 전송량·디스크로 옮겨 가는 교환이므로 **각도 수·해상도가 곧 용량**이다. 부적합: 조명·반사가 실시간으로 변해야 하는 장면. (출처 SLW-07 — slowroads.io `/dev/imposters`, 접근일 2026-08-17 · **소비처**: recipe `baked-frame-sequence-scrub` 이 같은 원리의 프레임 시퀀스 판이다) |\r\n| 선·경계를 「손으로 그은 자국」처럼 낡게 보이려 할 때 | ④(또는 ① SVG) | **SDF + 다중 옥타브 노이즈 풍화 + 존 임계값 침식** — 선을 거리 함수로 그리고 그 **거리값 자체에** 노이즈를 더해 가장자리를 흐트러뜨린 뒤, 저주파 존 노이즈가 만든 임계값(`mix(.3,.7,zone)`)을 고주파 샘플이 넘는 자리에서 선을 지운다. ⚠ **노이즈 진폭은 선 폭보다 작아야 한다** — 크면 흐트러지는 것이 아니라 선이 통째로 사라진다(M68 실측 결함). 기존 「그레인/필름 노이즈 ①」·「텍스트·이미지 왜곡 ①」과 대조: 그 둘은 표면에 텍스처를 얹거나 이미지를 변위시키고, 이 행은 **형태 자체의 경계를 갉는다**. (출처 LAC-11 — Lacoste Ace Breaker, 접근일 2026-08-17 · **소비처**: 데모 `procedural-court-line`) |\r\n| 진입 대기 시간을 연출로 채워야 할 때 | ①/② | **길이가 다른 단계를 겹쳐 바통을 넘긴다** — 순차가 아니다. 관측 3단은 로더 안무(750ms, 회전과 대시 길이 두 애니메이션 동시) · 셰이더/오버레이 크로스페이드(4s) · 카메라 풀백(6s)이고, **본 콘텐츠는 로더가 사라지는 동안 이미 같은 자리에 마운트돼 겹쳐 있다**. 순차로 이으면 총 시간이 합이 되고 이음매가 보인다. 폰트 로드 게이트를 함께 두어 늦게 온 서체가 레이아웃을 튀게 하지 않는다. (출처 SMA-02·SMA-04·SMA-05 — summer-afternoon.vlucendo.com, 접근일 2026-08-17 · **소비처**: 데모 `entrance-three-stage`, recipe `multi-stage-entry-loader`) |\r\n| 가로·세로가 함께 제약된 화면(게임 UI·키오스크·풀스크린 캔버스)의 타이포 | ① | **폭 clamp 에 뷰포트 높이 배수를 곱한다** — 폭 기준 유동 타이포만 쓰면 넓고 낮은 창에서 글자가 세로로 넘친다. 관측 구현은 `--base-text: calc(22px * var(--h-demult))` 처럼 폭에서 나온 값에 높이 배수를 한 번 더 곱하는 3중 체계(고정 px 표 · vw 선형식 · 높이 배수)다. 기존 「이중 클램프 플루이드 타이포」의 폭 축을 높이 축으로 보강한다. 부적합: 세로 스크롤 문서(높이 제약이 없다). (출처 LAC-13 — Lacoste Ace Breaker, 접근일 2026-08-17 · **소비처**: recipe `fluid-type-dual-clamp` 보강) |\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. **하위 티어 우선.** 같은 효과가 두 티어로 가능하면 항상 낮은 티어를 쓴다 — 예: 단순 hover에 JS 금지, 단순 스크롤 리빌에 GSAP 금지, 유사 3D(coverflow류)에 three.js 도입 금지(CSS `perspective`+`rotateY`로 충분한지 먼저 판정).\r\n - **예외 축 — 이미 로드된 상위 도구**: 상위 티어 도구가 *다른 정당한 이유로* 이미 번들에 있으면(예: WebGL 씬 애니메이션용 GSAP), 그 도구로 하위 티어 효과(DOM 페이드인)를 함께 처리하는 것은 허용한다 — 한계비용 0 + 타임라인 문법 통일 이득. 단 **도구 도입 자체의 근거는 여전히 상위 티어 쪽 작업이 져야 하고**, 그 작업이 사라지면 하위 효과도 CSS 로 회귀한다(무임승차 금지). (BK2 11장 — 이 축이 비어 있어 실무 최빈 판정에 답이 없었다)\r\n2. **티어 상승에는 근거를 적는다.** ②→③→④로 올릴 때는 \"하위 티어로 안 되는 이유\"(커서 이력 필요, 상호 충돌 필요, 조명/노이즈 필요 등)를 코드 리뷰어가 볼 위치(PR/변경 기록)에 남긴다.\r\n3. **③·④ 티어는 reduced-motion 분기가 의무.** canvas/WebGL은 CSS와 달리 자동 억제되지 않는다 — `matchMedia('(prefers-reduced-motion: reduce)')`로 렌더 루프·자동 회전을 명시적으로 끄고, 스크린리더용 병렬 DOM 레이어를 제공한다.\r\n4. **④ 티어는 lazy-load가 의무.** three.js는 gzip ~155KB 고정 비용 — dynamic import + Suspense로 격리하고 초기 번들에 넣지 않는다.\r\n5. **시그니처 접점**: 실험적 표현(③·④ 티어 대부분, ②의 커서/마그네틱)은 운용 원칙 5 \"실험적 터치는 수동\" — 사용자가 명시로 요청했을 때만 도입하고, 기본 제품 UI에는 ① 티어와 ②의 실무 패턴(스프링·FLIP·stagger)까지만 기본값으로 쓴다.\r\n6. **모션은 전환 규모에 비례한다.** 작은 상태 변화(토글·선택)는 짧고 담백하게, 크고 주목할 전환(시트 확장·전면 이동)은 길고 표현적으로 — 작은 것에 과장 모션, 큰 것에 인색한 모션이 둘 다 실패다 (M3 easing/duration 판단 diff 채택, taste ledger T-16).\r\n\r\n7. **숨김 모션에는 접근성 상태를 짝짓는다.** disclosure 류(아코디언·접이 패널·필터 서랍·확장 행·\"더 보기\") 애니메이션은 컨트롤의 `aria-expanded` 와 짝이 의무이고, 접힌 콘텐츠가 애니메이션을 위해 DOM 에 남아 있으면 `aria-hidden`·pointer guard·focus guard 로 상호작용을 함께 끈다 — \"모션은 상태 변화를 설명하고 접근성 의미론을 보존할 때 안전하며, 시각적 숨김과 상호작용 상태가 어긋나는 순간 위험해진다.\" 또한 reduced-motion 은 ③·④ 티어만의 의무가 아니다 — ①·② 티어라도 장식적이거나 큰 움직임이면 `prefers-reduced-motion` 분기를 둔다. (KG `frontend-motion-accessibility-source-map` · MDN prefers-reduced-motion, 접근일 2026-07-28)\r\n8. **모션의 \"무엇을·왜\"는 [[motion-principles]] 가 정본이다.** 이 문서는 \"무엇으로 만드나\"(티어·도구)만 판정한다 — 전환의 목적 선택·still appeal 게이트·타이포 가독성 등 품질 원칙은 `knowledge/motion-principles.md` 를 먼저 통과한다.\r\n9. **티어 ① 도 공짜가 아니다.** 뷰포트 크기 요소에 스크롤 연동 `filter: blur()`·대면적 그림자를 걸면 페인트 비용이 프레임마다 발생한다 — 티어 판정과 별개로 속성 비용(Layout/Paint/Composite)을 본다. 전환 속성은 `transition-property: all` 금지 — 실제로 바꾸는 속성(`transform`, `opacity`)만 명시한다. (BK2 9장·4장 실측)\r\n10. **폴백 계약.** ①에서 신형 기능(scroll-driven animations 등)을 쓰면: (a) **지원 대상을 먼저 정한다** — 대상 없이는 폴백 범위를 판단할 수 없다(점유율 판단은 caniuse, `toolshelf/cards/caniuse.md`) (b) `@supports not (...)` 가드를 의무로 두되 (c) **폴백은 장식을 끄는 것이지 기능을 끄는 것이 아니다** — 반례 실측: 미지원 브라우저에서 네비게이션을 통째로 숨긴 폴백(BK2 9장). 앵커 이동·콘텐츠 도달은 애니메이션 지원과 무관하게 살아 있어야 한다.\r\n\r\n## 인접 게이트 — ④ 티어가 외부 3D 에셋을 실을 때\r\n\r\n3D **씬 구현**(three.js/R3F)은 이 문서 소관이지만, 씬에 싣는 **에셋**(GLB/glTF·Gaussian splat)은 별도 게이트를 통과해야 한다 — 요지와 정본 위치만 적는다 (VI8 three.js recipe 확장 시 필수 참조):\r\n\r\n- **에셋 파이프라인 게이트**: export→최적화→검증→런타임 스모크. 핵심 규칙 — *Khronos validator 0 errors 가 제품 계약 보존을 증명하지 않는다* (실측: Khronos-clean 최적화 GLB 가 mesh name 13개·hitbox 13개를 잃고 앱 계약을 깨뜨림) — named mesh·hitbox·extras 를 앱 전용 contract validator 로 따로 검증. → KG `nodes/개발/web-3d-asset-pipeline-quality-gate.md` (접근일 2026-07-28)\r\n- **텍스처 포맷 게이트**: 바닥·환경 텍스처를 무압축 PNG 로 싣지 않는다 — 후보 순서는 GPU 압축(KTX2/Basis) → WebP → (사진류 한정) JPG. 반례 실측: 2.6MB PNG 바닥 텍스처(BK2 12장, `Youngjin-com/interactive_web@a2dfc627` 접근 2026-08-10). 에셋 총량이 수 MB 를 넘으면 lazy-load(절차 4)와 로딩 표시가 함께 의무다.\r\n- **AI 3D 생성 도구 선택**: 생성기는 완결 에셋이 아니라 upstream 후보 소스 — 입력 유형·품질·하드웨어·텍스처·배포 요건으로 레인을 고른 뒤 위 게이트에 반드시 짝지운다. → KG `nodes/AI/ai-3d-asset-generation-model-selection-gate.md` (접근일 2026-07-28)\r\n\r\n## 외부 기준선 — \"CSS 로 여기까지 된다\"의 실물 목록\r\n\r\n티어 상승 판정(절차 1 \"충분한지\")의 외부 대조군: 『인터랙티브 웹 애니메이션』(김영민, 영진닷컴) 4장은 JS 0줄로 3D 카드 플립(`perspective`+`preserve-3d`+`backface-visibility`)·3D 회전목마(`@keyframes`+`transform-style`)·SVG 모션·반응형까지 만든다 — 이 목록에 있는 효과에 티어 ② 이상을 제안하면 근거가 필요하다. 소스: `github.com/Youngjin-com/interactive_web@a2dfc627` `public/css/chapter_4/` (접근 2026-08-08, 판독 `research/books/interactive-web-animation/ch04.md`).\r\n\r\n## 자체 쇼케이스 역산 (참조 구현)\r\n\r\naskewly.com 랜딩 Showcase Atlas 12종의 티어 분포 — 실코드 근거는 research doc 역산 표:\r\n① CSS 다수(Image Treatment·Hero 등) + CSS 3D 유사물(Coverflow, Agent 카드 tilt) · ② Cursor-Reactive Field, Motion Choreography · ③ Physics(matter-js) · ④ Shader Gradient(Paper Shaders). three.js는 미사용 — GPU 티어 확장은 VI4.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: M68 — 결정표에 배치 3(웹게임·구조 메타포) 축 **6행 추가**(절차적 지형 다층 해상도 · 인스턴싱 정점 스웨이 · 오프라인 베이킹 · SDF 노이즈 풍화 · 진입 3단 겹침 · 높이 제약 타이포). 행마다 `absorption-criteria.md` 「knowledge 착지 게이트」 3조건(출처 백링크 · 기존 행 중복 대조 · 소비처 실증)을 통과시켰다. 기존 22행 무손실. 근거 `research/2026-08-17-m68-ledger.md` §1·§5.\r\n- 2026-08-10: BK2 — 결정표 스크롤 행 view-timeline/scroll() 구분·GSAP 사유 이원화(정도/종류), 판정 절차 1 「이미 로드된 상위 도구」 예외 축, 절차 9(티어① 속성 비용·transition-all 금지)·10(폴백 계약 — 지원 대상 선행·장식만 끈다) 신설, 인접 게이트 텍스처 포맷, 외부 기준선 절. 근거 `research/2026-08-10-bk2-promotion-verdicts.md` V09·V30·V40·V50·V52.\r\n- 2026-07-28: VI6 — KG 흡수: 결정표에 disclosure grid-track 행 추가, 판정 절차 7(접근성 짝규칙·전 티어 reduced-motion)·8(motion-principles 위임) 추가, ④ 티어 인접 게이트(3D 에셋 파이프라인·AI 생성 선택) 링크 절 신설. 근거 `research/2026-07-28-vi6-kg-crosswalk.md`.\r\n- 2026-07-17: 초판 (VI1 — 리서치 30기법 계보 + 쇼케이스 12종 역산에서 증류)."},{"id":"focus-keyboard","title":"Focus & Keyboard","description":"Focus & keyboard discipline for custom widgets and overlays (Radix/WAI-ARIA APG): APG is canon — never invent keyboard behavior; modal contract (trap + focus return to trigger + first-logical-item initial focus + title/description announcement); composite widgets are one Tab stop with roving tabindex, arrows, Home/End, and typeahead; explicit dismiss layer contract (Esc closes top layer only); no unnamed controls","tags":["accessibility","keyboard","focus","aria"],"source_path":"knowledge/focus-keyboard.md","body":"# Focus & Keyboard — 포커스·키보드 규율 판정 규칙\r\n\r\nDate: 2026-08-01\r\nMilestone: M10 (plan: `plans/2026-08-01-m10-radix-focus-keyboard-absorption.md`)\r\n지위: 커스텀 위젯·오버레이의 포커스·키보드 행동 판정 정본. 근거 원문은 `research/2026-08-01-m10-radix-focus-keyboard-capture.md` 에 동결(Radix Primitives, 접근 2026-08-01). 어떤 오버레이 **컨테이너를 고를지**는 [[mobile-navigation]] 소관, 이 문서는 고른 뒤의 **행동 계약**만 담는다. 포커스 링의 시각 규격은 terms(`focus-ring`), 감사 함정(programmatic focus 오탐)은 kg 노드 `focus-visible-audit-real-tab-key` 참조.\r\n\r\nAudience: 에이전트(커스텀 위젯·오버레이 생성 시 필수 참조) + 사람.\r\n\r\n## 0. 대원칙 — 정답은 WAI-ARIA APG, 발명하지 않는다\r\n\r\n네이티브 요소를 div 로 대체하는 순간 시맨틱(role·aria)과 **행동**(키보드·포커스) 재구현이 전부 우리 책임이 된다. 각 패턴의 행동 정답은 WAI-ARIA authoring practices 에 이미 있다 — 키보드 동작을 창작하지 말고 APG 패턴을 찾는다.\r\n\r\n## 1. 모달 오버레이 계약 (트랩 + 반환)\r\n\r\n1. modal 이면 **포커스 트랩** — Tab 순환이 내부에 갇힌다. non-modal(popover 등)엔 트랩을 걸지 않는다.\r\n2. **닫힐 때 포커스는 연 트리거로 반환** — Esc·버튼·scrim 어느 해제 경로든 동일. 반환 없는 트랩은 미완성.\r\n3. **열림 직후 첫 논리 항목에 포커스** — 메뉴 = 첫 항목, 파괴적 확인 = 안전한 버튼(Cancel), 폼 모달 = 첫 필드. 포커스 이동이 곧 스크린리더 공지 채널이다.\r\n4. Title/Description 을 배선해 열림이 \"무엇인지\" 공지되게 한다.\r\n\r\n## 2. 복합 위젯 계약 (roving tabindex)\r\n\r\n탭 목록·메뉴·툴바·라디오 그룹 같은 복합 위젯은 **Tab stop 1개**다:\r\n1. Tab 은 위젯에 들어왔다(활성 항목) 다음 Tab 에 밖으로 나간다 — 항목마다 Tab 을 먹으면 FAIL.\r\n2. 내부 이동은 방향키(orientation 기준) + Home/End.\r\n3. 항목 목록엔 **typeahead** — 타이핑으로 매칭 항목 점프.\r\n4. 이동-활성화 결합(automatic activation)이 기본, 비용 큰 패널 전환만 Enter 분리(manual).\r\n\r\n## 3. 해제(dismiss) 계층 계약\r\n\r\n1. 오버레이 해제 경로는 **계약으로 명시**: Esc · 외부 클릭/scrim · 트리거 재활성. 셋의 동작이 예측 가능해야 한다.\r\n2. 겹친 레이어의 Esc 는 **최상위 하나만** 닫는다 — 스택 순서 명시.\r\n3. 모달 깊이·중첩 허용 예외는 [[mobile-navigation]] §4 를 따른다.\r\n\r\n## 4. 라벨 계약\r\n\r\n1. **이름 없는 커스텀 컨트롤 금지** — 시각 라벨이 있으면 연결(label/aria-labelledby), 없으면 aria-label. 프리미티브가 배선을 추상화해도 라벨 내용은 제품 책임이다.\r\n2. 상태 변화로 화면이 바뀌면 포커스 이동 + 라벨로 새 맥락을 공지한다(라이브 리전 남발 대신 포커스 채널 우선).\r\n3. **입력 그룹은 `<fieldset>`+`<legend>` 로 묶는다** — 라디오·체크박스 묶음을 제목 태그(`<h2>` 등)로 감싸면 시각적으로만 묶이고 접근성 트리에서는 안 묶인다 — 스크린리더 사용자는 \"인천 라디오버튼\"이 무슨 질문의 선택지인지 모른다. 반례 실측: BK2 2장(`research/books/interactive-web-animation/ch02.md`).\r\n4. **포커스 표시의 시각 최소선** — 배경색 틴트만 바꾸는 것은 포커스 표시가 아니다(저시력 가시성·비인접 대비 모두 미달). 최소선은 `:focus-visible` outline(시각 규격은 terms `focus-ring` 소유). 리셋이 outline 을 지우는 문제와 복구 의무는 [[css-reset]] §2. 반례 실측: `all: unset` 리셋 후 `:focus { background-color }` 만 복구(BK2 3장).\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. 커스텀 위젯을 만들기 전에 APG 에 해당 패턴이 있는지 먼저 확인한다(§0) — 있으면 그 키보드 표가 스펙이다.\r\n2. 모달을 만들면 §1 4항(트랩·반환·초기 포커스·공지) 전부 체크 — 하나라도 빠지면 미완성.\r\n3. 항목형 위젯이면 §2 로 Tab stop 수를 센다 — 위젯당 1개.\r\n4. 포커스 가시성 검증은 실키 Tab 으로 한다 — 프로그램적 focus() 는 :focus-visible 을 안 태워 오탐을 만든다(kg 실측).\r\n5. Radix 등 프리미티브의 기본 비주얼은 흡수 대상이 아니다 — 행동 계약만 가져오고 look 은 토큰 소유.\r\n\r\n## Changelog\r\n\r\n- 2026-08-10: BK2 — §4 에 fieldset/legend 그룹 계약(3)·포커스 표시 시각 최소선(4) 추가. 근거 `research/2026-08-10-bk2-promotion-verdicts.md` V02·V05.\r\n- 2026-08-01: 초판 (M10) — Radix Primitives(Introduction·Accessibility·Dialog·DropdownMenu·Tabs) 실브라우저 캡처 기반."},{"id":"form-fields","title":"Form Fields","description":"Baseline contract every form field must meet: a visible label (placeholder is not a label), a name attribute, POST for credentials/sensitive data, and correct autocomplete tokens","tags":["forms","accessibility","autocomplete","labels"],"source_path":"knowledge/form-fields.md","body":"# Form Fields — 폼 필드 기본 계약\r\n\r\nDate: 2026-08-10\r\nMilestone: BK2 (plan: `plans/2026-08-10-bk2-batch-promotion.md` step-3)\r\n지위: 살아있는 정본. **모든 폼 표면**(recipe·블록·데모·아티팩트)의 필드 최소 계약 — 특정 recipe 종속이 아니라서 knowledge 에 있다. 라벨-컨트롤 연결·이름 계약의 상위 정본은 [[focus-keyboard]] §4, 입력 그룹 묶기(fieldset/legend)도 그쪽 소관 — 이 문서는 필드 하나가 갖춰야 할 나머지를 담는다.\r\n근거: 『인터랙티브 웹 애니메이션』 7장 로그인(반례 4건이 한 파일)·8장 검색 폼(정답 실물) 실측 — `github.com/Youngjin-com/interactive_web@a2dfc627`, 접근 2026-08-08. 장부 `research/2026-08-10-bk2-promotion-verdicts.md` V22.\r\n\r\nAudience: 에이전트(폼 생성 시 필수 참조) + 사람.\r\n\r\n## 계약 4조\r\n\r\n1. **보이는 `<label>` 텍스트 — placeholder 는 라벨이 아니다.** placeholder 는 입력을 시작하면 사라진다 — 그 순간 \"이 칸이 뭐였는지\"를 확인할 방법이 없어진다(길수록·수정할 때일수록). 접근 가능한 이름으로도 계약이 안 된다(보조기술 구현마다 갈림). placeholder 는 형식 예시(`you@example.com`)만.\r\n2. **`name` 필수.** `name` 없는 컨트롤은 제출에서 제외된다 — 폼이 시각적 소품이 된다. 반례 실측: 로그인 폼 전 필드 `name` 부재 → 제출해도 아무 값도 안 나감(7장).\r\n3. **자격증명·민감 데이터는 `method=\"POST\"`.** `method` 생략 = GET = **값이 URL 쿼리스트링에 실린다** — 브라우저 기록·서버 로그·리퍼러에 남는다. 검색처럼 URL 공유가 가치인 폼만 GET(8장 검색 폼이 정답 실물: `method=\"get\"` + `name=\"searchWords\"` + `URLSearchParams` 복원).\r\n4. **`autocomplete` 토큰.** 로그인 `username`+`current-password`, 가입 `new-password`, 그 외 `email`·`name`·`tel` 등. 없으면 비밀번호 관리자가 안정적으로 못 채운다 — **보안 도구를 쓰는 사용자를 밀어내는 결함**이다. 끄기(`off`)는 계약 위반의 명시 사유가 있을 때만.\r\n\r\n## 부속 규칙\r\n\r\n- 비밀번호 표시 토글은 `<button type=\"button\" aria-pressed>` — `<i>` 클릭 리스너 금지([[input-modality]] §3).\r\n- 라디오·체크박스 그룹은 `<fieldset>`+`<legend>` — 제목 태그로 묶으면 접근성 트리에서 안 묶인다([[focus-keyboard]] §4).\r\n- 제출·초기화 버튼에 빈 `<label>` 을 만들지 않는다 — `value`/텍스트가 이미 이름이다(빈 라벨 반례 3회 실측: 2·7·8장).\r\n\r\n## 폼 구성 규칙 (계약 위, 화면 판정)\r\n\r\n위 4조가 필드 하나의 최소 계약이라면 이 절은 **필드를 모아 폼을 만들 때**의 판정이다. 계약은 통과했는데도 쓰기 힘든 폼이 여기서 갈린다.\r\n\r\n1. **필드 수를 먼저 깎는다.** 각 필드에 \"지금 받아야 하는가, 나중에 받아도 되는가, 우리가 이미 아는가\"를 묻는다. 나중에 받아도 되는 것은 첫 제출에서 뺀다. 폼 개선의 대부분은 배치가 아니라 삭제다.\r\n2. **순서는 내부 스키마가 아니라 사용자의 머릿속 순서를 따른다.** DB 컬럼 순이나 관리자 화면 순으로 늘어놓으면 사용자는 맥락을 계속 갈아탄다. 같은 주제끼리 묶고, 묶음마다 왜 필요한지 한 줄로 설명할 수 있어야 한다.\r\n3. **모바일 키보드 타입을 지정한다.** 숫자에는 숫자 키패드, 이메일에는 `@` 가 있는 배열이 뜨게 한다(`inputmode`·`type`). 지정하지 않으면 사용자가 매 필드마다 키보드를 전환한다. 이것은 §계약 4조의 `autocomplete` 와 별개 축이다.\r\n4. **검증 시점은 필드를 떠날 때다.** 타이핑 중에 빨간 글씨를 띄우면 아직 다 치지도 않은 사용자를 비난하게 되고, 제출 시점에 몰아 띄우면 어디가 틀렸는지 찾게 만든다.\r\n5. **제출 실패가 입력을 삼키지 않는다.** 값 보존·포커스 이동·부분 실패 지목은 [[empty-and-error-states]] §4 소관이다.\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n폼을 만들거나 검토할 때 **필드 전수**로: ① 보이는 라벨 텍스트 ② `name` ③ 민감 데이터면 POST ④ `autocomplete` 토큰. 4개가 다 있는가. 하나라도 없으면 그 폼은 \"동작하는 폼\"이 아니라 \"폼처럼 보이는 그림\"일 수 있다. 반례 실측이 정확히 그것이었다(7장: 아이디어는 이 책 최고였는데 폼은 소품).\r\n\r\n이어서 **폼 단위**로: ⑤ 뺄 수 있는 필드를 뺐는가 ⑥ 순서가 사용자 기준인가 ⑦ 모바일 키보드 타입이 맞는가 ⑧ 실패 시 입력이 보존되는가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: M57 — §폼 구성 규칙 신설(필드 수 삭감·입력 순서·모바일 키보드·검증 시점·실패 보존)과 판정 절차 4항 추가. `ux-dictionary@60cbab3` `form-ux` 흡수분이며, 기존 4조가 마크업 계약만 덮고 구성 축은 비어 있던 것을 메운다. 판정 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #2.\r\n- 2026-08-10: 초판 — BK2 V22. 근거 장부 `research/2026-08-10-bk2-promotion-verdicts.md`."},{"id":"graph-content-schema","title":"Graph Content Schema","description":"Graph content schema: independent type axis (7 node types with fixed visual vocabulary) and 3-level hierarchy axis, directed/undirected relation split, provenance.reviewed tracking for machine-generated edges, data-declared overview visibility — harvested from a production knowledge graph","tags":["graph","schema","data-viz"],"source_path":"knowledge/graph-content-schema.md","body":"# Graph Content Schema — 지식 그래프 콘텐츠 스키마\n\n> 지위: 지식·콘텐츠 그래프 화면(연결 시각화, 세컨드브레인, 관계 탐색 UI)을 만들 때의 데이터 스키마 판정 규칙. 출처 = second-brain/poc-graph(brain.askewly.com) 실구현 채굴(harvest, 2026-08-04 — `research/2026-08-04-m22-harvest-mining-ledger.md` ② 카드). 코드 벤더링이 아니라 스키마 계약을 이식한다. 뷰어 코드(three.js 씬·force 어댑터)는 별도 이월(같은 장부 C1).\n\n## 핵심 계약 — 타입 축과 계층 축을 동시에 갖는다\n\n노드는 **분류 축 두 개를 독립으로** 가진다 — 하나로 합치면 \"무엇인가\"와 \"어디에 속하는가\"가 서로를 오염시킨다.\n\n| 축 | 필드 | 값 | 규칙 |\n|---|---|---|---|\n| 타입(무엇) | `type` | 7종: `semantic`(개념)·`reflective`(통찰)·`procedural`(절차)·`episodic`(사건)·`thesis`(주장)·`topic`(주제)·`unclassified`(미분류) | 타입마다 고정 컬러+글리프 매핑(시각 어휘) — `unclassified` 를 정식 타입으로 두어 미분류가 조용히 사라지지 않게 한다 |\n| 계층(어디) | `hierarchy` | 3단 `galaxy > cluster > system` + `role`(`galaxy\\|cluster\\|system\\|star`) | 계층 자체도 노드로 렌더하되 `hierarchy_virtual` 플래그로 실콘텐츠 노드와 구분 — 가상 노드가 콘텐츠 수를 부풀리지 않는다 |\n\n- 노드 정본 = 원문 1개(마크다운 파일)당 1노드. 화면이 아니라 소스 저장소가 정본이고, 그래프 데이터(`graph.json`)는 빌드 산출물이다.\n\n## 엣지 계약 — 관계 종류·방향·검수 추적\n\n| 요소 | 규칙 |\n|---|---|\n| 방향성 관계 | `rel` 11종: supports·extends·requires·refines·instantiates·contradicts·triggered-by·composes·mentions·related·contains — 방향(`dir`) 이 의미의 일부다 (`A extends B` ≠ `B extends A`) |\n| 무방향 관계 | 태그·유사 계열(near-miss·topic-tag·thesis-tag)은 방향 없음으로 분리 — 방향성 관계와 같은 시각 언어를 쓰지 않는다 |\n| 출처 추적 | `provenance: {layer, kind, reviewed}` — **기계 생성 엣지와 사람 검수 엣지를 같은 스키마 안에서 구분**한다. `reviewed:false` 엣지를 지우지 않고 표시만 다르게 |\n| 노출 제어 | `overview_visible`·`overview_emphasis` — 전체 뷰에 다 그리지 않는다. 어떤 엣지가 개요에 나올지는 데이터가 선언 |\n\n## 판정 절차\n\n새 그래프 화면 의뢰 시: ① 타입 축(고정 시각 어휘 포함)과 계층 축을 분리 정의 ② 관계는 방향성/무방향 두 부류로 나누고 방향성 관계에 어휘를 먼저 확정 ③ 기계 생성 연결에는 `provenance.reviewed` 를 처음부터 배선 ④ 개요 노출은 렌더러 휴리스틱이 아니라 데이터 필드로. 물리·레이아웃(force 설정)은 스키마와 독립 관심사 — 이 문서 범위 밖.\n\n## Changelog\n\n- 2026-08-04: 초판 — M24 harvest 배치 3 (B4). second-brain/poc-graph 스키마 채굴 착지."},{"id":"input-modality","title":"Input Modality","description":"Input-modality judgment rules -- never assume the input device or sensory channel: hover dichotomy (decorative vs reach-path), Pointer Events over mouse events, interactive elements must be real buttons/links, and state changes must reach non-visual channels (aria-pressed/checked/expanded)","tags":["input","accessibility","pointer","hover","aria"],"source_path":"knowledge/input-modality.md","body":"# Input Modality — 입력 수단을 가정하지 않는다\n\nDate: 2026-08-10\nMilestone: BK2 (plan: `plans/2026-08-10-bk2-batch-promotion.md` step-3)\n지위: 살아있는 정본. 화면 UI 가 **입력 수단(마우스·터치·펜·키보드)과 감각 채널(시각·보조기술)을 특정하지 않고 작동하는가**를 판정한다. 커스텀 위젯·오버레이의 포커스·키보드 *행동 계약*은 [[focus-keyboard]] 소관 — 이 문서는 그 앞 단계, \"어떤 입력이 오든 도달 가능한가\"의 계약이다.\n근거: 『프론트엔드 개발자를 위한 인터랙티브 웹 애니메이션』(김영민, 영진닷컴) 12장 완주 실측 — 반례 6건 + 29.8KB HTML 의 `aria-` 0건 (`research/2026-08-10-bk2-promotion-verdicts.md` V08, 소스 동결 `github.com/Youngjin-com/interactive_web@a2dfc627`, 접근 2026-08-08~10).\n\nAudience: 에이전트(화면 UI 생성 시 필수 참조) + 사람.\n\n## 0. 대원칙 — 결함은 만든 사람의 화면에 나타나지 않는다\n\n이 문서의 반례 6건은 실력 문제가 아니었다 — 코드 규율이 단단한 저자가 같은 결함을 12장 내내 반복했다. 원인은 하나다: **hover 없는 기기, 마우스 아닌 포인터, 화면을 못 보는 사용자는 개발자의 책상에 없다.** 그래서 이 계약의 각 규칙은 \"잘 만들기\"가 아니라 **\"내 화면에 안 나타나는 사용자를 목록으로 대신하기\"** 다.\n\n## 1. hover 이분법 — 장식과 도달 경로는 처방이 다르다\n\n1. **장식적 hover**(카드 들림·그림자·색 강조 — 없어도 콘텐츠 도달 가능) → `@media (hover: hover)` 로 감싼다. 터치 기기에서 첫 탭이 hover 를 소비하는 끈적임을 막는다.\n2. **도달 경로 hover**(hover 로만 드러나는 콘텐츠·컨트롤) → **대체 경로 의무.** `:hover` 에는 `:focus-within` 을 짝짓고, 터치에는 탭 동작(첫 탭 = 드러내기)을 준다. `(hover: hover)` 가드만으로는 안 풀린다 — 그건 장식을 끄는 장치지 도달 경로를 만들지 않는다.\n3. 판정 질문: **\"hover 를 영원히 못 하는 사용자가 이 내용에 도달할 수 있는가?\"** — 아니오면 그 hover 는 도달 경로다.\n\n## 2. 포인터 — 마우스 이벤트 대신 Pointer Events\n\n1. 드래그·그리기·스와이프류 상호작용은 `mousedown`/`mousemove`/`mouseup` 대신 **`pointerdown`/`pointermove`/`pointerup`** — 마우스·터치·펜을 한 코드로 덮는 상위 집합이다.\n2. **레이아웃 반응성과 입력 반응성은 다른 문제다.** `matchMedia` 로 모바일 레이아웃을 분기했다고 입력을 챙긴 게 아니다 — 반례 실측: 카메라·스케일·타임라인을 전부 `isMobile` 로 분기하면서 드래그는 마우스 전용(11장), 게임 전체가 마우스+키보드 전용에 터치 절 자체가 부재(12장).\n3. 포인터 리스너는 **상호작용 대상 요소에** 단다 — `window` 에 달면 페이지 전체가 조작면이 되어 텍스트 선택·스크롤과 충돌한다.\n4. 터치 타깃 최소 44px — 토큰 `dimension.size.touch-target-min` (기존, `docs/design-system/token-coverage-gen2.md`).\n\n## 3. 조작 대상은 조작 가능한 요소로\n\n1. **`<i>`·`<div>`·`<span>` 에 클릭 리스너를 달지 않는다.** 조작 대상은 `<button type=\"button\">`(동작) 또는 `<a href>`(이동) — 포커스·Enter/Space·role 이 공짜로 따라온다. 반례 실측: 비밀번호 표시 토글이 `<i>` 라 키보드로 누를 수 없음(7장).\n2. 장식 아이콘(옆에 텍스트가 있는 경우)은 `aria-hidden=\"true\"` — 아이콘 폰트·SVG 가 보조기술에 잡음으로 읽히지 않게.\n3. 게임·캔버스류의 키 입력은 `event.preventDefault()` 로 기본 동작(스페이스=스크롤)과 분리한다.\n\n## 4. 이름과 상태 — 둘 다 비시각 채널에 실어야 한다\n\n1. **이름(name)**: 아이콘 버튼은 SVG `<title>`·`aria-label` 로 이름을 준다. (반례의 저자도 이건 일관되게 했다 — \"모바일 메뉴\"·\"찾기\". 이름만으로는 절반이다.)\n2. **상태(state)**: 토글은 `aria-pressed`, 배타 선택은 `role=\"radiogroup\"`+`aria-checked`, 열림/닫힘은 `aria-expanded`, 오버레이는 `role=\"dialog\"`(+ [[focus-keyboard]] §1 계약). **상태 변화가 `class=\"active\"` 라는 시각 전용 채널에만 실리면, 화면을 못 보는 사용자에게 그 변화는 일어나지 않은 것이다.** 반례 실측: 대시보드 29.8KB HTML 에 `aria-` 속성 0건 — 즐겨찾기·뷰 전환·테마 토글 전부 시각 채널 전용(8장).\n3. 판정 질문: **\"방금 누른 결과를 눈 감고 알 수 있는가?\"**\n\n## 판정 절차 (에이전트 의무)\n\n화면 UI 를 만들거나 검토할 때:\n\n1. hover 전수 — 각 `:hover` 가 장식인가 도달 경로인가 분류, 후자에 `:focus-within`/탭 대체 존재 확인.\n2. `mouse*` 리스너 grep — 있으면 Pointer Events 전환 또는 사유.\n3. 클릭 리스너가 달린 비조작 요소 grep(`<i>`·`<div ... onClick>`) — 0 이어야 한다.\n4. 상태를 바꾸는 컨트롤 전수 — `aria-pressed`/`aria-checked`/`aria-expanded` 중 하나가 있는가.\n5. 이 검사들은 기계화 가능하다 — 검사 스크립트 승격은 후속(할 일 문서 B4).\n\n## 반례 코퍼스 (전건 실측 — 규칙별 근거)\n\n| 장 | 반례 | 위반 규칙 |\n|---|---|---|\n| 4 | `<article>` hover 로만 드러나는 텍스트 — 터치·키보드 도달 불가 | §1-2 |\n| 4 | 카드 플립 `:hover` 단독 — Tab 도달해도 무반응 | §1-2 |\n| 5 | 캔버스 페인터 `mouse*` 전용 — 터치·펜에서 무동작 | §2-1 |\n| 7 | 비밀번호 토글 `<i>` — 키보드 조작 불가 | §3-1 |\n| 8 | `aria-` 0건 — 상태가 시각 채널 전용 | §4-2 |\n| 11·12 | 모바일 레이아웃 분기하면서 입력은 마우스 전용 / 게임에 터치 부재 | §2-2 |\n\n(6장 `@media (hover: hover)` 는 같은 저자의 **부분 자가수정** — 장식 hover 에는 정답, 도달 경로에는 무효. §1 이분법의 근거.)\n\n## Changelog\n\n- 2026-08-10: 초판 — BK2 V08 (V20·V23·V26·V42·V47 합병). 근거 장부 `research/2026-08-10-bk2-promotion-verdicts.md`."},{"id":"landing-section-rhythm","title":"Landing Section Rhythm","description":"Landing section rhythm: alternating background bands (base/warm/ink) as scroll pacing, single ink band on the process section, identical explanation hierarchy across sections — harvested from a production landing","tags":["landing","layout","rhythm","marketing"],"source_path":"knowledge/landing-section-rhythm.md","body":"# Landing Section Rhythm — 랜딩 섹션 리듬 (밴드 교차)\n\n> 지위: 설명형 랜딩(제품·서비스 소개)의 섹션 시퀀스 판정 규칙. 출처 = upstage-sixsense(sixsense.askewly.com) 랜딩 실구현 채굴(harvest, 2026-08-04 — `research/2026-08-04-m22-harvest-mining-ledger.md` ⑬ 카드).\n\n## 계약 — 배경 밴드가 리듬을 만든다\n\n섹션 시퀀스는 내용 순서이자 **배경 톤의 교차**다. 실측 시퀀스:\n\n```\n히어로(기본 지면 + 인터랙티브 데모/증거 카운터)\n→ band-warm : 대비 카드 (문제 vs 해답 — contrast-duo-card)\n→ band-ink : 프로세스 스테퍼 (3단계 — 어두운 밴드가 \"방법\"을 분리)\n→ band-warm : 참여/제보 CTA\n→ closing : 예산 칩 등 마지막 행동 유도\n→ footer\n```\n\n| 규칙 | 근거 |\n|---|---|\n| 인접 섹션은 배경 톤을 교차한다 (기본→warm→ink→warm→기본) | 스크롤 위치 감각 — 단색 연속이면 어디까지 읽었는지 사라진다 |\n| 어두운 밴드(ink)는 1곳, \"방법/프로세스\"에 배정 | 가장 밀도 높은 정보에 가장 강한 분리 |\n| 설명 블록 위계는 전 섹션 동일: muted 라벨 → 볼드 헤드라인 → 보조 설명 | 반복되는 위계가 리듬을 읽히게 한다 (contrast-duo-card 와 동일 원리) |\n| 히어로에 실물 증거 1개(데모 카드·카운터)를 넣는다 | 주장보다 관측 — 첫 화면에서 제품이 움직이는 것을 보인다 |\n\n## 판정 절차\n\n랜딩 의뢰 시: ① 섹션 목록을 먼저 톤(기본/warm/ink)으로 배정 ② ink 1곳 초과면 재검토 ③ 설명 블록 위계 3단을 전 섹션에 강제. 조합 원리는 `research/2026-07-19-st4-composition-patterns.md`(순서·비중이 유형을 가른다)과 상보.\n\n## Changelog\n\n- 2026-08-04: 초판 — M23 harvest 배치 2 (B3). sixsense 랜딩 실구현 채굴 착지."},{"id":"mobile-navigation","title":"Mobile Navigation","description":"Mobile navigation & modality container selection rules (Apple HIG x Material 3): one primary nav per screen with a destination-count/breakpoint decision table (nav drawer deprecated by M3 Expressive — prefer expanded rail), nav behavior contract (no swipe switching, preserve-vs-reset state, no hidden tabs), modal surface choice by task weight (snackbar/dialog/modal sheet/full-screen dialog/detents), and the modal-depth-of-one rule","tags":["mobile","navigation","modality","hig","material"],"source_path":"knowledge/mobile-navigation.md","body":"# Mobile Navigation & Modality — 컨테이너 선택 판정 규칙\r\n\r\nDate: 2026-08-01\r\nMilestone: M7 (plan: `plans/2026-08-01-m7-platform-guideline-absorption.md`)\r\n지위: 모바일 화면에서 내비게이션 컨테이너와 모달 표면을 고르는 판정 규칙의 정본. 근거 원문은 `research/2026-08-01-m7-mobile-nav-sheets-capture.md` 에 동결(출처 URL·접근일 포함) — 이 문서는 판정 규칙만 담고 원문을 재서술하지 않는다. Apple HIG × Material 3 를 대조해 플랫폼 공통 규칙과 분기 지점을 가른다.\r\n\r\nAudience: 에이전트(모바일 화면 생성 시 필수 참조) + 사람.\r\n\r\n## 1. 주 내비게이션 컨테이너 선택\r\n\r\n**화면당 주 내비게이션 컴포넌트는 1개다.** drawer + bottom bar 동시 사용은 안티패턴.\r\n\r\n| 조건 | 컨테이너 | 근거 |\r\n|---|---|---|\r\n| 최상위 목적지 3~5개 · compact(폰) | bottom navigation / tab bar | M3 3~5 규칙 · HIG 탭바 |\r\n| 목적지 3개 미만 | 페이지 내 tabs (주 내비 아님) | M3 — nav bar 금지 |\r\n| 목적지 5개 초과 · medium+ | navigation rail (확장형) 또는 사이드바 전환 | M3 Expressive · HIG sidebarAdaptable |\r\n| 목적지 5개 초과 · compact | 탭 커스터마이즈(기본 ≤5) 또는 rail 로 승격 | HIG iPadOS 커스터마이즈 |\r\n| 데스크톱/expanded | rail·사이드바 — bottom bar 금지 | M3 |\r\n| **navigation drawer 신규 채택** | **비권장** — 확장형 rail 이 대체 (M3 Expressive, 2026-08-01 확인) | 기존 제품 유지만 허용 |\r\n\r\n## 2. 주 내비게이션 행동 계약\r\n\r\n1. **탭 전환은 탭/클릭만** — 좌우 스와이프로 목적지를 전환하지 않는다(스와이프는 카드 캐러셀·행 액션 몫).\r\n2. **상태 계약을 명시적으로 정한다** — preserve(스크롤·하위 탭·검색 유지) vs reset. 섹션을 자주 오가는 앱은 preserve.\r\n3. **활성 목적지 재선택 = 최상단 스크롤.**\r\n4. **탭 숨김·비활성 금지** — 내용이 없으면 빈 섹션에 이유를 보여준다.\r\n5. **라벨 필수** — 1~2 단어, 줄바꿈·축소·제거 금지. 활성 표시는 1개만.\r\n6. **스크롤 숨김은 스크린리더 활성 시 끈다.** FAB 는 바 위에, 바를 가리지 않는다.\r\n7. **배지는 critical 정보만** — 남용하면 의미가 죽는다.\r\n\r\n## 3. 모달 표면 선택 (태스크 무게로 가른다)\r\n\r\n| 태스크 | 표면 | 핵심 계약 |\r\n|---|---|---|\r\n| 저중요 알림 (액션 선택적) | snackbar/toast | 비차단·자동 소멸 — dialog 금지 |\r\n| 고중요 결정·확인 | dialog | 액션 ≤2(확인 트레일링) · 제목은 구체 질문 · 제3의 이동 버튼 금지 |\r\n| 맥락 밀착 짧은 선택·목록 | modal bottom sheet | 초기 높이 ≤50% · scrim 탭/스와이프 dismiss · 초과분은 풀확장+내부 스크롤 |\r\n| 메인 UI 와 병행 사용 | standard bottom sheet (nonmodal) | scrim 없음 · 동시 상호작용 (예: 미니 플레이어) |\r\n| 키보드 폼·비즉시 저장·다단 작성 | full-screen dialog (compact 전용) | 확인 = 구체 동사(Send/Create) · 비활성화 금지 · 미저장 닫기 확인 · 큰 화면은 basic dialog 로 스왑 |\r\n| 리사이즈 가능한 시트 | detent + grabber | medium detent = 점진 공개 · compose 류는 full-height 만 · grabber 가 스크린리더 리사이즈 경로 |\r\n\r\n## 4. 모달 깊이 규칙\r\n\r\n- **모달 깊이 = 1.** 시트 위에 시트를 쌓지 않는다 — 첫 시트를 닫고 다음을 연다.\r\n- 예외 2개뿐: ① alert 는 모든 것 위에 뜰 수 있다(동시 1개만) ② full-screen dialog 위에는 basic dialog(미저장 확인·실패 통보)만 허용.\r\n- 해제 경로는 항상 2개 이상(버튼 + 스와이프/scrim) + 데이터 유실 위험 시 확인 단계.\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. 모바일 화면을 만들기 전에 목적지 수·브레이크포인트로 §1 표에서 컨테이너를 정한다 — \"모바일이니까 햄버거 메뉴\"는 금지 수(§1 마지막 행).\r\n2. 오버레이가 필요하면 §3 표에서 태스크 무게로 표면을 정하고 §4 깊이 규칙을 검사한다.\r\n3. 규칙과 다른 선택을 하면 그 이유를 산출물에 한 줄 남긴다(플랫폼 규범 이탈은 의도여야 한다).\r\n4. 스타일(Liquid Glass·M3 색 역할·인디케이터 모양)은 흡수 대상이 아니다 — look 은 프로젝트 토큰 소유.\r\n\r\n## 집행 경로\r\n\r\n이 문서의 판정 규칙은 자산에 대해 **기계로 집행된다** — `node scripts/validate-mobile-assets.mjs`(= `npm run gate:mobile`)가 `surface_refs: mobile-apps` 자산을 390×844 에서 렌더해 §4 모달 깊이를 포함한 다섯 축을 잰다. 자산이 매이는 조건과 예외는 `docs/design-system/pattern-taxonomy.md` §2 「모바일 표면 자산이 충족해야 하는 것」. (M72, 2026-08-19)\r\n\r\n## Changelog\r\n\r\n- 2026-08-01: 초판 (M7) — HIG(tab-bars 2026-06-08판·sheets·modality) × M3(navigation-bar·bottom-sheets·dialogs·navigation-drawer) 실브라우저 캡처 기반. M3 Expressive drawer 비권장 반영."},{"id":"motion-principles","title":"Motion Principles","description":"Motion quality judgment rules — read BEFORE picking a tier or tool: still-appeal gate (motion cannot rescue a weak still frame), transition purpose taxonomy (continue/create/replace/compare/focus/confirm), one focal element per entrance, motion typography readability, and naming rules (user-facing behavior first, CSS technique second)","tags":["motion","quality","judgment"],"source_path":"knowledge/motion-principles.md","body":"# Motion Principles — 모션 품질 판정 규칙\n\nDate: 2026-07-28\nMilestone: VI6 (plan: `plans/2026-07-28-vi6-knowledge-consolidation.md`)\n지위: 살아있는 정본. \"이 모션이 좋은가\"를 판정한다 — \"무엇으로 만드나\"(티어·도구)는 [[expressive-stack]] 소관이며, 두 문서는 순서가 있다: **원칙 먼저, 도구 나중.**\n근거: knowledge-graph 노드 4+1건 흡수 (`research/2026-07-28-vi6-kg-crosswalk.md` 판정 #2~#5·#10). 각 절에 KG 원본 경로 병기 — 원 출처(remotion-data ANALYSIS 등)는 KG 노드 frontmatter 가 보유.\n\n## 1. Still appeal 게이트 — 모션은 약한 정지 화면을 못 구한다\n\n애니메이션 전에 **정지 프레임이 먼저 통해야 한다**: 타이포·구성·실루엣·대비가 정지 상태로 읽히지 않으면 모션을 얹어도 실패다. 중요한 프레임마다 답한다 — 눈이 먼저 읽는 것은 무엇인가 · 두 번째는 · 배경으로 물러나도 되는 것은 · focal anchor 는 어디인가 · 대비가 한 곳에 집중되는가 · beat 이 안착할 여백이 있는가.\n\n- 씬당 승리 요소는 하나. 고채도·고휘도 강조도 하나. 비활성·배경 요소는 대비·채도·선명도를 낮춘다.\n- 등장 순서는 위계와 일치시킨다 — 먼저 등장하는 것이 primary 로 읽힌다.\n- 실무 진단: **그레이스케일 + 블러 리뷰**에서 의도한 primary 가 사라지면 위계가 약한 것이다 — 애니메이션 전에 고친다.\n\n(KG `nodes/디자인/motion-styleframe-visual-hierarchy.md` · `nodes/디자인/motion-animation-principles-product-motion.md`, 접근일 2026-07-28)\n\n## 2. 목적 먼저, 효과 나중 — 전환의 의미 6분류\n\n전환 효과(fade/slide/match/morph/crop/stagger/shared element)를 고르기 전에 **전환의 목적**을 먼저 고른다:\n\n| 목적 | 뜻 |\n|---|---|\n| continue | 같은 흐름의 지속 |\n| create | 새 것의 진입 |\n| replace | 상태 교체 |\n| compare | 두 상태 비교 |\n| focus | 주목 이동 |\n| confirm | 결과 확인 |\n\n- 복잡한 전환에서는 **shared/focal 요소 하나**를 안정적으로 유지하고 나머지가 그 주변에서 출입하게 한다 — shared element 가 많거나 경로가 교차하면 관계가 안 읽힌다.\n- 지속시간은 프레임 수가 아니라 목적·이동 거리·표면적 변화로 정한다 (짧게/표준/확장). [[expressive-stack]] 규칙 6(전환 규모 비례)의 상위 판단이다.\n\n(KG `nodes/디자인/motion-semantic-transition-design.md`, 접근일 2026-07-28)\n\n## 3. 등장 안무 — 한 번에 하나\n\n- 한 번에 **하나의 focal element** 만 등장시킨다. 동시 등장은 의도된 예외로만.\n- 관계를 명확히 할 때만 보조 요소가 지연·정착(follow-through)하게 한다.\n- 이징 기본값은 명시적 easing 또는 스프링 — **linear motion 은 이유 있는 예외**다.\n- 타이밍은 톤에 맞춘다: 차분한 제품·코퍼레이트는 정제된 페이싱, 프로모·액션은 빠르게.\n\n(KG `nodes/디자인/motion-animation-principles-product-motion.md`, 접근일 2026-07-28)\n\n## 4. 움직이는 텍스트의 가독성\n\n텍스트 비중이 큰 모션(슬라이드·모션 그래픽·히어로 타이포)은 시간 압박 아래의 커뮤니케이션이다:\n\n- role map(headline/support/label/caption/CTA)과 read order(first/second/last)를 정하고, 모든 텍스트를 동시에 애니메이션하지 말고 **의미 우선순위로 스태거**한다.\n- 텍스트가 빠르게 움직이면: 카피를 줄이거나 · 크기를 키우거나 · 자간을 넓히거나 · 블러를 줄인다.\n- 보이스오버 주도가 아니면 **음소거 리뷰**로 텍스트만으로 전달되는지 확인한다.\n\n(KG `nodes/디자인/motion-typography-readability.md`, 접근일 2026-07-28)\n\n## 5. 호명 규칙 — 동작을 먼저, 기법을 나중에\n\n스펙·코드 리뷰·지식 문서에서 모션을 서술할 때 **사용자가 보는 동작을 먼저 이름 짓고, CSS 기법을 그다음에** 짓는다.\n\n- 구현 언어(스펙·리뷰용): disclosure, collapsible region, expand/collapse, fade, clipping, layout interpolation, pointer guard, focus guard, ARIA state.\n- 제품 언어(사용자 대면 카피용): \"open details\", \"show filters\", \"hide completed items\" — 두 언어를 섞지 않는다.\n\n(KG `nodes/개발/ui-state-motion-vocabulary.md`, 접근일 2026-07-28)\n\n## 6. 미리보기는 실시간이 아니어도 된다\n\n목록·탭·작업 표시줄에서 **내용을 열지 않고 미리 보여 주는** 썸네일은 실시간 프레임률로 그릴 이유가 없다. 사용자가 그 순간 판단하는 것은 \"저 안에서 무엇이 움직이는가\"이지 정확한 프레임이 아니다.\n\n- **15fps 로 충분하다** (관측: daedalOS 작업 표시줄 피크 — `research/m60-round6/p2-daedalos.json` 행 `r6-18`). rAF 루프를 그대로 돌리지 말고 프레임을 솎는다.\n- **최대 폭을 고정하고 높이를 비례로** 잡는다. 원본 크기를 그대로 캡처하면 미리보기 하나가 본 화면보다 비싸진다.\n- 캡처 대상에서 의미 없는 노드(소스 요소 등)는 필터로 뺀다.\n- 미리보기가 사라질 때 루프를 반드시 멈춘다 — 보이지 않는 썸네일이 계속 그려지는 것이 이 패턴의 기본 실패 모드다.\n\n정지 화면으로 충분한지 먼저 묻고, 움직임이 판단에 필요할 때만 라이브로 간다. 흡수 판정은 **B(축소이식)** 였다 — 원본은 `html-to-image` 의존이라 그대로 옮기면 무겁고, 옮길 것은 구현이 아니라 위 판정이다.\n\n## 7. 사이트 하나의 이징은 두셋이다\n\n커브를 매번 새로 고르면 리듬이 생기지 않는다. 사이트의 모션이 하나의 감속감으로 읽히는 것은 **커브를 등록해 두고 부르기 때문**이다.\n\nPartizan 실측(2026-08-17, `research/m67-batch2-css/p2-ledger.json` 행 `pz2-08`) — 브랜드 이름으로 CustomEase **3종**을 등록하고 전 모션이 그것만 참조한다. 사용 빈도가 하나로 쏠린다: `alpha` 68회 · `inOut` 22회 · `inOut2` 2회. 즉 **지배 커브 하나 + 보조 하나 + 특수 하나**다.\n\n같은 배치의 다른 두 곳도 습관은 같고 값만 다르다 — Made With Gsap 은 `cubic-bezier(.9,0,.1,1)` 과 CSS `linear()` 스프링을 반복하고, NORMAL IS BORING 은 `power3.inOut`·`power3.out`·`power2.out` 셋이 사실상 전부다.\n\n- **「이징을 몇 개 둘 것인가」가 「어느 커브인가」보다 상위 결정이다.** 후자는 취향이고 전자는 시스템이다.\n- 새 연출에 새 커브가 필요하다고 느끼면, 커브가 부족한 것이 아니라 **duration 이나 stagger 로 풀 문제**일 때가 많다.\n- 토큰으로 등록하면 이름이 곧 용도가 된다 — recipe 마다 커브를 따로 적는 지금 방식은 이 규칙이 없는 상태다.\n\n## 8. 전환은 걷어내는 쪽이 절반이다\n\n들이는 연출만 설계하고 걷어내는 쪽을 두지 않으면, 라우터 전환이 반복될수록 이전 화면의 애니메이션이 살아남아 쌓인다. 화면에는 안 보이고 프레임만 먹는다.\n\nMade With Gsap 실측(행 `mwg-12`) — 이탈마다 ① `ScrollTrigger` 전건 kill ② `<main>` 하위를 타깃으로 가진 트윈만 **선별** kill ③ DOM 교체 ④ 커튼 타임라인만 재사용. 전역 `killAll()` 이 아니라 **범위를 좁힌 kill** 인 것이 핵심이다 — 전역으로 죽이면 같은 페이지의 다른 연출까지 죽는다.\n\n- **정리 코드는 값어치가 화면에 나타나지 않는다.** 그래서 빠뜨리기 쉽고, 빠뜨린 것도 한동안 안 보인다. [[page-transition-modes]] 는 그 값어치를 「살아 있는 애니메이션 수」 계기로 드러낸다.\n- 적용처: [[curtain-page-transition]]·[[view-transition-language-crossfade]]·[[shared-element-flip-transition]] — 전환을 소유한 recipe 는 언마운트 규율을 함께 갖는다.\n\n## 9. 겹치는 동작은 구간이 아니라 이징이 잇는다\n\n\"A 가 끝나기 전에 B 가 가로채듯 들어온다\"를 **구간을 겹치는 것**으로만 지시하면 여전히 \"끝나고 나서 시작\"으로 보인다. 앞으로 몰린 이징(`cubic-bezier(0.16, 1, 0.3, 1)` 같은 강한 ease-out)은 **구간의 30% 지점에서 사실상 끝나서**, 70% 를 겹쳐 놓아도 실제로 겹친 것은 눈에 안 보이는 잔여뿐이기 때문이다.\n\n§7 의 \"두셋\"을 용도로 가른다:\n\n| 용도 | 성질 | 실측값 |\n|---|---|---|\n| 단발 동작 | 앞으로 몰린다 | `bezier(0.16, 1, 0.3, 1)` |\n| 겹치는 동작 | 중간에 무게가 실린다 — 옆 동작과 손이 맞는다 | `bezier(0.38, 0.02, 0.28, 1)` |\n| 훑고 지나가는 경계선 | 거의 균등 | `bezier(0.35, 0.15, 0.45, 0.95)` |\n\n- **경계선에 S자 이징을 쓰지 않는다.** wipe·`clip-path` 로 여는 연출에 S자를 걸면 드러남이 창의 가운데 몇 프레임에 몰려 \"켜졌다 꺼진 것\"으로 읽힌다. 지나가는 경계선은 일정한 속도로 가야 경계선으로 읽힌다.\n- **훑기의 이동 폭은 상자에 딱 맞춘다.** 범위가 상자(0~100%, 기울기가 있으면 그만큼 더)보다 넓으면 구간 앞부분만 쓰이고 나머지는 이미 다 드러난 채로 흘러간다 — 훑기가 순식간에 끝난 것처럼 보이는 원인이다.\n\n(KG `nodes/디자인/motion-overlap-needs-easing-not-just-range.md`, 실측 2026-09-13)\n\n## 10. 함께 보일 것은 함께 움직인다 — 부품을 따로 안무하지 않는다\n\n로고 락업·카드 묶음·라벨 붙은 도형처럼 **최종적으로 한 덩어리로 읽혀야 하는 것**은, 부품마다 좌표를 계산해 각자 움직이게 짜지 않는다. 최종 배치대로 부품을 담은 **상자 하나**를 움직이고, 부품은 상자 안에서 프레임과 무관한 상수 좌표를 갖고 **불투명도만**으로 등장한다.\n\n- 마지막 프레임이 원본 배치와 기하적으로 같아진다 — 배치를 두 군데에서 계산하지 않으니까.\n- 부품끼리 겹치는 일이 **구조적으로 불가능**해진다. 원본 배치의 간격이 곧 둘 사이의 거리다.\n\n**따로 움직이고 있다는 신호**: 등장하는 부품이 다른 부품 뒤에서 배어 나온다 → z-order 를 DOM 순서로 잡게 된다 → 겹침을 막으려고 `clip-path` 로 한쪽을 잘라 연다. 셋째는 훑기 연출처럼 보이지만 땜질이다. 실측 사례에서 상자 하나로 합치자 셋이 전부 코드에서 사라졌다.\n\n**함정** — 상자의 배율과 이동을 서로 다른 램프로 몰 때(겹치는 연출에서는 그래야 한다), 부품의 화면 좌표는 `x_box + x_local · s` 다. `x_box` 의 출발점을 상수로 두면 축소가 진행되는 동안 부품이 한쪽으로 **흘러간다**. 출발점을 현재 배율에 묶는다 — `x_box = lerp(p, -x_anchor * s, 0)`. 코드를 읽어서는 잘 안 보이고 **중앙선을 그려 넣은 스틸 대조**로 잡힌다.\n\n(KG `nodes/디자인/motion-composite-moves-as-one-unit.md`, 실측 2026-09-13)\n\n## 11. 크기는 변환으로 바꾼다 — 레이아웃 크기를 애니메이션하면 래스터가 깨진다\n\n브라우저가 그리는 모션에서 요소를 크게 보였다 작게 보이게 하는 길은 둘이다 — 레이아웃 크기(`width`/`height`)를 바꾸거나 `scale` 변환을 걸거나. 좌표 결과는 같지만 **래스터 품질은 같지 않다.** 레이아웃 크기를 프레임마다 바꾸면 브라우저가 매 프레임 새 크기로 다시 래스터화하는데, 그 경로는 정지 이미지 한 장을 리샘플링하는 경로가 아니라 빠른 쪽이다. 가장자리가 계단으로 보인다.\n\n- 상자는 그 요소가 애니메이션 동안 갖는 **최대 크기**로 고정하고 `scale` 로만 줄인다 — 모든 구간이 축소 경로가 되고 확대 경로는 아예 생기지 않는다.\n- 래스터 원본은 최대 표시 크기보다 크게 둔다(LANCZOS 초해상 등). 없던 디테일이 생기지는 않지만 확대 경로를 막는다.\n- 변환은 문자열 조립 대신 개별 속성(`translate`·`scale`·`rotate`)으로 — `docs/design-system/video-contract.md` §Studio 편집 가능 작성 규칙과 같은 처방이다.\n\n**진단 신호**: 같은 화면의 두 요소 중 **크기가 변하는 쪽만** 깨진다. 자산 품질이 원인이면 둘 다 깨져야 한다. 한쪽만이면 원인은 자산이 아니라 그 요소에만 걸린 애니메이션 방식이다 — 실측 사례에서는 이 비대칭을 읽지 못해 윤곽 추적·평활화·벡터 재작도·초해상으로 오진이 넷 쌓였다. 자산을 의심하기 전에 **깨지는 것과 멀쩡한 것의 차이**를 먼저 적는다. 확인은 스틸을 NEAREST 로 4배 확대해서 한다(스튜디오 프리뷰는 한 번 더 스케일을 먹으므로 판정에 쓰지 않는다).\n\n관련: 자산 자체가 손상된 사본일 가능성은 그 전에 거른다 — KG `nodes/디자인/damaged-asset-diagnose-source-before-restoring.md`.\n\n(KG `nodes/디자인/motion-animate-transform-not-layout-size.md`, 실측 2026-09-13)\n\n## 12. 정체가 바뀌는 전환은 단색 구간을 다리로 놓는다\n\n전환은 둘로 갈린다 — **같은 것이 움직이는** 전환과 **무엇인가가 다른 것으로 바뀌는** 전환. 뒤쪽(여러 개가 하나가 되거나, 비율이 바뀌거나, 내용이 통째로 갈리는 것)은 중간 프레임이 정직하게 그려질수록 어색해진다. 어느 프레임에도 \"지금 무엇인가\"라는 답이 없기 때문이다.\n\n전환하는 대상들을 흰 면으로 덮어 개성을 지우고, 모두가 똑같은 흰 판이 된 프레임에서 교체한 뒤, 새 대상에서 흰 면을 걷는다. 관객은 \"하얘졌다가 다시 나타났다\"로 읽고 그 사이에 몇 장이 몇 장이 되었는지 묻지 않는다.\n\n- **다리는 동작의 앞 절반 안에 완성한다.** 단색화가 느리면 대상들이 겹치기 시작할 때 아직 반투명이라 서로의 윤곽·텍스트가 비쳐 — 가리려던 것이 오히려 강조된다. 실측에서 50프레임을 22프레임으로 줄이자 사라졌다.\n- **상자 밖에 그려지는 것은 단색 면이 못 덮는다.** `box-shadow` 가 대표다. 하얗게 만든 카드 뒤에 검은 후광이 원래 개수만큼 남는다. 그림자·외곽 글로우는 단색화와 **같은 램프로 따로** 걷고, 새 대상의 그림자는 내용이 돌아올 때 같이 돌려준다.\n- **교체는 양쪽이 같은 자리·같은 크기·같은 색인 프레임 안에서** 한다. 그 조건이 갖춰지면 교체에 이징이 필요 없다 — 오히려 이징을 걸면 단색 구간의 길이가 흔들려 교체가 구간 밖으로 삐져나온다. 다리 위의 값은 **선형**이다.\n- 부수 효과: 다리 구간의 중간 프레임이 설계 대상에서 빠진다. 전환을 프레임 단위로 다듬는 대신 **경계 두 개**를 지키는 문제가 된다.\n\n(KG `nodes/디자인/motion-blank-bridge-for-identity-change.md`, 실측 2026-09-13)\n\n## 13. 군더더기는 길이가 아니라 관객이 이름 붙일 수 있는 단계 수다\n\n전환이 늘어져 보일 때 길이를 줄여도 답답함이 남는다면, 원인은 **\"이제 ○○합니다\"라고 이름 붙일 수 있는 단계가 하나 더 있다**는 것이다. 전환을 보며 그 문장을 몇 번 말할 수 있는지 센다.\n\n실측(세 조각 → 한 카드)에서는 넷이 나왔고 가운데 둘이 군더더기였다:\n\n| | 단계 | |\n| --- | --- | --- |\n| 1 | 이제 모입니다 | |\n| 2 | 이제 포개진 것이 정렬됩니다 | ← 군더더기 |\n| 3 | 이제 커집니다 | ← 군더더기 |\n| 4 | 이제 내용이 나타납니다 | |\n\n둘 다 **구현하기 편해서 생긴 단계**였다 — 크기가 다른 조각을 작은 자리에 모으는 쪽이 계산이 쉬웠고, 포갠 티를 내려고 엇갈리게 두었다가 다시 맞춰야 했다. 관객에게는 목적이 없는 동작이다.\n\n- **고치는 법은 중간 목표를 없애는 것**이다. 「작은 자리에 모았다가 키운다」와 「엇갈려 놓았다가 맞춘다」는 각각 목표를 둘로 쪼갠 것이다. 처음부터 최종 자리·최종 크기로 보내면 단계가 하나로 합쳐진다. 같은 프레임 수를 써도 군더더기가 사라진다.\n- ⚠ **겹침은 단계가 아니다.** 동작 둘을 겹치는 것(§9 가로채기)은 단계를 늘리지 않고 오히려 하나의 흐름으로 읽히게 만든다. 세는 대상은 「끝나고 나서 시작하는 것으로 읽히는 단계 수」다. 이어지는 두 동작을 관객이 둘로 센다면 단계가 많은 게 아니라 **겹침이 모자란 것**이다.\n\n(KG `nodes/디자인/motion-count-nameable-beats.md`, 실측 2026-09-13)\n\n## 관련\n\n- 미시 상호작용 프레임(trigger/rules/feedback/loops/modes)으로 작은 순간의 실패를 진단하려면: KG `nodes/책/book-microinteractions.md` (규칙 중복 없음 — 링크만).\n- 티어·도구 판정과 접근성 짝규칙: [[expressive-stack]] §판정 절차.\n- 외부 데모·사례 북마크: [[motion-references]].\n\n## Changelog\n\n- 2026-09-13(2): §12(정체가 바뀌는 전환은 단색 구간을 다리로 놓는다)·§13(군더더기는 관객이 이름 붙일 수 있는 단계 수다) 추가. 같은 영상의 S3 합체 연출을 여섯 라운드 고치며 나온 규칙 — KG 노드 2건 흡수.\n- 2026-09-13: §9(겹치는 동작은 구간이 아니라 이징이 잇는다)·§10(함께 보일 것은 함께 움직인다)·§11(크기는 변환으로 바꾼다) 추가. MABC 데모 영상 인트로 로고 락업 실측(Remotion 4.0.520) — KG 노드 3건 흡수. 매체 공통 규칙만 옮기고 Remotion API 는 `docs/design-system/video-contract.md` 에 둔다.\n- 2026-08-18: M67 배치 2 회차 — §7(사이트 하나의 이징은 두셋이다)·§8(전환은 걷어내는 쪽이 절반이다) 추가. 세 사이트 이징 운영 실측과 라우터 teardown 관측. 흡수 판정 A — 값이 아니라 운영 규칙만 옮긴다.\n- 2026-08-17: §6 「미리보기는 실시간이 아니어도 된다」 추가 (M60 회차 6 daedalOS 처분 K2 — 작업 표시줄 라이브 썸네일 15fps 관측). 흡수 판정 B: 구현이 아니라 프레임률·크기 판정만 옮긴다.\n- 2026-07-28: 초판 (VI6 — KG 모션 원칙 노드 4건 + 어휘 노드 1건 흡수)."},{"id":"motion-references","title":"Motion References","description":"External motion/interaction reference bookmarks: GSAP ScrollTrigger demo hubs, spatial 3D landing pattern with its decorative-skin failure gate, observation galleries (60fps.design, landing.love), and the media boundary note for pre-rendered video (Remotion)","tags":["motion","references","bookmarks"],"source_path":"knowledge/motion-references.md","body":"# Motion & Interaction References\r\n\r\n> 인터랙티브 UI를 만들 때 참고하는 외부 자료 북마크. 코드 라이브러리 클론은 toolshelf(`~/projects/toolshelf`)에 있고, 여기엔 레포가 아닌 기법·데모 레퍼런스를 남긴다.\r\n\r\n## 스크롤 내러티브 (scroll-driven storytelling)\r\n\r\n- **GSAP + ScrollTrigger** — 스크롤 핀·스크럽·패럴랙스·단계적 리빌 기법의 가장 깊은 데모 라이브러리.\r\n - 데모 허브: `https://gsap.com/scroll/` · `https://demos.gsap.com/plugin/scrolltrigger/` · CodePen 컬렉션 `https://codepen.io/collection/DkvGzg`\r\n - 라이선스: GSAP 코어 + 전 플러그인(ScrollTrigger 포함)이 2024년 Webflow 인수 이후 **모든 용도 무료**(상업 포함). 의존성으로 추가하기 전 `gsap.com/pricing`에서 현재 약관 재확인.\r\n - 용도: showcase 카드 4 (Scroll-Driven Narrative) 기법 참고. 레포가 아니라 데모 갤러리라 toolshelf에 클론하지 않고 여기 북마크. 우리 모션 primitive로 재구현하거나, 무료화됐으니 `gsap` 직접 추가 중 택.\r\n - 접근일: 2026-07-07\r\n\r\n## 공간형 랜딩 (Three.js walkable/spatial hero)\r\n\r\n포트폴리오·랜딩을 평면 목록이 아니라 **장소**(방·도시·캠퍼스)로 모델링하는 임팩트 패턴. 실사례: Bruno Simon Folio 2025 · Sooah's Room Folio · My Room in 3D · Repolis(레포→3D 도시) 등 — 사례 장부와 실측 리포트는 KG `nodes/디자인/spatial-repo-portfolio-agentic-wayfinding-pattern.md` + toolshelf `3d-spatial-landing-reference-pack`(8사이트) (접근일 2026-07-28).\r\n\r\n**채택 게이트 (decorative-skin failure)**: 3D 가 데이터 위의 장식 스킨이면 실패다 — ① 정보 구조(구역·경로) ② 시각 위계(랜드마크·스케일·조명) ③ 증거 접근(각 목적지에서 실제 콘텐츠 열람)의 3층이 전부 의도적으로 매핑될 때만 공간 메타포가 정당하다. 첫 제품 형태는 도시보다 **룸 스케일**이 적합할 수 있다. LLM 없는 non-agent 폴백(로컬 검색·결정적 라우팅)을 유지한다.\r\n\r\n## 영상 매체 (Remotion) — 매체가 다르다, 계약은 `video-contract.md`\r\n\r\n사전 렌더 영상은 화면 UI 와 게이트가 다르다(프레임 결정론·스틸·프레임 시퀀스 검증). **2026-09-02 M95 부터 이 레포의 4번째 매체다** — 게이트·무대·Element 재흥 규칙·라이선스·관측 경로는 `docs/design-system/video-contract.md` 가 소유하고, 자산은 `examples/ui-vocabulary-site/src/video/elements/` 에 산다(구 「범위 밖 — 링크만」 문구는 이 날 개정). KG 원본은 원리 참조용으로 유지한다 (접근일 2026-07-28):\r\n\r\n- `nodes/디자인/motion-remotion-animation-pattern-library.md` — 프리미티브 패턴 조직법\r\n- `nodes/디자인/motion-remotion-scene-spec-workflow.md` — 씬 스펙·컴포넌트 레지스트리·렌더 루프\r\n- `nodes/디자인/motion-remotion-official-resource-selection.md` — 공식 리소스 역설계 루프\r\n- `nodes/디자인/motion-remotion-frame-render-guardrails.md` — 프레임 기반·CSS 애니메이션 배제 가드레일\r\n\r\n단, 모션 **원칙**(still appeal·목적 6분류·타이포 가독성)은 매체 공통 — [[motion-principles]] 가 정본.\r\n\r\n## 타격감·붕괴 (역설계 실측 3건 — M58 2026-08-16)\r\n\r\n수치 원문과 처분은 `research/2026-08-16-m58-carryover.md` 가 소유한다. 여기엔 재사용되는 판단만 남긴다.\r\n\r\n- **타격감은 시간 정지가 아니어도 성립한다** — js13kGames 2025 1위작 CLAWSTRIKE 는 전역 타임스케일 정지 없이, x 10Hz·y 15Hz 의 **결정론적 사인 카메라 셰이크**(일반 타격 0.05s·진폭 5, 강타격 0.1s·진폭 10)와 공격 쿨다운(0.1s, 6연타 후 0.5s)만으로 타격을 만든다. 난수 지터보다 저용량이고 재현 가능하다. 화면 UI 의 대응물 = 확정 순간의 짧은 이중 주파수 진동 + 재입력 쿨다운. (`https://play.js13kgames.com/clawstrike/`, 접근 2026-08-16)\r\n- **행 단위 붕괴형 리빌** — Dragonfly 의 GL_SMEAR 는 스크롤 진행률을 `1 - progress` 로 뒤집어 1초 `expo.out` 로 트윈한 뒤, 셰이더에서 행 붕괴(위 행이 마지막 행으로 빨려듦) + 가로 늘임(`0.1 * uSmear^2`) + 깊이 페이드 3채널을 합성한다. 난수는 전부 행 인덱스 해시라 프레임 간 안정적이다. 우리 저장고에 없는 계열이라 다음 회차 구현 후보. (`https://www.dragonfly.xyz/`, 접근 2026-08-16)\r\n- **지도 메타포 내비게이션은 zoom 스칼라 하나가 지배한다** — Chartogne-Taillet 은 카메라 고도·패닝 속도·모드 분기(지도/다이브)를 전부 하나의 `zoom.value`(switch 임계 .8)에서 파생시키고, 위치·회전은 목표값을 향해 `easing * delta` 로 감쇠 추종한다. 파라미터를 3벌 따로 두면 이 일관성이 깨진다. (`https://chartogne-taillet.com`, 접근 2026-08-16)\r\n\r\n## 구조 판단 (역설계 실측 6건 — M58 회차 4, 2026-08-16)\r\n\r\n수치 원문과 처분은 `research/m58-round4/` 가 소유한다(`p2-nk-studio.json` 행 ID 로 대조). 여기엔 다른 작업에서 재사용되는 판단만 남긴다. 출처는 전부 20 Years Inspired by People (`https://inspiring.nk.studio/es`, 접근 2026-08-16).\r\n\r\n- **사운드는 효과음 목록이 아니라 단계 번호로 배열한다** — 파일명이 `0-ambient` `1-intro-overview-transition` `2-overview-cards-intro` `3-1/3-2-cards-hover` `4-history-open` `5-overview-drag` 로 연출 순서를 달고 있다. 볼륨도 임의값이 아니라 사다리다: 방금 사용자가 한 일일수록 크고(상세 열기 0.7) 배경에 가까울수록 작다(호버 0.1). 호버는 2종을 교대로 써서 연발을 막고, 드래그는 제스처가 지속되는 동안만 도는 루프다. 이 배열법은 매체와 무관하게 옮겨진다. (r4-03)\r\n- **자산 해석은 로컬 우선 + 짧은 프로브 + 폴백** — `/sounds/<name>.mp3` 를 300ms AbortController 로 HEAD 프로브해 `content-type` 이 audio 계열일 때만 채택하고, 아니면 CDN 으로 간다. 인스턴스는 이름별 참조 카운트로 캐시하고 마지막 구독이 풀리면 언로드 + `revokeObjectURL`. 자체 호스팅으로 옮겨도 호출부를 안 고치는 구조다. (r4-04)\r\n- **관성이 멎기 전에 스냅을 시작한다** — 스냅 판정을 \"속도가 0에 가까운가\"가 아니라 \"감속 중인가\"로 바꾼다: `|v| < |v_prev| - 1.05` 이면서 `|v|` 가 2.5~76 구간이면 최근접 슬롯 정렬을 건다. 멈춘 뒤 끌려가는 느낌 대신 미끄러지는 도중에 빨려드는 느낌이 나온다. (r4-24)\r\n- **라우트 없는 상세 오버레이의 대가를 명시한다** — 33인의 스토리를 개별 URL 없이 클라이언트 상태 패널로만 여는 선택은 전환 연출을 자유롭게 하지만, 정적 HTML 에 본문이 없어 공유·색인이 사라진다. 이 사이트는 쿼리 파라미터 진입로만 남겨 딥링크를 반쯤 회수했다. 같은 선택을 할 때 반드시 함께 결정할 항목. (r4-35)\r\n- **본문 저작 문법은 마크다운 전체가 아니라 필요한 한 줄이면 된다** — `[라벨][href]` 하나만 정규식으로 파싱해 조각 배열을 만들고, 링크를 걷어낸 평문 버전을 함께 반환한다(요약·메타태그용). 편집자에게 문법 하나만 가르치고 렌더러는 20줄로 끝난다. (r4-36)\r\n- **의사요소 + radial-gradient 로 이음매를 그린다** — 탭·말풍선 밑동의 오목한 곡선을 SVG 없이 `radial-gradient(circle at top left, transparent 15px, #fff 16px)` 13×13px 의사요소 2개로 만든다. 1px 차이로 안티에일리어싱 경계를 잡는 것이 요령. (r4-39)\r\n\r\n## 관찰 갤러리 (기법이 아니라 사례를 본다)\r\n\r\n- **60fps.design** — iOS·웹 앱의 마이크로인터랙션·전환·제스처 디테일 큐레이션. `https://60fps.design/` (접근일 2026-07-28)\r\n- **landing.love** — 애니메이션 랜딩 갤러리, 풀페이지 영상 기록 — 스크롤 페이싱·히어로 전환 관찰용. `https://www.landing.love/` (접근일 2026-07-28)\r\n- **Brainwave Interactive Landing** — 애니메이션 헤비 AI 제품 랜딩 완성 사례(히어로 레이어링·스크롤 트리거 섹션). `https://github.com/arnobt78/Brainwave-Interactive-Landing--React-Frontend` (접근일 2026-07-28)\r\n\r\n판정 이력: 위 3건 + 공간형 랜딩 팩은 absorption-criteria **B(링크 참조)** — 재서술·벤더링 금지, 관찰 소스로만 (`research/2026-07-28-vi7-toolshelf-placement.md`).\r\n\r\n## 관련 toolshelf 항목 (코드 클론)\r\n\r\n- `magicui` — 애니메이션 컴포넌트 패턴 (카드 5/6/9)\r\n- `shaders` (paper-design) — WebGL 메시 그라디언트 (카드 6)\r\n- `cmdk` — 커맨드 팔레트 primitive (카드 10)\r\n- `cult-ui` — 편집 캔버스·머티리얼 컴포넌트 (카드 1/7)\r\n- `react-bits` · `motion` — 애니메이션 컴포넌트/엔진 (기존)\r\n\r\n상세 후보 분석: `docs/research/showcase-card-reference-shortlist-2026-07.md`"},{"id":"onboarding","title":"Onboarding","description":"Onboarding judgment rules: design toward the first real success (activation) rather than signup completion, cut every step that is not required to reach it, teach in context at the moment of use instead of front-loading slides, and treat the first empty screen as the primary onboarding surface","tags":["onboarding","activation","first-run","empty-state"],"source_path":"knowledge/onboarding.md","body":"# Onboarding, 온보딩 판정 규칙\r\n\r\nDate: 2026-08-17\r\nMilestone: M57 (plan: `plans/2026-08-17-m57-ux-dictionary-absorb.md`)\r\n지위: 처음 온 사용자를 첫 성공까지 데려가는 흐름의 판정 정본. 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #7·#8 에 동결(원본 `ux-dictionary@60cbab3` `docs/ux-dictionary/methods.yml` 의 `onboarding-ux`·`progressive-onboarding`). 온보딩 중 권한을 묻는 지점은 [[permission-and-notification]] §1 소관, 첫 화면의 빈 상태는 [[empty-and-error-states]] §2 소관, 가입 폼 자체는 [[form-fields]] 소관이다.\r\n\r\nAudience: 에이전트(첫 실행·첫 설정 화면 생성 시 필수 참조) + 사람.\r\n\r\n## 0. 대원칙, 목표는 가입 완료가 아니라 첫 성공이다\r\n\r\n온보딩의 성공 기준을 가입 완료로 잡으면 가입만 잘 되고 사용은 일어나지 않는 화면이 나온다. 기준은 **사용자가 이 제품으로 실제 가치를 처음 얻은 순간**이어야 한다. 협업 도구라면 첫 문서를 남과 공유한 순간이지 계정을 만든 순간이 아니다.\r\n\r\n이 지점을 먼저 정하고 나면 나머지 판정이 기계적으로 따라온다. **그 순간에 도달하는 데 필요 없는 단계는 전부 뒤로 미룰 후보다.**\r\n\r\n## 1. 첫 성공 앞의 단계를 센다\r\n\r\n- 첫 성공에 **반드시 필요한 것**만 앞에 둔다.\r\n- **나중에 물어도 되는 것**은 나중에 묻는다. 프로필 사진, 팀 이름, 알림 취향은 대개 여기에 속한다.\r\n- **우리가 궁금한 것**과 사용자가 필요한 것을 구분한다. 유입 경로 설문은 사용자에게 아무 값도 주지 않는다. 넣으려면 건너뛰기를 준다.\r\n- 값을 보기 전에 결제 정보를 요구하지 않는다.\r\n\r\n## 2. 설명 슬라이드를 온보딩이라고 부르지 않는다\r\n\r\n기능 소개 화면을 3장 넘기게 하는 것은 학습이 아니라 지연이다. 사용자는 아직 맥락이 없어서 그 설명을 기억할 자리가 없다.\r\n\r\n대신 **쓰는 순간에 가르친다.**\r\n\r\n- 해당 기능을 처음 만나는 화면에서 한 문장으로 안내한다.\r\n- 코치마크는 그 화면에서 **하나만** 띄운다. 여러 개를 동시에 띄우면 사용자는 전부 닫는다.\r\n- 한 번 본 안내는 다시 띄우지 않는다. 다시 띄우려면 사용자가 그 사이에 실패했다는 근거가 있어야 한다.\r\n\r\n## 3. 첫 화면의 빈 상태가 곧 온보딩이다\r\n\r\n신규 사용자가 처음 보는 것은 대개 아무것도 없는 목록이다. 이 화면이 온보딩의 본체인데 흔히 방치된다.\r\n\r\n- 이 화면이 무엇으로 채워지는지 보여주고, 첫 항목을 만드는 행동 하나를 눈에 띄게 둔다([[empty-and-error-states]] §1 최초 사용 행).\r\n- 예시 데이터를 넣을 거라면 **그것이 예시임을 알리고 지우는 법을 준다.** 사용자가 자기 데이터와 헷갈리면 신뢰가 깨진다.\r\n- 체크리스트를 쓴다면 항목이 실제 가치와 이어져야 한다. `terms.yml` 의 `onboarding-checklist` 가 이 자리의 물건이다.\r\n\r\n## 4. 건너뛰기를 막지 않는다\r\n\r\n이미 아는 사용자, 두 번째 계정을 만드는 사용자, 남의 초대로 온 사용자가 있다. 건너뛰기가 없으면 이들에게 온보딩은 순수한 손해다. `onboarding-carousel-skip-pattern` 이 이 계약의 물건이다.\r\n\r\n건너뛴 사용자가 나중에 그 안내를 다시 찾을 수 있어야 한다.\r\n\r\n## 5. 판정 절차\r\n\r\n1. 이 제품의 첫 성공 순간을 한 문장으로 말할 수 있는가. 그것이 가입 완료가 아닌가.\r\n2. 첫 성공까지의 단계를 셌는가. 각 단계가 그 순간에 필요한 이유를 댈 수 있는가.\r\n3. 설명 슬라이드로 시작하고 있지 않은가.\r\n4. 첫 빈 화면이 설계돼 있는가.\r\n5. 건너뛸 수 있고, 건너뛴 뒤 되찾을 수 있는가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 (M57). `ux-dictionary@60cbab3` 의 `onboarding-ux`·`progressive-onboarding` 두 항목을 합본으로 흡수. `knowledge/` 에 온보딩 축이 grep 0건이던 공백을 메운다. 원문이 지표 측정(activation metric 정의·이탈 계측)까지 다루지만 그 부분은 이 레포 무게중심 밖이라 담지 않았다(판정 장부 §3-2 참조). 원문의 2차 출처(`material-navigation`)는 1차 확인 전이라 인용하지 않는다."},{"id":"permission-and-notification","title":"Permission & Notification","description":"System-permission and notification judgment rules: never fire an OS permission prompt without an in-app pre-explanation tied to the action that needs it, a denied permission must lead somewhere (never a dead end), one prompt per moment of need, and notifications are chosen by channel and urgency with a visible opt-out on every recurring type","tags":["permission","notification","mobile","consent","interruption"],"source_path":"knowledge/permission-and-notification.md","body":"# Permission & Notification, 권한 요청과 알림 판정 규칙\r\n\r\nDate: 2026-08-17\r\nMilestone: M57 (plan: `plans/2026-08-17-m57-ux-dictionary-absorb.md`)\r\n지위: 시스템 권한 요청과 알림 전달의 판정 정본. 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #3·#4 에 동결(원본 `ux-dictionary@60cbab3` `docs/ux-dictionary/methods.yml` 의 `permission-request-ux`·`notification-ux`). 알림을 **무엇으로 띄우는가**(snackbar·dialog·sheet 등 컨테이너 선택)는 [[mobile-navigation]] §3 소관이고, 이 문서는 **언제 물을 것인가와 어느 채널로 보낼 것인가**를 담는다. 권한 거절 후의 빈 화면 설계는 [[empty-and-error-states]] §1 과 맞물린다.\r\n\r\nAudience: 에이전트(권한 요청·알림 설계 시 필수 참조) + 사람.\r\n\r\n## 0. 대원칙, 요청은 필요해진 순간에 붙는다\r\n\r\n권한과 알림은 둘 다 사용자의 주의를 강제로 가져오는 장치다. 판정 기준은 하나로 모인다. **그 순간 사용자가 원하던 일과 이 요청이 이어져 있는가.**\r\n\r\n앱 첫 실행에 위치·알림·카메라를 몰아서 묻는 화면이 전형적인 실패다. 사용자는 아직 아무것도 하지 않았으므로 허용할 이유가 없고, 시스템 권한은 대개 한 번 거절되면 앱 안에서 되물을 수 없다.\r\n\r\n## 1. 시스템 프롬프트 앞에 앱 안 설명을 둔다\r\n\r\n시스템 프롬프트는 문구를 우리가 못 정하고 재요청도 막힌다. 그래서 순서가 규칙이 된다.\r\n\r\n1. 사용자가 그 권한이 필요한 행동을 시작한다.\r\n2. **앱 안 사전 설명**을 보여준다. 무엇에 쓰는지, 허용하지 않으면 무엇이 안 되는지. 이 화면은 우리가 문구를 정할 수 있고 거절해도 비용이 없다.\r\n3. 사용자가 여기서 동의하면 그때 시스템 프롬프트를 띄운다.\r\n\r\n`terms.yml` 의 `permission-education-screen` 이 2단계의 물건이고 `permission-prompt` 가 3단계다. 2단계를 건너뛰고 3단계로 직행하는 구현이 결함이다.\r\n\r\n**한 순간에 하나만 묻는다.** 두 권한이 동시에 필요해 보이면 대개 흐름을 쪼갤 수 있다.\r\n\r\n## 2. 거절은 막다른 길이 아니다\r\n\r\n거절률은 0 이 될 수 없으므로 거절 경로가 제품의 정상 경로다.\r\n\r\n- 거절한 사용자에게 **무엇이 제한되는지와 대안**을 보여준다. 위치를 거절했으면 주소 직접 입력을 준다.\r\n- 기능 전체를 잠그지 않는다. 권한 없이 되는 부분은 되게 둔다.\r\n- **설정으로 가는 경로를 제공한다.** 시스템 권한은 앱 안에서 되물을 수 없으므로 OS 설정 화면으로 보내는 것이 유일한 복구다. `location-permission-empty` 같은 빈 상태가 이 자리를 맡는다([[empty-and-error-states]] §1 표의 권한 행).\r\n- **재요청은 맥락이 바뀐 뒤에만 한다.** 같은 화면에서 반복해 조르지 않는다.\r\n\r\n## 3. 알림은 채널과 긴급도로 고른다\r\n\r\n전부 푸시로 보내면 사용자는 전부 끈다. 하나를 끄면 다른 것도 같이 꺼지는 것이 문제다.\r\n\r\n| 성격 | 채널 | 판정 기준 |\r\n|---|---|---|\r\n| 놓치면 손해가 나고 시간에 묶임(예약 변경, 결제 실패) | 푸시 | 지금 화면 밖에 있어도 알아야 하는가 |\r\n| 기록이 남아야 함(영수증, 정책 변경) | 이메일 | 나중에 찾아볼 일이 있는가 |\r\n| 지금 이 화면 안의 결과(저장됨, 실패함) | 인앱 토스트·배너 | 화면을 보고 있는 사용자에게만 의미 있는가 |\r\n| 몰아서 확인해도 되는 것(누가 댓글을 달았다) | 알림 센터 | 개별로 깨울 만한가 |\r\n| 마케팅·재방문 유도 | 별도 채널, 별도 동의 | 사용자가 요청한 적 있는가 |\r\n\r\n마지막 행이 규칙의 핵심이다. **마케팅을 핵심 알림 채널로 보내지 않는다.** 한 번 그렇게 하면 사용자가 핵심 알림을 끄고, 그 손해는 되돌리기 어렵다.\r\n\r\n## 4. 반복되는 알림에는 끄는 길이 보인다\r\n\r\n- 반복 유형마다 해제 경로가 있어야 하고, 그 경로가 알림 자체나 설정 화면에서 한두 단계 안에 닿아야 한다.\r\n- **전체 끄기만 있는 설정은 설정이 아니다.** 유형별로 끌 수 없으면 사용자는 전체를 끈다.\r\n- 빈도 상한을 유형별로 정한다. 같은 사건으로 여러 번 깨우지 않는다.\r\n\r\n## 5. 판정 절차\r\n\r\n1. 이 권한 요청이 사용자가 시작한 행동에 붙어 있는가. 첫 실행에 몰려 있지 않은가.\r\n2. 시스템 프롬프트 앞에 앱 안 사전 설명이 있는가.\r\n3. 거절 상태에 대안과 설정 이동 경로가 있는가.\r\n4. 이 알림이 §3 표의 어느 행인가. 푸시여야 할 이유를 댈 수 있는가.\r\n5. 이 알림 유형만 따로 끌 수 있는가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 (M57). `ux-dictionary@60cbab3` 의 `permission-request-ux`·`notification-ux` 두 항목을 합본으로 흡수. `knowledge/` 에 권한 축은 grep 0건, 알림 축은 [[mobile-navigation]] 1줄뿐이던 공백을 메운다. 원문의 2차 출처(`apple-hig`)는 1차 확인 전이라 인용하지 않고, 플랫폼별 수치·API 제약은 담지 않았다."},{"id":"recreated-sites","title":"40개 재현 사이트에서 요소 꺼내 쓰기","description":"메뉴 스크롤 영상 캐러셀 가격 호버 등장 글자 셰이더 — 40개 사이트 원문 기반 모듈 검색과 이식","tags":["recreation","motion","menu","scroll","video","carousel","pricing","shader","Apple","Framer","Rivian","Cloudflare"],"source_path":"knowledge/recreated-sites.md","body":"# 40개 재현 사이트에서 요소 꺼내 쓰기\r\n\r\n이 색인은 승인 회차40곳 전용이다. 이전 페이지·장면·예제를 포함한 전체 목록은 `/site-library/index.json`과 `docs/design-system/site-reference.md`를 본다.\r\n\r\n2026-10-16 사용자 시각 검토 승인. 원시 움직임 잔차는 각 changeset/evidence에 그대로 남아 있다.\r\n이 문서는 검색 색인이다. 본문 코드는 로컬 미배포 자산이며 npm/원격 등재를 뜻하지 않는다.\r\n\r\n로컬 검색: `python scripts/recreate/catalog.py \"메뉴\" --limit 5` (ui-dictionary 루트).\r\n사이트 이름 또는 원본 부품 이름도 검색한다. 전체 목록은 `docs/design-system/recreated-sites.json`.\r\n색인 갱신: `python scripts/recreate/catalog.py --refresh`.\r\n\r\n결과의 모듈 → 사이트 motion.js 마운트 옵션 → 원본 DOM/CSS → decoded 원문 → changeset 근거 순으로 읽는다.\r\n선택자·CSS 변수·상위 컨텍스트·자산·상대 import·정지 함수를 확인하고, 필요한 의존만 함께 옮긴다.\r\n내용은 대상 프로젝트의 문구·로고·사진·영상으로 바꾼다. 해당 프로젝트 토큰으로 restyle하고\r\n진입/이탈/재진입·hover·메뉴·reduced-motion·모바일을 실제 렌더로 검증한다.\r\n파일 하나가 범용 독립 컴포넌트 하나라는 뜻은 아니다. 내부 도우미와 사이트 전용 결합도 포함한다.\r\n정식 recipe/registry 승격은 `methodology/site-recreation/8-fragments.md` 및 RE 계약을 따른다.\r\nHAR/tmp 캐시는 검색 결과나 대상 프로젝트에 복사하지 않는다.\r\n\r\n| 사이트 | 모듈 파일(부품과 내부 도우미 포함) |\r\n|---|---|\r\n| claude-startups | AnimatedHeading, AnimatedReveal, CoverVideo, Dropdown, ExistingRuntime, Illustration, LogoWall, useInView |\r\n| claude-overview | AnimatedHeading, AnimatedReveal, ConstellationMap, Dropdown, ExistingRuntime, HeroClip, HeroVideoCover, NewsToastOverview, OverviewHero, OverviewOrbit, RolesBand, SectionReveal, StepPanel, TestimonialPreload, UseCaseArtifact, useInView |\r\n| claude-code | AnimatedHeading, AnimatedReveal, Dropdown, ExistingRuntime, Illustration, LogoWall, Tabs, useInView |\r\n| apple-iphone-air | AirGalleryState, AirInlineState, AirKeyframes, AirLocalNavTabs, AnimLazyImage, ExistingRuntime, ExpressionParser, GlobalNav, InlineMedia, IntroTurntable, LocaleSwitcher, MediaCardGalleryControl, PictureFrameToggle, PictureLazyLoading, ProductViewerIntro, ScrollTimeline, SpringLayer, StaggeredFadeIn, TextIconControl, TimeTimeline, UpgradeLoadAssets, VideoScrub |\r\n| mistral | Headline, Navigation, PreservedInteractions |\r\n| github-home | Dropdown, HeaderScroll, HeroCarousel, HeroCarouselIcons, IntroScroll, PreservedInteractions, SectionReveals |\r\n| vercel-ship | CanvasShell, EventCountdown, FeaturedSpeakers, PreservedInteractions, ScrambleText |\r\n| shopify-editions-w26 | BackgroundImages, CanvasShell, CloudCards, DavinciLines, MediaVisibility, NavigationScroll, NavigationTheme, OriginalPostprocessing, PreservedInteractions, RiveShell, RolloutParticles, SidekickCard, SkillTags |\r\n| apple-airpods-pro | AudioPerformance, ExpressionParser, GlobalNav, ImageLoadBootstrap, InlineMedia, LocaleSwitcher, LocalnavTrigger, MediaCardGalleryControl, ParallaxImage, PictureLazyLoading, PictureToggleSource, ProductViewerIntro, RetainedInteractions, ScrollTimeline, SpringLayer, StaggeredFadeIn, TextIconControl, TimeTimeline, VideoScrub |\r\n| apple-macbook-pro | AirKeyframes, BatteryAnim, CaptionTileMedia, ChipFamily, ChipHero, ClassKeyframes, ExpressionParser, GlobalNav, HardwareSlide, HardwareZoom, ImageLoadBootstrap, InlineMedia, InlineMediaLoad, LocaleSwitcher, LocalnavTrigger, MediaCardGalleryControl, PictureFrameToggle, PictureLazyLoading, ProductViewerIntro, RetainedInteractions, ScrollTimeline, SpringLayer, StaggeredFadeIn, TextIconControl, TextOverMedia, TimeTimeline |\r\n| apple-ipad-pro | AllAccessPass, ClassKeyframes, ExpressionParser, GlobalNav, HighlightsGallery, ImageLoadBootstrap, InlineMedia, InlineMediaLoad, LocaleSwitcher, LocalnavTrigger, MediaSource, PictureLazyLoading, ProductGalleryColor, RetainedInteractions, ScrollTimeline, TextAnim, TimeTimeline |\r\n| claude-cowork | Dropdown, EmbedFrame, RetainedInteractions, useInView |\r\n| dia-browser | ExistingRuntime, NavigationCta, SunriseEntry, ViewReveal |\r\n| cloudflare-home | AgentRows, CornerLines, CustomerBrowser, HeaderScrollState, InfiniteSlider, LogosCompact, NavDropdown, PixelGrid, PlatformTabs, Popups, PricingCategories, ProductNodes, RetainedInteractions, RotatingText, RotatingTextHover, Shipping, TabIndicator, WorkflowCard |\r\n| raycast-home | ApiFig3, ApiFig4, CmdSpaceKeyboard, ExtensionHighlight, FeatureWall, HotkeysShowcase, RetainedInteractions, ShowcaseReel, SnippetsShowcase, Stars, WorkflowDemo, YoutubeCarousel |\r\n| spotify-premium | BackgroundColorManager, ContentCardGroup, FeatureCardGroup, FeatureGrid, HorizontalLoop, RetainedInteractions, ScrollingHero, ScrollingImageGallery, SpotlightSection, TextReveal |\r\n| apple-vision-pro | AnimLazyImage, DisplayLayers, ExpressionParser, FocusManager, Foundations, GLGradient, GlobalNav, InlineMedia, InlineMediaLoad, LocaleSwitcher, LocalNavTheme, PictureLazyLoading, Portal, ScrollTimeline, Sensors |\r\n| apple-iphone | DeepLink, ExpressionParser, GlobalNav, ImageAccordion, LocaleSwitcher, MediaCardGalleryControl, PictureLazyLoading, ScrollTimeline, SpringLayer, StaggeredFadeIn |\r\n| apple-mac-studio | ChipBentoContent, ChipBentosContainer, ConnectivityReveal, ConnectivitySequence, ExpressionParser, GlobalNav, ImageLoadBootstrap, ImageSequencePlayer, InternalsXRay, LocaleSwitcher, LocalNav, PictureLazyLoading, RetainedInteractions, ScrollTimeline, StatsGallery |\r\n| claude-platform-api | AnimatedHeading, AnimatedReveal, Dropdown, Illustration, LogoWall, useInView |\r\n| microsoft-ai | Locations, MetaNav, SiteReady, SplitText |\r\n| elevenlabs-home | ChladniContours, ChladniHover, CustomerStoryRotator, FloatingAgentLayout, NavigationMenu |\r\n| stripe-home | AgenticCommerce, BackgroundFade, BentoCard, BillingPlan, ConnectPlatform, DevelopersWaveEngine, DeveloperSystems, ExistingLogoStrip, Globe, GlobeCore, GlobeDependencies, GlobeWorker, HeroValue, LazyAnimation, MotionEngine, NavigationMenu, Payments, WaveFirstDraw |\r\n| rivian-r2 | ChargerLayers, ChargingMap, HapticTabs, HeroLoad, HeroScroll, InteriorStopIndicator, LottiePlayer, NavbarTrigger, NavPanelEntry, RetainedInteractions, RivianAssistant, SafetyReveal, StickyNav, TrimReveal |\r\n| claude-financial-services | AnimatedHeading, AnimatedReveal, Dropdown, EmbedFrame, Illustration, LogoWall, NewsToast, SnapshotInteractions, useInView |\r\n| anthropic-opus-5-5 | AnthropicHeader, ArticleInteractions, BenchmarkGrid, ChartFrame, FluidStage, LaunchHero, LogoWordmark, Lottie, ModelComparison, Prefooter, SideToc |\r\n| meta-quest-3 | PreservedMedia, TopNav, TopNavButtons |\r\n| surface-laptop | FloatingButton, InView, PageInteractions, SplitText, SplitTextCore |\r\n| framer-ai | AgentDotAnimation, AnimatedCounter, CookieBanner, FramerScene, GenBorder, InstanceTextProps, LayoutProjection, MotionMixer, MotionSpring, Progress, PromptPlaceholder, ProTypewriter, ResponsiveVisibility, ScrollTransform, ServiceStatus, SignupHover, SnapshotInteractions, StrokeLoop, StyleAppearEffect, VariantController |\r\n| google-flow | FlowNavigation, MenuOverlay, SnapshotInteractions |\r\n| github-universe | HeaderCountdown, LogoEntry, NavScroll, NavUnscramble, SectionReveals, SnapshotInteractions, SpeakerReveal, TestimonialTypewriter |\r\n| linear-next | AsciiLoader, MediaIdle, Navigation, PreservedInteractions, Tooltip |\r\n| deepmind-veo | ExistingRuntime, Jumplinks, MediaVideo, Navigation |\r\n| deepmind-genie | ExistingRuntime, Jumplinks, MediaVideo, Navigation |\r\n| deepmind-alphafold | Jumplinks, Navigation, NewsGroups, PreservedEffects, QuoteAmbient |\r\n| gemini-overview | ArticleMobileNav, Drawer, Header, PreservedInteractions |\r\n| anthropic-claude-opus | LogoWordmark, Lottie, PreservedInteractions |\r\n| xai-grok-4 | AgentOrb, ArrowIcon, BentoImagine, BentoPreview, BentoRender, BotConvo, BotEntries, BotMark, BotRender, DOM, DotLoader, HeaderNav, HeaderScroll, HoverHighlight, ImagineImages, MotionEngine, OrbRenderer, PageInteractions, TerminalPrimitives |\r\n| perplexity-comet | MediaLifecycle, MotionEngine, NavHover, NavScrollVariant, PageInteractions, planet-effects, PlanetAppear, ScrollTransform |\r\n| figma-config | EmbeddedMedia |"},{"id":"scroll-collapse-header","title":"Scroll-Collapse Header","description":"Weather-app style header collapse — one scroll progress value drives per-layer parallax coefficients, threshold-staggered fade-outs, and a final absorb-into-background step; the mechanics are asset-agnostic so illustration layers can be swapped without touching the wiring","tags":["motion","scroll","header","pattern"],"source_path":"knowledge/scroll-collapse-header.md","body":"# Scroll-Collapse Header — 스크롤 붕괴 헤더 패턴\n\nDate: 2026-09-02\n지위: 살아있는 정본. 일러스트/씬 헤더가 스크롤에 반응해 접히는 방식을 판정한다. \"이 모션이 좋은가\"의 상위 판단은 [[motion-principles]], 티어·도구 선택은 [[expressive-stack]] 소관.\n근거: askewly-command M132 실구현(오너 실기기 판정 \"스크롤 느낌 괜찮아\", 2026-09-01) + 상용 날씨 앱 관찰·리서치(`askewly-command/research/2026-09-01-m132-header-methodology.md` — KG `nodes/모바일/layered-parallax-fade-color-absorb-scroll-pattern` 병행 인입). 참조 실물: 갤럭시 One UI 날씨 앱의 헤더 붕괴.\n\n## 패턴 — 진행도 하나, 세 가지 차등\n\n씬 헤더는 스크롤을 따라 **한 번에 사라지지 않는다.** 레이어마다 다르게 접혀야 깊이가 생긴다. 구동은 전부 **스크롤 진행도(progress) 값 하나**에서 파생한다 — 레이어별로 타이머·상태를 따로 두지 않는다.\n\n1. **레이어별 패럴랙스 계수** — 각 레이어는 `translateY = scroll × k` 로 밀린다. k 가 다르면(예: 태양 0.45 · 구름 0.18/0.3/0.5) 스크롤 자체가 깊이를 연출한다. 원경일수록 작게, 근경·소품일수록 크게.\n2. **threshold 차등 fade** — fade 곡선은 같고 **시작·끝 스크롤 범위만 레이어마다 다르다**(예: 태양 [0,140] · 구름 [0~25, 130~190] · 씬 바탕 [90,230]). 소품이 먼저, 바탕이 마지막에 사라지는 순서가 \"장면이 걷힌다\"로 읽힌다.\n3. **배경 흡수** — 마지막 단계는 씬이 앱 배경색으로 녹는 것이다. 씬 바탕의 최후 fade(또는 배경색 보간)로 만든다. 헤더가 사라진 뒤 남는 것은 앱의 평상시 표면이어야 한다 — 경계선·잔상을 남기지 않는다.\n\n## 판정 규칙\n\n- **헤더는 고정 크롬이 아니라 스크롤 콘텐츠다.** 리스트를 밀면 같이 밀려 올라간다. 고정해 두고 투명도만 바꾸는 것은 이 패턴이 아니다.\n- **역학과 에셋을 분리한다.** 패럴랙스 계수·fade 범위는 레이어 슬롯에 붙고, 그림(SVG/비트맵)은 슬롯에 꽂힌다 — 에셋을 갈아끼워도 배선이 그대로 살아야 유지비가 낮다.\n- **주변광 모션과 붕괴 모션은 합성한다.** 상시 드리프트(느린 루프, ≥20s)와 스크롤 구동 transform 은 같은 레이어에서 곱으로 결합한다 — 서로를 끄지 않는다.\n- 상용 구현의 완전 재현은 목표가 아니다 — Apple 날씨는 데이터 기반 절차 렌더링(다른 계열)이고, 재현 시도 1차 기록도 sticky fade-out 완전 재현 실패를 인정한다(리서치 §2). 위 3요소가 충족되면 사용자 눈에는 같은 문법으로 읽힌다.\n\n## 구현 스케치 (React Native / Reanimated 기준)\n\n```\nscrollY (SharedValue) ← 스크롤 핸들러\nlayer.style = {\n opacity: base × interpolate(scrollY, fadeRange_layer, [1,0], CLAMP),\n transform: [{ translateY: scrollY × k_layer }, ...주변광 transform],\n}\nsceneBase.opacity = interpolate(scrollY, [진입 늦게, 헤더높이 근처], [1,0], CLAMP)\n```\n\n실구현 정본: `askewly-command/mobile-v2/src/components/HeaderArt.tsx` + `Screen.tsx` (M132 step-9)."},{"id":"session-persistence-boundary","title":"Session Persistence Boundary","description":"Deciding what a UI remembers across visits and what it deliberately forgets: user-authored arrangement (position, size, sort order, view mode) persists, while momentary traces (stack order, foreground focus, load flags) are dropped on purpose; includes the observed schema split and how to apply the rule","tags":["session","persistence","state","windows","workspace"],"source_path":"knowledge/session-persistence-boundary.md","body":"# 재방문 지속의 경계 — 무엇을 기억하고 무엇을 잊을 것인가\r\n\r\nDate: 2026-08-17\r\nMilestone: M60 (plan: `plans/2026-08-17-m60-round6-prep-and-maintenance.md`, 처분 K1)\r\n지위: 살아있는 정본. 관측 근거는 `research/m60-round6/p2-daedalos.json` 행 `r6-19`(동결 기록).\r\n출처: daedalOS (`https://dustinbrett.com` · `github.com/DustinBrett/daedalOS`, MIT, HEAD `0df82d7`) — 회차 6 소진형 심층에서 0.5층 정적 직독으로 스키마 전 구간 확보.\r\n\r\nAudience: 사용자가 상태를 만드는 표면(작업 공간, 편집기, 대시보드, 파일 목록)을 만드는 에이전트. \"이걸 저장해야 하나\"를 판정하는 데 쓴다.\r\n\r\n## 왜 데모가 아니라 지식으로 착지했나\r\n\r\n이 규칙은 **값어치가 화면에 나타나지 않는다.** 다시 방문했을 때에만 드러나므로 한 화면짜리 데모로 보여 줄 수 없다. 계약 §1 의 「시각이 아니라 규칙이 값어치인 기법」 경로를 따랐다(선례 [[adjacency-level-lod]]).\r\n\r\n## 규칙\r\n\r\n**사용자가 만든 배치는 남기고, 방금 무엇을 했는지의 흔적은 버린다.**\r\n\r\n가르는 질문은 하나다: **이것을 잃으면 사용자가 다시 해야 하는 일이 생기는가.** 창을 다시 배치하고, 정렬을 다시 고르고, 뷰 모드를 다시 바꾸는 것은 노동이다. 반면 어느 창이 맨 위였는지는 다시 만들 필요가 없다 — 다음에 클릭하는 것이 맨 위가 되면 그만이다.\r\n\r\n## 관측된 경계 (daedalOS `SessionData`)\r\n\r\n| 남긴다 | 버린다 |\r\n|---|---|\r\n| 창의 위치·크기 (`windowStates`) | 창 겹침 순서 (`stackOrder`) |\r\n| 아이콘 위치 (`iconPositions` — 격자 좌표, [[adjacency-level-lod]] 참조) | 지금 활성인 창 (`foregroundId`) |\r\n| 디렉터리별 정렬 (`sortOrders`) | 세션 로드 완료 여부 (`sessionLoaded`) |\r\n| 디렉터리별 뷰 모드 (`views`) | |\r\n| 최근 항목·실행 이력 (`recentFiles`·`runHistory`) | |\r\n| 테마·배경 (`themeName`·`wallpaperImage`·`wallpaperFit`) | |\r\n\r\n버리는 셋은 런타임 컨텍스트(`SessionContextState`)에만 있고 저장 스키마(`SessionData`)에는 아예 없다 — **저장을 잊은 것이 아니라 타입 수준에서 분리해 둔 것**이다. 이 분리 자체가 규칙의 표현이다.\r\n\r\n## 적용\r\n\r\n- **저장 목록을 타입으로 분리하라.** \"저장되는 것\"과 \"런타임에만 있는 것\"을 한 객체에 섞으면 무엇이 지속되는지 읽을 수 없고, 실수로 순간 상태가 디스크에 눌어붙는다.\r\n- **위치는 가능하면 상대 좌표로.** 픽셀로 저장한 배치는 다음 방문의 화면 크기가 다르면 무너진다. 격자·비율·앵커 기준이 있으면 그쪽을 쓴다([[adjacency-level-lod]]).\r\n- **되살릴 때도 경계 검사를 다시 한다.** 저장 시점에 유효했던 위치가 지금도 유효하다는 보장이 없다(화면이 작아졌을 수 있다). 복원 값을 그대로 믿지 말고 현재 뷰포트로 클램프한다.\r\n- **쓰기 가능 여부를 전역 플래그로 잠글 수 있게 하라.** daedalOS 는 `window.sessionIsWriteable` 로 세션 저장을 통째로 막는다 — 초기화 중이거나 사용자가 리셋을 원할 때 저장 경로 하나만 끄면 된다.\r\n\r\n## 언제 쓰지 않나\r\n\r\n- 사용자가 배치를 만들지 않는 표면(읽기 전용 문서, 랜딩)에는 지속시킬 것이 없다.\r\n- 공유 링크로 도달하는 화면에서는 **URL 이 상태의 정본**이어야 한다 — 세션 저장이 URL 을 이기면 링크를 열 때마다 남의 배치가 덮인다.\r\n- 민감한 내용(최근 항목·검색 이력)은 지속의 이득보다 노출 위험이 클 수 있다. 남길 값의 성격을 따로 판정한다.\r\n\r\n## 방문 사실도 지속의 대상이다 (M67 2026-08-18)\r\n\r\n위 표는 **사용자가 만든 배치**를 다룬다. 그런데 지속시킬 값이 하나 더 있다 — **이 사람이 여기 와 본 적이 있는가.** 배치가 아니라 방문 사실이고, 그것을 남기면 대기 연출을 두 번 시키지 않을 수 있다.\r\n\r\nNORMAL IS BORING 은 `localStorage` 플래그 하나(`first_charge`)로 초회와 재방문에 **완전히 다른 타이밍**을 태운다(관측 2026-08-17, `research/m67-batch2-css/p2-ledger.json` 행 `nib-02`):\r\n\r\n| | 초회 | 재방문 |\r\n|---|---|---|\r\n| 진행률 | 실제 이미지 로드율을 5ms 카운터가 뒤쫓는다(앞지르지 못하게 클램프) | 진행 바·숫자를 즉시 감춘다 |\r\n| 본문 | 로드 완료 후 노출 | **즉시** 노출 |\r\n| 브랜드 | 카운터와 함께 재생 | 0.5초 뒤 로고만 다시 조립 |\r\n| 상호작용 개방 | 완료 + 1.5초 | 3초(고정) |\r\n\r\n**규칙: 대기 연출의 값어치는 첫 만남에 있다.** 두 번째 방문자는 그 연출을 이미 봤으므로 같은 시간을 다시 낼 이유가 없다 — 브랜드는 짧게 되풀이하고 본문은 먼저 준다.\r\n\r\n- 가르는 질문은 위와 같다: **이것을 잃으면 사용자가 다시 해야 하는 일이 생기는가.** 방문 사실을 잃으면 사용자는 **기다리는 일**을 다시 한다.\r\n- 다만 이것은 배치와 달리 **잃어도 치명적이지 않다** — 플래그가 없으면 초회 경로로 떨어질 뿐이다. 그래서 저장 실패를 방어할 필요가 없다.\r\n- 적용처: [[fake-progress-preloader]]·[[multi-stage-entry-loader]]·[[reward-reveal-loader]] 세 recipe 가 전부 「매번 같은 대기」를 전제한다. 이 규칙이 그 셋의 적용 조건을 정한다.\r\n\r\n## 관련\r\n\r\n[[adjacency-level-lod]](격자 기준 저장 — 이 문서의 \"상대 좌표로\" 항목의 구체 사례) · `docs/design-system/reverse-engineering-contract.md` §1(규칙형 기법의 착지 판정).\r\n\r\n## Changelog\r\n\r\n- 2026-08-18: M67 배치 2 회차 — 「방문 사실도 지속의 대상이다」 절 추가(NORMAL IS BORING 초회/재방문 분기 관측). 흡수 판정 A, 데모가 아니라 지식으로 착지한 이유는 초판과 같다 — 값어치가 재방문에만 드러난다.\r\n- 2026-08-17: 초판 — M60 회차 6(daedalOS) 처분 K1 의 지식 착지. 데모 없이 규칙만 남긴다."},{"id":"slide-principles","title":"Slide Principles","description":"What makes a good presentation slide: 5 convergent principles from 8 master lineages (one message per slide, title-as-assertion with Penn State evidence, visual evidence over bullets, audience-first editing, structure before design) with evidence grades, plus HTML-native expressive techniques (SVG filters, chart/3D embeds) and their caveats","tags":["slides","deck","presentation"],"source_path":"knowledge/slide-principles.md","body":"# Slide Principles — 무엇이 좋은 발표 슬라이드인가\n\n> 발표 슬라이드의 콘텐츠·구조 원칙. 거장 방법론 8계보가 수렴하는 5원칙과 표현 기법을 담는다.\n> 제작 절차(\"어떻게 만드는가\")는 [slide-production](../methodology/slide-production.md), 캔버스·대비·통설 규칙의 **게이트 정본**은 [slide-spec](../docs/design-system/slide-spec.md) — 이 문서는 게이트를 재서술하지 않는다.\n> 일반 지식 원본(KG): `nodes/디자인/slide-deck-convergent-principles.md` — 여기는 Askewly Design 적용 관점만 적는다.\n\n## 수렴 원칙 5\n\nMinto(피라미드 원칙)·Michael Alley(assertion-evidence)·맥킨지(action title)·Nancy Duarte(slide:ology/Resonate)·Garr Reynolds(Presentation Zen)·Guy Kawasaki(10/20/30)·스티브 잡스 키노트 분석(카민 갤로)·speaking.io(Zach Holman) — 표현만 다를 뿐 같은 곳으로 수렴한다. (전체 출처 장부: `research/2026-07-28-sl1-slide-methodology-research.md`, 접근일 전부 2026-07-28)\n\n| # | 원칙 | 계보 | 에이전트 규칙화 |\n|---|---|---|---|\n| 1 | **슬라이드당 메시지 하나** — 메시지가 둘이면 슬라이드도 둘 | 맥킨지 · Alley · Jobs | 자동 검사 가능 |\n| 2 | **제목이 곧 주장** — 명사구 제목(\"결과\", \"개요\") 금지, 완결 문장. 제목만 읽어도 스토리가 통해야 함(title-only read test) | Alley(assertion) · 맥킨지(action title) · Minto(결론 우선) | 자동 검사 가능 |\n| 3 | **텍스트 대신 시각 증거** — 불릿 나열 지양, 그래프·다이어그램·이미지가 주장을 증명 | Alley(evidence) · Reynolds · Duarte · Jobs · Kawasaki(30pt 하한) | 부분 검사 가능 (비율 휴리스틱) |\n| 4 | **청중 관점 편집** — 발표자가 하고 싶은 말이 아니라 청중이 받아야 할 말. MECE + 핵심 메시지 의도적 반복 | Duarte · Minto(MECE) · speaking.io | 프로세스 규칙 |\n| 5 | **구조 확정이 디자인보다 먼저** — 청중 분석 → 논리 구조(SCQA) → 스토리보드 → 그다음 슬라이드 도구 | Duarte · Minto · Reynolds · Jobs(과잉 리허설) | 프로세스 규칙 (순서 강제) |\n\n근거 무게는 같지 않다 — [slide-spec](../docs/design-system/slide-spec.md)의 등급 철학을 따른다:\n\n- **원칙 2는 실증이 있다**: Penn State assertion-evidence 연구가 전통 불릿 슬라이드 대비 이해도·기억 회상 우위를 보고 (Michael Alley 계열, ASEE: `https://peer.asee.org/assertion-evidence-slides-appear-to-lead-to-better-comprehension-and-recall-of-more-complex-concepts.pdf`, 접근일 2026-07-28).\n- **수치 규칙은 대부분 통설(folklore)이다**: Kawasaki 10장/20분/30pt(원저자 `https://guykawasaki.com/the_102030_rule/`, 접근일 2026-07-28)는 저자는 특정되나 숫자의 실증이 없다 — slide-spec §3이 24pt를 `folklore` 등급 옵트인 경고로 두는 것과 같은 이유로, 이 수치들을 차단 규칙으로 승격하지 않는다.\n- speaking.io는 반대 방향의 수치 조언을 준다 — 장수를 줄이지 말고 **장당 정보 밀도**를 줄여라(`https://speaking.io/plan/number-of-slides/`, 접근일 2026-07-28). \"10장 이내\"와 충돌하는 게 아니라, 제약 대상이 장수가 아니라 밀도임을 보여준다.\n\n## 표현 기법 — HTML이 제작 표면일 때 열리는 것\n\n출처: 코딩애플 영상 2건 요약(사용자 제공 2026-07-28) — ① \"관종이 될 수 있는 PPT\" `https://www.youtube.com/watch?v=2kdo2ZLTG_E` ② \"진정한 남자는 포토샵 대신 html 쓴다\" `https://www.youtube.com/watch?v=RrYPBkmnUwc`.\n\n- **PPT 대비 이점**: 애니메이션 자유도, Chart.js 차트, Three.js 3D, 실시간 데이터 반응, 브라우저·모바일 대응, 깃허브 페이지 배포. 이 레포의 [expressive-stack](expressive-stack.md) 티어와 recipe 층([motion-principles](motion-principles.md) 원칙 포함)이 슬라이드에도 그대로 표현 재료가 된다.\n- **SVG 필터 기법**: feTurbulence + displacement map(물결·자글거림 뒤틀기), feGaussianBlur + feColorMatrix(슬라임/liquid 병합), `<animate>`(프레임 작업 없는 필터 속성 애니메이션). 임팩트 장면용 — 본문 가독 영역에는 쓰지 않는다.\n- **보안 주의**: SVG 필터는 픽셀 단위 색상 조작이 가능해 클릭재킹(가짜 UI 겹치기)에 악용된 실사례가 구글 보안 리포트에 있다 — 외부 SVG/필터 코드를 슬라이드에 반입할 때 출처 검증.\n- **실전 주의 3가지** (HTML 발표에서 매번 명시해야 하는 것): 한글 폰트 지정, 글자 크기, 다크모드 대비 — 기본값에 맡기면 export에서 깨진다. 레이아웃이 많아지면 파일 분리(단일 파일 비대화 방지).\n\n## 관련\n\n- 제작 절차·export 경로·엔진 선택: [slide-production](../methodology/slide-production.md)\n- 게이트(캔버스 프리셋·WCAG 대비·통설 규칙 옵트인): [slide-spec](../docs/design-system/slide-spec.md) · 매체 판정: [medium-taxonomy](../docs/design-system/medium-taxonomy.md)\n- KG 일반 지식: `slide-deck-convergent-principles` · `html-first-slide-export-pipeline` (`~/projects/knowledge-graph`)\n\n## Changelog\n\n- 2026-07-28: 초판 (SL1 step-1 — 리서치 `research/2026-07-28-sl1-slide-methodology-research.md` 흡수)."},{"id":"spacing-and-alignment","title":"Spacing & Alignment","description":"Why an interface reads as tidy: pick one base unit and use only its multiples (the number itself is not the rule — measured grids are 4, 5, and 8px across benchmarks), derive the rest instead of defining it, align labels/icons/controls on shared axes, and treat whitespace as a grouping and importance signal rather than decoration","tags":["spacing","layout","alignment","tidiness"],"source_path":"knowledge/spacing-and-alignment.md","body":"# Spacing & Alignment — 정돈돼 보이게 만드는 규율\r\n\r\nDate: 2026-08-12\r\nMilestone: M48 (plan: `plans/2026-08-12-m48-visual-benchmark-absorption.md`)\r\n지위: 살아있는 정본. \"왜 이 화면은 정돈돼 보이고 저 화면은 어수선해 보이는가\"를 여백·정렬 축에서 판정한다. 이것은 **기반층**(판정 축)이지 시그니처가 아니다 — 특색은 [[identity-layers]] 의 시그니처층 소관이고 이 문서는 값을 정하지 않는다.\r\n근거: `research/2026-08-12-visual-benchmark-references.md` (Linear·Stripe·Apple 1차 + Notion·Figma·Attio·Vercel·Framer 보강, 출처 URL+접근일 전건 · 실브라우저 계측 포함).\r\n소비처: M44 자산 전수 점검의 판정 기준 — **미실증**(M48 시점에 M44 가 아직 실행되지 않았다).\r\n\r\n인접 문서 경계: 밀집 화면의 소음 판정은 [[dashboard-density]], 랜딩 섹션의 배경 밴드 리듬은 [[landing-section-rhythm]], 슬라이드 지면 격자는 [[deck-layout-vocabulary]] 가 소유한다. 이 문서는 매체를 가리지 않는 **여백값의 결정 방식과 정렬**만 다룬다.\r\n\r\n## 0. 대원칙 — 정돈은 값이 아니라 구조에서 온다\r\n\r\n벤치마크 5종의 여백 격자를 실측하면 **8px 이 보편이 아니다**: Stripe 8 · Notion 8 · Figma 8 · **Attio 4** · **Framer 5**. 그런데 다섯 다 정돈돼 보인다. 공유되는 것은 숫자가 아니라 **\"기본 단위를 하나 정하고 그 배수만 쓴다\"** 는 규율이다.\r\n\r\nStripe 는 이것을 제3자에게 노출하는 계약으로까지 만들었다: \"The base spacing unit that all other spacing is derived from. Increase or decrease this value to make your layout more or less spacious.\" (Stripe Elements Appearance API, 접근일 2026-08-12)\r\n\r\n> **왜 정돈돼 보이는가**: 값이 배수로 갇히면 눈이 값의 종류를 세지 않아도 되고, 인접한 두 간격의 관계가 자동으로 정수비가 된다. 값이 자유로우면 뇌가 매번 \"이건 저것과 같은가 다른가\"를 판정해야 한다.\r\n\r\n## 1. 결정표 — 여백\r\n\r\n| 상황 | 규칙 | 왜 |\r\n|---|---|---|\r\n| 프로젝트 시작 | **기본 단위 1개를 먼저 정한다.** 4·5·8 어느 것도 정답이며, **정한 뒤 배수만 쓰는 것**이 규칙이다 | 값의 종류가 줄면 어긋남이 눈에 안 띈다 |\r\n| 새 간격이 필요 | 배수 안에 없으면 **값을 만들지 말고 가장 가까운 배수를 쓴다.** 배수로 표현이 안 되면 그건 간격 문제가 아니라 구조 문제다 | 예외값 하나가 다음 예외값을 정당화한다 |\r\n| 파생 가능한 값 | **정의하지 말고 파생시킨다** — 컴포넌트 패딩·gap 은 기본 단위의 함수로 쓴다 | 정의가 늘면 테마·표면이 늘 때마다 곱해진다 (Stripe `spacingUnit`·Linear 테마 변수 98→3, [[dark-mode]] §3 참조) |\r\n| 마케팅 ↔ 제품 표면 | **격자는 공유하고 진폭만 바꾼다.** 같은 단위의 큰 배수(마케팅)와 작은 배수(제품) | Stripe 실측: 마케팅 16/24/32/64/96, 문서 4/8/16 — 같은 8의 배수 골격 |\r\n| 중요한 정보 | **공간을 주는 것이 곧 중요도 선언이다** — 중요한 것 주위를 비운다 | \"Make essential information easy to find by giving it sufficient space.\" (Apple HIG Layout) |\r\n| 그룹 표현 | 관련 항목은 **여백으로 먼저 묶는다** — 선·배경은 그다음 | \"you might use negative space, background shapes, colors, materials, or separator lines\" — Apple 의 열거 순서 자체가 규율이다 (HIG Layout). [[dashboard-density]] §2-1 과 같은 순서 |\r\n\r\n## 2. 결정표 — 정렬\r\n\r\n정렬은 이 문서에서 가장 강한 근거를 가진 항목이다. 서로 다른 두 조직이 서로 다른 문맥에서 **\"정돈돼 보인다\"의 실체로 정렬을 지목**했다.\r\n\r\n> \"Align components with one another to make them easier to scan and to communicate organization and hierarchy. **Alignment makes an app look neat and organized** and can help people track content while scrolling or moving their eyes.\" (Apple HIG Layout, 접근일 2026-08-12)\r\n\r\n> \"I also spent time aligning labels, icons, and buttons, both vertically and horizontally in the sidebar and tabs.\" (Linear, How we redesigned the Linear UI, 접근일 2026-08-12 — 좁은 표면의 밀도를 관리한 실작업이 여백값 조정이 아니라 정렬이었다)\r\n\r\n| 상황 | 규칙 | 왜 |\r\n|---|---|---|\r\n| 요소가 많은 좁은 표면(사이드바·툴바·탭) | **정렬을 먼저 손본다.** 여백을 늘리기 전에 라벨·아이콘·컨트롤이 공통 축에 걸리는지 본다 | 좁은 표면에서는 여백을 늘릴 자리가 없다 — 정돈은 정렬로만 얻는다 |\r\n| 축의 개수 | 화면당 **수직·수평 정렬 축을 세지 못할 만큼 늘리지 않는다.** 셀 수 없으면 축이 없는 것이다 | 축이 많으면 정렬이 아니라 우연이 된다 |\r\n| 아이콘과 텍스트 | 광학 중심으로 맞춘다 — 바운딩 박스가 아니라 **보이는 무게**를 기준으로 | 기하학적 중앙 정렬이 눈에는 어긋나 보이는 경우가 흔하다 |\r\n| 들여쓰기 | 위계를 나타낼 때만 쓰고, 위계 단계와 들여쓰기 단계를 1:1 로 묶는다 | \"Along with indentation, alignment can also help people understand an information hierarchy.\" (Apple HIG Layout) |\r\n| 값이 표로 늘어설 때 | 숫자는 오른쪽·소수점 정렬, 텍스트는 왼쪽 | 스캔 시 눈이 한 축만 따라가면 된다 |\r\n\r\n## 2-1. 하한만 지키면 정돈이 아니라 밋밋함이 된다\r\n\r\n§1·§2 는 전부 **줄이는 규칙**이다 — 값의 종류를 줄이고 축을 맞춘다. 이것만 지키면 어긋남은 사라지지만 위계도 함께 사라진다.\r\n\r\n실측 근거: BK3 에서 취향 기각된 recipe 7종을 재보니 간격 값 종류가 1~5개, 반경 1종, 글자 크기 2~3단이었다 — **줄이는 규칙을 전건 통과한 상태에서 기각됐다**(`research/2026-08-12-visual-benchmark-references.md` §9-2). 규칙이 기각을 하나도 설명하지 못했다.\r\n\r\n| 확인 | 질문 |\r\n|---|---|\r\n| 간격 진폭 | 가장 촘촘한 간격과 가장 넓은 간격의 비가 화면의 위계 단계 수만큼 벌어져 있나 — 전부 같은 배수면 그룹이 없는 것이다 |\r\n| 여백의 역할 | 여백이 **무엇이 중요한지 말하고 있나.** 균일한 여백은 \"다 똑같이 중요하다\"는 선언이고, 그것은 대개 거짓이다 |\r\n| 진폭의 값 | 얼마나 벌릴지는 **취향이라 기반층이 정하지 않는다** — 이 문서는 \"벌려야 한다\"까지만 말한다([[identity-layers]] 시그니처층) |\r\n\r\n## 2-2. 그리드 공식은 컴포넌트 폭까지 지배한다 (M67 2026-08-18)\r\n\r\n격자를 레이아웃에만 쓰면 그 밖의 표면(모달·팝오버·시트)은 결국 임의 폭을 갖게 된다. 그러면 정렬 축이 화면 한가운데서 끊긴다.\r\n\r\nNORMAL IS BORING 실측(2026-08-17, `research/m67-batch2-css/p2-ledger.json` 행 `nib-05`·`nib-10`):\r\n\r\n```\r\n.col-N { width: calc((99.9vw - var(--wrapper-padd)*2 - var(--grid-gap)*11) / 12 * N + var(--grid-gap)*(N-1)) }\r\n.modal--contact .modal__content { width: calc(... / 12 * 5 + var(--grid-gap)*4 + var(--grid-gap)*2.5) }\r\n```\r\n\r\n- **컬럼 수는 브레이크포인트에서 바뀌지 않는다.** 항상 12분할이고, 스텝하는 것은 여백·거터 토큰뿐(`--grid-gap 1.5rem → 0.75rem`, `--wrapper-padd 1.65rem → 0.75rem`). 「반응형 = 컬럼을 접는 것」이 유일한 답은 아니다.\r\n- **모달 폭이 같은 공식의 5칸이다.** 임의 `max-width` 가 아니라 격자에 물려 있어, 어떤 화면 폭에서도 본문과 모달의 좌우가 같은 축을 공유한다.\r\n- **radius 도 같은 토큰(`--border-radius`)을 공유해 함께 스텝한다.** 화면이 좁아지면 격자와 모서리가 같이 작아진다.\r\n\r\n**규칙: 격자 공식을 하나 정했으면 컴포넌트 폭도 그 공식의 항으로 쓴다.** 적용처 — [[responsive-content-grid]]·[[popover-anatomy-variants]]·[[mobile-buy-bar-sheet]] 가 현재 폭을 각자 정하고 있다.\r\n\r\n## 3. 하지 않는 것\r\n\r\n- **특정 제품의 여백 값을 이식하지 않는다.** 이 문서는 \"8px 을 써라\"고 말하지 않는다 — 단위를 하나 정하라고 말한다. look 은 프로젝트 토큰 소유(component-restyle 대원칙).\r\n- **밀도 자체를 규칙으로 삼지 않는다.** \"조밀하되 구조적으로\"는 표면 종류의 함수라 보편 규칙이 못 된다 — Apple 은 부차 정보를 다른 곳으로 옮기라 하고([[dashboard-density]] 가 인용한) Linear 는 정보를 지키고 소음을 줄이라 한다. 두 처방이 반대다. 이 문서가 채택한 보편항은 밀도가 아니라 **정렬**이다.\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. 화면을 만들기 전에 **기본 단위를 선언한다.** 프로젝트 토큰에 이미 있으면 그것을 쓰고, 없으면 하나 정한 뒤 문서에 적는다.\r\n2. 완성된 화면에서 **쓰인 간격값의 종류를 센다.** 기본 단위의 배수가 아닌 값이 있으면 근거를 대거나 배수로 되돌린다.\r\n3. **간격 진폭을 잰다** — §2-1. 모든 간격이 같은 배수면 그룹 위계가 없는 것이다.\r\n4. **정렬 축을 센다.** 수직·수평 각각 몇 개인지 말할 수 없으면 정렬이 없는 것이다 — §2 첫 행부터 다시 본다.\r\n5. \"어수선하다\"는 진단을 받으면 **여백을 늘리기 전에 정렬을 먼저 본다.** 좁은 표면에서는 이것이 유일한 수단이다.\r\n6. 마케팅·제품 표면을 함께 만들 때 **격자를 갈라 놓지 않는다** — 진폭만 다르게 한다.\r\n\r\n## Changelog\r\n\r\n- 2026-08-18: M67 배치 2 회차 — §2-2 신설(그리드 공식이 컴포넌트 폭까지 지배한다). 흡수 판정 A, 값이 아니라 공식의 적용 범위를 옮긴다.\r\n- 2026-08-12: M48 step-9 — §2-1 신설(하한만 지키면 밋밋해진다 — BK3 7종 소급 반증에서 도출), 판정 절차 3 추가·번호 재배치.\r\n- 2026-08-12: 초판 (M48) — Linear·Stripe·Apple 1차 근거 + Notion·Figma·Attio·Framer 보강 실측. 「8px 격자」 가설을 「단일 기본 단위」로 수정하고 정렬을 정돈의 실체로 채택."},{"id":"type-hierarchy","title":"Type Hierarchy","description":"How to build a readable type hierarchy without making the interface look generated: keep one typeface family by default (mixing families obscures hierarchy), pick ONE channel to carry the hierarchy — size amplitude, weight, or step count — rather than pushing all three at once, cap the number of sizes on a screen, and treat a second typeface as a signature-layer decision, not a tidiness one","tags":["typography","hierarchy","tidiness"],"source_path":"knowledge/type-hierarchy.md","body":"# Type Hierarchy — 글자 위계 판정 규칙\n\nDate: 2026-08-12\nMilestone: M48 (plan: `plans/2026-08-12-m48-visual-benchmark-absorption.md`)\n지위: 살아있는 정본. \"이 화면의 글자 위계가 제대로 서 있는가\"를 판정한다. **기반층**(판정 축)이며 특정 서체·크기 값을 정하지 않는다 — 서체 선택 자체는 [[identity-layers]] 시그니처층 소관이다.\n근거: `research/2026-08-12-visual-benchmark-references.md` §5-1~§7 (Apple HIG Typography 1차 + Linear·Stripe·Figma·Notion 실측, 출처 URL+접근일 전건).\n소비처: M44 자산 전수 점검의 판정 기준 — **미실증**.\n\n인접 문서 경계: 커스터마이저가 타이포를 **어느 축까지 노출하는가**는 [[customizer-and-packaging]] §1 이 소유한다(그 문서의 \"서체 한 가족\" 규칙이 이 문서의 §1 과 같은 결론이며, 여기서는 근거와 예외를 붙인다). 모션 중 글자 가독성은 [[motion-principles]], 밀집 화면의 2단 텍스트 위계는 [[dashboard-density]] §3-2 소관이다.\n\n## 1. 서체 가족 — 기본값은 하나\n\n> \"Minimize the number of typefaces you use, even in a highly customized interface. **Mixing too many different typefaces can obscure your information hierarchy and hinder readability**, in addition to making an interface feel internally inconsistent or poorly designed.\" (Apple HIG Typography, 접근일 2026-08-12)\n\n이 문장이 주는 것은 금지가 아니라 **인과**다. 서체를 섞으면 위계가 흐려지는 이유는 서체마다 같은 크기에서 보이는 무게가 다르기 때문이다 — 크기로 세운 순서를 서체 차이가 무너뜨린다.\n\n**예외는 있고, 그것은 정돈이 아니라 표현의 선택이다.** Linear 는 헤딩에만 표현형 서체를 들였다(\"to add more expression to our headings while maintaining their readability\", 접근일 2026-08-12). 판정 기준이 \"가독성을 유지하면서\"였다는 점이 중요하다 — 표현을 위해 위계를 깎지는 않았다.\n\n| 상황 | 규칙 |\n|---|---|\n| 기본 | **한 가족.** 위계는 크기·굵기로 만든다 |\n| 두 번째 가족을 들이고 싶을 때 | ① 역할을 **하나로** 한정한다(예: 헤딩 전용) ② 본문에는 손대지 않는다 ③ 이것은 **시그니처층 결정**이므로 기반층 규칙으로 정당화하지 않는다 |\n| 공용 플랫폼 안에 사는 앱 | 시스템 서체를 쓴다 — 주변 앱과의 일관성이 비용이다 (Apple HIG) |\n\n## 2. 위계는 채널 하나로 만든다\n\n실측된 경로가 셋이고, 셋 다 성공한다. **중요한 것은 어느 채널을 골랐느냐가 아니라 하나를 골랐다는 것이다.**\n\n| 경로 | 방식 | 실측 |\n|---|---|---|\n| **크기 진폭** | 굵기를 거의 안 쓰고(가는 값 하나로 고정) 크기를 크게 벌린다 | Stripe 홈: 본문 굵기가 사실상 한 값, 크기 16→48(3배). Figma: 굵기 400 미만 값을 본문에 쓰고 크기 16→48 |\n| **굵기 단** | 크기 범위를 좁히고 굵기를 3~4단으로 쓴다 | Stripe 문서: 크기 14~24 로 좁고 굵기 4단. Attio: 한 굵기를 기본값으로 놓고 위아래로 조금 |\n| **단계 축소 + 여백** | 크기 종류를 2단 수준으로 줄이고 나머지를 여백이 가른다 | Notion: 사실상 2단. Apple HIG 도 이쪽 |\n\n**⚠ 고른 채널은 실제로 벌려야 한다 — 종류를 줄이는 것만으로는 위계가 안 생긴다.** 이 조항은 소급 반증에서 나왔다. BK3 에서 취향 기각된 recipe 7종을 재보니 **전건이 크기 2단·굵기 1종**이었다(`research/2026-08-12-visual-benchmark-references.md` §9-2). 종류를 줄이라는 규칙은 완벽히 지켰는데 채널을 고르지 않아 진폭이 0이었고, 결과는 정돈이 아니라 밋밋함이었다. 위 세 경로의 실측 진폭을 하한 감각으로 삼는다 — 크기 진폭 경로는 본문에서 최상위 헤딩까지 **3배 안팎**, 굵기 단 경로는 **3~4단**, 단계 축소 경로는 단계를 줄이는 대신 **여백이 그 몫을 대신하는지**를 확인한다.\n\n**세 채널을 동시에 밀면 위계가 아니라 소란이 된다.** 크기도 벌리고 굵기도 4단이고 색도 3단이면, 각 채널이 서로를 상쇄해 어느 것이 primary 인지 읽히지 않는다.\n\n## 3. 결정표\n\n| 판정 | 규칙 | 왜 |\n|---|---|---|\n| 한 화면의 크기 종류 | **셀 수 있어야 한다.** 못 세면 스케일이 없는 것이다 | 값이 자유로우면 눈이 매번 \"이건 저것과 같은 급인가\"를 판정한다 |\n| 세 번째 회색이 필요할 때 | 위계 설계 실패 신호 — 구조를 다시 본다 ([[dashboard-density]] §3-2) | 색으로 못 만든 위계를 색을 더해 만들 수는 없다 |\n| 가는 굵기를 쓸 때 | **권장보다 크게 잡는다** — \"If you use a custom font with a thin weight, aim for larger than the recommended sizes to increase legibility.\" (Apple HIG Typography) | 굵기를 내리면 가독성 예산이 줄고, 그 예산은 크기로만 되산다 |\n| 최소 크기 | 플랫폼 최소값을 하한으로 삼는다. Apple 공표값: iOS·iPadOS 17pt 기본 / 11pt 최소, macOS 13pt / 10pt, visionOS 17pt / 12pt (HIG Typography) | 값 이식이 아니라 **하한 참조**다 — 웹은 해당 플랫폼 규범을 따로 확인한다 |\n| 크기 파생 | 단계값을 손으로 나열하지 말고 기준 크기에서 파생시킨다 | Stripe 계약: \"other font size variables such as `fontSizeXs` or `fontSizeSm` are scaled from this value using `rem` units\" (Appearance API) |\n| 크기 변경 대응 | 사용자가 글자를 키우면 **전부 키우지 않는다** — 중요한 것만 | \"Prioritize important content when responding to text-size changes.\" (Apple HIG Typography) |\n\n## 4. 하지 않는 것\n\n- **특정 제품의 서체 이름을 규칙에 적지 않는다.** 이 문서는 \"헤딩용 표현형 서체를 하나 둘 수 있다\"고 말하고 그것이 무엇인지는 프로젝트가 정한다.\n- **\"강한 타이포 위계\"를 단일 규칙으로 쓰지 않는다.** 실측 경로가 셋이고 서로 반대 방향(굵기를 내리는 쪽 / 굵기 단을 늘리는 쪽)이라 하나로 뭉치면 틀린다.\n\n## 판정 절차 (에이전트 의무)\n\n1. 화면을 만들기 전에 **어느 채널로 위계를 만들지 하나 선언한다**(크기 진폭 / 굵기 단 / 단계 축소).\n2. 완성 후 **크기 종류와 굵기 종류를 센다.** 선언한 채널 밖에서 종류가 늘었으면 되돌린다.\n2-1. **선언한 채널의 진폭을 잰다.** 크기 2단·굵기 1종으로 끝났으면 채널을 고르지 않은 것이다 — §2 의 경고 조항으로 되돌아간다.\n3. 서체 가족이 둘 이상이면 §1 표의 3조건을 통과하는지 확인하고, **시그니처층 결정임을 보고에 적는다.**\n4. 가는 굵기를 썼으면 크기를 올렸는지 확인한다.\n5. 크기 단계가 손으로 나열돼 있으면 기준값 파생으로 바꾼다.\n\n## Changelog\n\n- 2026-08-12: M48 step-9 — §2 에 진폭 조항 추가(BK3 7종 소급 반증에서 도출: 종류 축소만으로는 위계가 안 생긴다), 판정 절차 2-1 추가.\n- 2026-08-12: 초판 (M48) — Apple HIG Typography 인과 + Linear·Stripe·Figma·Notion 실측 3경로. `customizer-and-packaging.md` §1 의 \"서체 한 가족\" 결론에 근거와 예외를 붙였다."}]
1
+ [{"id":"adjacency-level-lod","title":"Adjacency-Level LOD","description":"Choosing resolution tiers by grid topology distance (Chebyshev ring from the focused cell) instead of screen distance in pixels, so the sharp region keeps the same cell-count shape at any zoom, cell size, or viewport; includes the neighbor-count identity, tier atlas ratios, and the speed-gated promotion rule","tags":["lod","grid","navigation","performance","canvas"],"source_path":"knowledge/adjacency-level-lod.md","body":"# Adjacency-Level LOD — 해상도 등급을 화면 거리가 아니라 격자 위상 거리로 정한다\r\n\r\nDate: 2026-08-17\r\nMilestone: M60 (plan: `plans/2026-08-17-m60-round6-prep-and-maintenance.md`, D3)\r\n지위: 살아있는 정본. 관측 근거는 `research/m59-round5/p2-nothing-to-watch.json` 행 `r5-06`(동결 기록) — 이 문서는 판정 규칙만 담고 수치의 출처 서술을 중복하지 않는다.\r\n출처: There's nothing to watch (`https://nothing-to-watch.port80.ch/`) — 자체 WebGL2 엔진 voroforce. 회차 5 소진형 심층에서 0.5층 정적 직독으로 파라미터 전 구간 확보.\r\n\r\nAudience: 격자·타일·목록을 다루는 탐색 UI를 만드는 에이전트. \"어느 셀을 몇 배 해상도로 그릴 것인가\"를 판정하는 데 쓴다.\r\n\r\n## 왜 데모가 아니라 지식으로 착지했나\r\n\r\n이 기법은 **값어치가 화면에 나타나지 않는다.** 재현 데모를 만들어 사용자 관측에 올렸으나 \"아름답지 않다 + 흡수할 의미를 찾아야 하나\"로 보류됐다(M59 회차 5). 절차 생성 타일로는 등급이 갈리는 것만 보이고 **왜 이 방식이 옳은지**가 보이지 않기 때문이다. 규칙 자체의 이식 가치는 높으므로 계약 §1 「시각이 아니라 규칙이 값어치인 기법」 경로를 따라 지식으로 착지시켰다. 구현(`code_asset`)은 없다 — recipe 를 신설하지 않은 이유다.\r\n\r\n## 규칙\r\n\r\n**초점 셀에서 격자상 몇 칸 떨어졌는가로 등급을 정한다. 화면상 몇 픽셀 떨어졌는가로 정하지 않는다.**\r\n\r\n열 방향 거리와 행 방향 거리를 각각 임계와 비교하고, 둘 다 임계 안이면 그 등급이다. 이것은 유클리드 원이 아니라 **체비쇼프 사각 링**이다.\r\n\r\n| 등급 | 판정 | 관측 임계 (원본) |\r\n|---|---|---|\r\n| v3 (최고 해상도) | 열 거리 ≤ 임계 **그리고** 행 거리 ≤ 임계 | 열 1 · 행 1 — \"초점 바로 옆\" |\r\n| v2 (중간) | v3 밖이면서 같은 방식으로 임계 안 | 열 18 · 행 18 |\r\n| v1 (기본) | 그 밖 전부 | — |\r\n\r\n**이웃 수 상한이 판정 방식의 방증이다**: 관측된 `MAX_NEIGHBORS_LEVEL_1 8` · `LEVEL_2 24` · `LEVEL_3 48` 은 각각 (2k+1)²−1 (k=1,2,3)과 정확히 일치한다. 체비쇼프 반경 k의 사각 블록에서 자기 자신을 뺀 수다. 유클리드 거리였다면 이 수가 나오지 않는다.\r\n\r\n## 무엇을 얻는가\r\n\r\n- **선명 영역의 모양이 불변이다.** 셀이 크든 작든, 뷰포트가 넓든 좁든, 줌이 어떻든 선명한 곳은 항상 같은 **칸 수**의 사각 블록이고 커서를 옮기면 그 블록이 통째로 따라간다. 픽셀 기준이면 줌·리사이즈마다 선명 영역의 셀 개수가 변해 사용자가 \"몇 개까지 또렷한가\"를 학습할 수 없다.\r\n- **로딩 예산이 해상도와 무관하게 고정된다.** 등급별 셀 수가 격자 위상만으로 결정되므로 고해상도 자산의 동시 상주 개수 상한이 화면 크기와 분리된다.\r\n\r\n## 등급별 자산 배율 (관측)\r\n\r\n원본은 3단 아틀라스를 미리 굽는다 — low `512×104` 타일(타일당 4×6px) · mid `90×60`(22×33px) · high `18×12`(110×165px). **타일 픽셀비가 대략 4 : 22 : 110**이고, 등급 간 한 단계가 5배 안팎이다. 이식할 때 이 절대 수치를 옮길 필요는 없다 — 옮길 것은 **등급이 3단이고 단계 간 배율이 5배 안팎**이라는 구조다. 아틀라스 생성 파이프라인 자체는 빌드 시점 자산이라 이식 대상이 아니다(장부 판정 \"축소이식\").\r\n\r\n## 승격 보류 — 빠르게 훑는 동안은 등급을 올리지 않는다\r\n\r\n관측: `handleMediaSpeedLimits` 가 preview 모드에서는 켜지고 select 모드에서는 꺼진다. **포인터가 빠르게 움직이는 동안에는 고해상도 승격을 미루고, 멈춘 뒤에 올린다.** 훑는 중에는 어차피 읽히지 않으므로 승격 비용이 낭비되고, 되돌리는 강등까지 겹치면 진동한다.\r\n\r\n⚠ **속도 임계의 실제 수치는 미확인이다** — 존재와 동작 방향만 관측됐다. 이식 시 직접 정해야 한다.\r\n\r\n## 언제 쓰지 않나\r\n\r\n- 격자가 작아 전부 최고 해상도로 그려도 예산이 남으면 등급 자체가 불필요하다.\r\n- 항목이 격자에 놓이지 않는 배치(자유 배치·힘 기반 레이아웃 중 정렬되지 않은 상태)에서는 \"몇 칸\"이 정의되지 않는다. 이때는 픽셀 거리로 돌아가는 것이 맞다.\r\n- 사용자가 초점을 옮기지 않고 훑기만 하는 표면(스크롤 목록)에서는 초점 셀이라는 개념이 약해 이득이 작다.\r\n\r\n## 관련\r\n\r\n`knowledge/dashboard-density.md`(밀도 판정) · `knowledge/expressive-stack.md`(렌더 티어) · 계약 `docs/design-system/reverse-engineering-contract.md` §1(규칙형 기법의 착지 판정 — 이 문서가 그 첫 사례다).\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 — M59 회차 5 보류분 `adjacency-level-lod` 의 지식 착지(D3 사용자 확정). 데모는 제거하고 규칙만 남긴다."},{"id":"checkout-flow","title":"Checkout Flow","description":"Checkout & conversion-flow rules (Stripe): friction is re-entry not step count — wallet one-click path above the form, never block autofill, address autocomplete as validation; inline validate-while-typing with descriptive localized errors and preemptive typo highlights; trust framing inside the surface (policy links, statement descriptor notice); payment methods/currency/language chosen dynamically by context; upsell restraint (one inline add-on, no forked pages)","tags":["commerce","checkout","forms","stripe"],"source_path":"knowledge/checkout-flow.md","body":"# Checkout Flow — 결제·전환 플로우 판정 규칙\n\nDate: 2026-08-01\nMilestone: M9 (plan: `plans/2026-08-01-m9-stripe-checkout-absorption.md`)\n지위: 결제·체크아웃처럼 전환이 걸린 플로우 표면의 판정 정본. 근거 원문은 `research/2026-08-01-m9-stripe-checkout-capture.md` 에 동결(Stripe Elements·Checkout, 접근 2026-08-01). 확인 모달·다이얼로그 규칙은 [[mobile-navigation]] §3 소관(액션 ≤2·구체 동사) — 이 문서는 플로우 고유 규칙만 담는다. 폼 일반 밀도·위계는 [[dashboard-density]] 소관.\n\nAudience: 에이전트(체크아웃·결제·가입 등 전환 플로우 생성 시 필수 참조) + 사람.\n\n## 0. 대원칙 — 마찰의 본체는 재입력이다\n\n단계 수를 줄이는 것보다 **입력을 없애는 것**이 우선이다. 같은 정보를 두 번 치게 하는 순간이 이탈 지점이고, 단일/다단 레이아웃 선택은 그 다음 문제(기기·태스크 무게로 결정)다.\n\n## 1. 입력 마찰 제거 (우선순위 순)\n\n1. **건너뛰기 경로 먼저** — 원클릭 지불(월렛·저장 결제) 버튼을 수동 폼 **위에** 병렬로 배치하고 \"다른 방법으로 결제\" 구분선으로 폼과 나눈다. 폼은 폴백이다.\n2. **저장·재사용** — 반환 사용자는 지불·배송 정보 자동채움 + 블록별 \"변경\" 진입점. 새 기기 검증은 일회용 코드로.\n3. **자동채움을 막지 않는다** — 브라우저·서드파티·인증 서비스 자동채움 호환은 설계 요건이다(autocomplete 속성·표준 필드 시맨틱).\n4. **주소는 자동완성 + 확정 블록** — 타이핑 절감이자 오배송·세금 오적용 방지 검증.\n5. 모바일: 숫자 입력엔 동적 숫자 키패드, 카드 스캔 경로 제공.\n\n## 2. 검증·에러 계약\n\n1. **타이핑 중 인라인 검증** — 제출 시 일괄 에러 금지. 에러는 발생 필드 옆, 서술형(\"무엇을 어떻게\")·로컬라이즈.\n2. **예측 가능한 실수는 선제 하이라이트** — 이메일 도메인 오타, 카드 브랜드 불일치.\n3. **입력 보조 = 신뢰 신호** — 카드 첫 자리로 브랜드 아이콘 표시, 브랜드별 CVC 힌트, 마스킹. 사용자가 \"시스템이 내 카드를 안다\"고 느끼게.\n4. 결제 폼 접근성(ARIA·포커스)은 기본 탑재 전제 — 포커스·키보드 규율 상세는 focus-keyboard 정본(M10) 참조.\n\n## 3. 신뢰 프레이밍\n\n전환 플로우 표면 **안에** 신뢰 신호를 심는다:\n- 반품 정책·지원 연락처·약관 링크 (별도 페이지로 쫓아내지 않는다)\n- **명세서 표기명 안내** — \"결제 내역에 X 로 표기됩니다\" (분쟁·챠지백 예방)\n- 결제 후 확인 화면에도 같은 표기명 반복.\n\n## 4. 국제화 = 동적 선택\n\n결제수단·통화·언어는 고정 목록이 아니다 — 사용자 위치·선호로 **동적으로 고른다**(로컬 수단 노출이 전환을 올린다는 것이 실측 근거). 가격은 로컬 통화로 표시.\n\n## 5. 플로우 구조 규칙\n\n1. **통합 깊이는 명시적 스펙트럼으로** — hosted 전체 페이지 ↔ embedded 폼(단일/다단) ↔ 컴포저블 요소 ↔ 노코드 링크. 통제 수준과 구축 비용을 트레이드오프로 제시하고 하나를 고른다.\n2. **업셀 절제** — cross-sell 은 제안 1개 인라인, 수량 조정 인라인. 업셀이 체크아웃을 별도 페이지로 분기시키면 FAIL.\n3. 확인·취소 모달 규칙은 [[mobile-navigation]] §3 을 그대로 따른다.\n\n## 판정 절차 (에이전트 의무)\n\n1. 전환 플로우를 만들기 전에 §0 을 선언 — 이 플로우에서 사용자가 새로 입력해야 하는 필드 수를 세고, 하나씩 \"없앨 수 있나(§1)\"를 먼저 묻는다.\n2. 모든 입력 필드에 §2 검증 계약(인라인·서술형·선제 하이라이트) 적용 여부를 확인한다.\n3. 신뢰 신호(§3) 3종 중 최소 2종이 표면 안에 있는지 확인한다.\n4. 수치 주장(전환율 %)은 재인용하지 않는다 — 원리만 쓰고 우리 실측으로만 주장한다.\n\n## Changelog\n\n- 2026-08-01: 초판 (M9) — Stripe Elements·Checkout 실브라우저 캡처 기반."},{"id":"commerce-conventions","title":"Commerce Conventions","description":"What three large commerce services agree on and where they split: sort-order defaults, filter-rail density, whether the cart is a page or a drawer, the sub-44px stepper convention that should not be copied, and the full-bleed breakout trick; each rule tagged as convention signal or option signal","tags":["commerce","cart","filter","sort","density","accessibility"],"source_path":"knowledge/commerce-conventions.md","body":"# 커머스 관습 — 세 곳이 합의한 것과 갈린 것\r\n\r\nDate: 2026-08-17\r\nMilestone: M62 (plan: `plans/2026-08-17-m62-round7-product-commerce.md`, 처분표 클러스터 C8·C9·C10·C12)\r\n지위: 살아있는 정본. 관측 근거는 `research/m62-round7/p2-ledger.json`(63행 동결) + `research/m62-round7/disposition.json` — 이 문서는 **판정 규칙만** 담고 수치의 출처 서술을 중복하지 않는다.\r\n출처: 쿠팡(`https://www.coupang.com/`) · 이마트몰(`https://emart.ssg.com/`) · 아마존(`https://www.amazon.com/`) 대조 심층 + 오픈소스 스토어프론트 직독(`Shopify/dawn@258f00f643` MIT · `vercel/commerce@3761e52e60` MIT · `medusajs/nextjs-starter-medusa@9818886f06` MIT). 2026-08-17 관측.\r\n\r\n여기 담긴 것은 **데모로 만들 값이 없거나 기본기라서 자산이 되지 못한 판정 규칙**이다. 데모가 된 것은 recipe 로 갔다(`price-hierarchy-stack` · `cart-line-item-anatomy` · `add-to-cart-feedback-modes` · `popover-anatomy-variants` · `mobile-buy-bar-sheet` · `product-grid-density-presets` · `button-size-scale`).\r\n\r\n읽는 법: **관습 신호**는 세 곳이 수렴한 것이고 이식 가치가 높다. **선택지 신호**는 갈린 것이고 어느 쪽을 쓸지가 제품 판단이다. 수렴이 곧 올바름은 아니다 (아래 §3 이 그 반례다).\r\n\r\n## 1. 정렬 옵션의 순서 — 관습 신호\r\n\r\n기본값은 **추천·랭킹순**이고 그다음이 가격 오름 → 가격 내림 → 판매량 → 신상품이다. 쿠팡 5종, 이마트몰 7종(할인율순·리뷰많은순이 더 붙는다), 아마존도 같은 축을 네이티브 select 로 쓴다.\r\n\r\n- 가격순을 기본값으로 두지 않는다. 목록의 첫 화면이 최저가로 채워지면 그 화면이 카탈로그가 아니라 할인 코너로 읽힌다.\r\n- 오름차순이 내림차순보다 먼저 온다. 순서를 뒤집으면 사용자가 매번 두 번 읽는다.\r\n- 페이지당 개수 프리셋을 함께 두는 경우가 있다(이마트몰 40/60/80/100, 기본 80).\r\n\r\n## 2. 필터 레일의 밀도 — 관습 신호\r\n\r\n필터 영역은 본문보다 촘촘하다. 아마존 실측으로 레일 폭 291px, 체크박스 16×16px, 리스트 항목 높이 20px, 라벨 14px 이다. 이 밀도는 **선택지를 한 화면에 최대한 담는 것이 목적**이므로 본문의 여백 리듬을 그대로 가져오면 레일이 화면을 다 먹는다.\r\n\r\n- 필터를 팝오버로 감출 것인가는 갈린다. `vercel/commerce` 는 할인코드·필터를 **팝오버 대신 인라인 라디오 그룹**으로 펼쳐 둔다. 항목 수가 적으면 인라인이 낫다.\r\n\r\n## 3. 수량 스테퍼의 32px 은 베끼지 않는다 — 나쁜 수렴\r\n\r\n수량 조절 컨트롤이 아마존 32px, Dawn 3.2rem(32px), `vercel/commerce` h-9(36px)로 **세 곳 전부 WCAG 최소 터치 타깃 44px 미달**이다. 같은 `vercel/commerce` 의 닫기 버튼만 h-11 w-11(44px)로 정확히 충족한다.\r\n\r\n**규칙: 이 값은 관습으로 인용하되 이식할 때 44px 로 교정한다.** 수렴은 「많이들 그렇게 한다」는 사실이고 「그것이 옳다」는 근거가 아니다. 교정 사실을 자산에 드러내 두면 다음 사람이 다시 32px 로 되돌리지 않는다(`cart-line-item-anatomy` 가 그 방식을 쓴다).\r\n\r\n## 4. 장바구니를 드로어로 만들 것인가 — 선택지 신호, 그리고 규모의 함수\r\n\r\n- 대형 커머스 3곳은 장바구니를 **전체 페이지**로 준다. 쿠팡은 그 페이지에 01~04 진행 스텝퍼를 노출하고, 이마트몰은 `pay.ssg.com` 결제 서브도메인으로 아예 이동하며, 아마존은 `/gp/cart/view.html` 이다.\r\n- 드로어는 **오픈소스 스토어프론트의 관습**이다(Dawn 40rem 불투명 · `vercel/commerce` 390px 반투명).\r\n\r\n**규칙: 드로어를 고를 때 그 선택이 규모 판단임을 안다.** 담을 품목이 적고 구매 결정이 짧으면 드로어가 맥락을 지켜 준다. 품목이 늘고 배송·쿠폰·수량 조정이 붙기 시작하면 드로어의 폭이 곧 병목이 되고, 큰 서비스가 페이지로 간 이유가 그것이다.\r\n\r\n체크아웃 단계도 같은 축에서 갈린다. medusa 는 사이트 안에서 4단계 아코디언(Addresses → Shipping → Payment → Review, 스텝 간 32px)을 돌리고, Dawn·`vercel/commerce` 는 자체 스텝 0개로 호스티드 체크아웃에 위임한다. **자체 스텝을 만들 것인가는 결제 주체가 누구인가에 따라 정해진다.**\r\n\r\n## 5. 구현 팁 — 컨테이너 패딩을 상쇄하는 풀블리드 breakout\r\n\r\n모바일에서 섹션 하나만 화면 폭 전체로 빼려면 `width: 100vw` 에 **컨테이너 좌우 패딩만큼의 음수 마진**을 더한다(쿠팡 실측: `w-[100vw]` + `ml-[-16px]`, 컨테이너 패딩 16px). 부모의 패딩을 지우거나 레이아웃을 다시 짜지 않고 그 한 섹션만 빼내는 방법이다.\r\n\r\n주의: `100vw` 는 세로 스크롤바 폭을 포함하므로 데스크톱에서 가로 스크롤이 생길 수 있다. 이 트릭은 스크롤바가 폭을 차지하지 않는 모바일 구간에만 걸어라.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17 (M62 step-8): 신설. 회차 7 처분표의 지식 착지 3건(C8·C9·C10) + 구현 팁 1건(C12) + 나쁜 수렴 1건(C3 파생)."},{"id":"css-reset","title":"CSS Reset & Base Layer","description":"Reset and base-layer contract for non-Tailwind outputs: @layer isolation, mandatory restoration list after all:unset (focus ring first), lineage selection table, the modern sr-only pattern, percentage-based root font sizing — plus one rule that applies to every web surface including Tailwind: always reserve the scrollbar gutter (overflow-y:scroll AND scrollbar-gutter:stable, since the latter is inert while overflow is visible)","tags":["css","reset","base-layer","sr-only","accessibility"],"source_path":"knowledge/css-reset.md","body":"# CSS Reset & Base Layer — 리셋·베이스 레이어 계약\n\nDate: 2026-08-10\nMilestone: BK2 (plan: `plans/2026-08-10-bk2-batch-promotion.md` step-3)\n지위: 살아있는 정본. **Tailwind 가 없는 산출물**(단일 HTML 아티팩트·지면·이식 블록·손 CSS 데모)에서 리셋·베이스 레이어를 어떻게 까는가의 계약. Tailwind 표면에서는 Preflight 와 `sr-only` 유틸이 이 계약을 대신 충족한다 — 이 문서는 그 바깥의 사각지대용.\n근거: 『인터랙티브 웹 애니메이션』 3·6장 실측(`github.com/Youngjin-com/interactive_web@a2dfc627`, 접근 2026-08-08) + The New CSS Reset(`github.com/elad2412/the-new-css-reset`, MIT — gh api 확인 2026-08-08) + Scott O'Hara \"The anatomy of visually-hidden\"(`https://www.scottohara.me/blog/2023/03/21/visually-hidden-hack.html`, 접근 2026-08-10).\n\nAudience: 에이전트(Tailwind 밖 화면·지면 산출물 생성 시 필수 참조) + 사람.\n\n## 1. 리셋은 `@layer` 로 격리한다\n\n```css\n@layer base, components; /* 순서 선언이 먼저 */\n@layer base { /* 리셋 + 베이스 */ }\n```\n\n`@layer` 는 특정성보다 **한 층 위**에서 작동한다 — 레이어에 든 규칙은 특정성이 아무리 높아도 뒤 레이어·레이어 밖 규칙에 진다. 리셋을 `base` 레이어에 넣으면 **내 스타일이 리셋을 항상 이긴다**는 보장이 특정성 경쟁 없이 성립한다. (Tailwind Preflight 가 같은 구조다.)\n\n## 2. 리셋이 지우는 것 — 복구 목록은 의무다\n\n`all: unset` 계열(The New CSS Reset 등)은 User-Agent 스타일을 통째로 지운다. **같이 지워지는 것들은 되돌려 놓아야 한다:**\n\n| 지워지는 것 | 복구 의무 |\n|---|---|\n| **포커스 링**(`outline`) | **필수** — `:focus-visible { outline: ... }`. 배경색 변경만으로는 포커스 표시가 아니다([[focus-keyboard]] 시각 최소선). 반례 실측: 리셋 후 `:focus { background-color }` 만 복구(3장) |\n| `cursor: pointer` | 리셋이 revert 해 주는지 확인(The New CSS Reset 은 a·button 에 revert 포함) |\n| 리스트 마커·테이블 간격 | 콘텐츠 문서(지면·아티클)면 복구, 앱 UI 면 생략 가능 |\n| 폼 컨트롤 외형 | 커스텀하지 않을 컨트롤은 `revert` |\n\n**판정 질문: \"이 리셋을 깐 페이지에서 Tab 을 눌렀을 때 지금 어디인지 보이는가?\"** — 안 보이면 미완성이다.\n\n## 3. 계보 선택\n\n| 계보 | 성격 | 쓰는 경우 |\n|---|---|---|\n| Eric Meyer Reset | 요소별 0 초기화(2007 계보) | 신규 채택 안 함 — 역사 참조 |\n| **The New CSS Reset** (MIT) | `all: unset` + `display: revert` 전면 | 손 CSS 산출물의 기본 후보 — 단 §2 복구 목록 필수 |\n| Tailwind Preflight | modern-normalize 기반 + `@layer` 격리 | Tailwind 표면이면 자동 — 이 문서 비적용 |\n| 최소 리셋(box-sizing + margin 0) | `*{box-sizing:border-box}` 수준 | 지면·단일 아티팩트처럼 수명 짧은 산출물 — 전면 리셋이 과할 때 |\n\n기준: **산출물 수명과 커스텀 범위.** 오래 살고 컴포넌트가 많으면 전면 리셋+복구, 한 장짜리면 최소 리셋.\n\n## 4. `sr-only` — 현대 판본, 한 곳에서만\n\n```css\n.sr-only:not(:focus):not(:active) {\n clip-path: inset(50%);\n height: 1px; width: 1px;\n overflow: hidden;\n position: absolute;\n white-space: nowrap;\n}\n```\n\n- `clip: rect(0,0,0,0)` 은 폐기 속성 — `clip-path: inset(50%)` 로. **`white-space: nowrap` 이 필수다** — 없으면 1px 폭에서 단어마다 줄바꿈되어 일부 스크린리더 낭독이 끊긴다. `:not(:focus):not(:active)` 가드는 skip-link 처럼 포커스 시 나타나야 하는 요소를 겸용할 때를 위한 것. (Scott O'Hara, 접근 2026-08-10)\n- **정적 콘텐츠 전용이다** — 인터랙티브 컨트롤을 이걸로 숨기지 않는다(1px 터치 타깃이 되어 터치 탐색 사용자가 못 찾는다. 같은 출처).\n- **파일마다 손 복제 금지** — 공용 베이스 한 곳에. 반례 실측: 같은 블록이 파일 3곳에 복제(3장), head 손 복제와 같은 병([[head-meta 반례]] — `docs/design-system/head-meta.md`).\n\n## 5. 루트 폰트 크기 — px 고정은 사용자 설정을 덮는다\n\n```css\n/* 금지 */ html { font-size: 16px; }\n/* 계약 */ html { font-size: 100%; } /* + 치수는 rem */\n```\n\n브라우저 기본 글꼴을 키워 둔 사용자(저시력·고령)의 설정은 `font-size: 16px` 로 **덮인다.** `100%` 기준 + `rem` 치수면 사용자 설정이 배수로 존중되면서 같은 스케일링이 된다. 반응형 축소도 px 재지정(`14px`·`12px`)이 아니라 **% 로**(`87.5%`) — px 로 내리면 확대 사용자에게 원래 설정의 절반이 될 수 있다. 반례 실측: 16→14→12px 3단 고정(6장).\n\n## 6. 스크롤바 거터 — 이 문서에서 유일하게 Tailwind 표면에도 걸린다\n\n> ⚠ **범위 예외.** 이 문서의 나머지는 Tailwind 가 없는 산출물용이고 Tailwind 표면은 Preflight 가 대신 충족한다. **이 절은 아니다** — Preflight 는 스크롤바 거터를 다루지 않으므로 Tailwind 프로젝트도 직접 걸어야 한다.\n\n**웹 산출물의 기본값으로 항상 넣는다** (사용자 확정 2026-08-12 — \"매번 반복되는 문제라 애초에 만들 때 고려하도록\").\n\n```css\nhtml {\n overflow-y: scroll;\n scrollbar-gutter: stable;\n}\n```\n\n**두 줄이 함께여야 한다.** `scrollbar-gutter: stable` 만 쓰면 아무 일도 일어나지 않는다 — 그 속성은 overflow 가 `visible` 일 때 효과가 없고, `visible` 이 루트 요소의 기본값이다. 선언은 있는데 거터는 안 잡히는 상태가 되고, 그게 제일 찾기 어렵다.\n\n**무엇을 막는가.** 세로 스크롤바는 콘텐츠 높이가 뷰포트를 넘을 때만 생긴다. 행이 펼쳐지거나, 필터가 목록을 줄이거나, 모달이 스크롤을 잠그면 그 경계를 넘나들고, 그때마다 **화면 전체가 스크롤바 너비만큼 옆으로 밀린다**(고전 스크롤바 기준 15~17px).\n\n**왜 이 정도로 규칙화하는가.** 밀림은 원인이 화면 밖에 있어서 **자산 결함으로 오진된다.** 실측 근거: M44 자산 관측(2026-08-12)에서 사용자가 서로 다른 자산 3종에 \"누를 때마다 사이트가 좌우로 흔들린다\"를 적었고, 셋 다 자산은 멀쩡했다. 같은 창에서 페이지 길이만 바꿔 재니 `documentElement.clientWidth` 가 2545 ↔ 2560 으로 뒤집혔다(장부 `research/2026-08-12-m44-inventory-verdicts.md` §1-1).\n\n**예외.** 세로 스크롤이 구조적으로 불가능한 전체화면 앱 셸(자체 스크롤 컨테이너만 쓰는 캔버스·키오스크)에서는 빈 거터가 낭비다. 그때는 스크롤을 소유한 컨테이너 쪽에 같은 두 줄을 건다 — 규칙은 \"루트에 걸어라\"가 아니라 **\"스크롤 주체에 거터를 예약해라\"** 다.\n\n## Changelog\n\n- 2026-08-12: M44 — §6 스크롤바 거터 신설(웹 산출물 기본값, 사용자 확정). 이 문서에서 유일하게 Tailwind 표면에도 걸리는 절이며, 두 줄이 함께여야 하는 이유와 자산 결함 오진 실측을 근거로 단다.\n- 2026-08-10: 초판 — BK2 V04(+V06 sr-only·V17 루트 폰트 합병). 근거 장부 `research/2026-08-10-bk2-promotion-verdicts.md`."},{"id":"customizer-and-packaging","title":"Customizer and Packaging","description":"Customizer and packaging rules for handing the system to someone else: which axes to expose (tone, accent, type direction) vs freeze (semantic token names, spacing scale, state strengths, contrast floor), why a tone preset carries radius rather than color alone, what makes generated output actually portable (one editable file, both light and dark faces, variable definitions shipped together with their utility mapping), and the four-part package claim (stack, assets, verification, round-trip)","tags":["packaging","customizer","portability","tokens"],"source_path":"knowledge/customizer-and-packaging.md","body":"# Customizer & Packaging — 시스템을 남에게 넘길 때의 규칙\n\n> 이식 가능한 제품(북극성)의 두 표면에 대한 규칙: **받는 사람이 무엇을 만질 수 있게 하는가**(customizer)와\n> **받는 사람에게 무엇을 약속하는가**(packaging). 근거 레퍼런스는 `research/around-template-system-capture.md`\n> (Around/Createx 실사 2026-07-04)와 그 재판정 `research/2026-08-06-m34-around-reverdict.md` — 원문을 여기 옮기지 않는다.\n\n## 1. 컨트롤로 노출할 것 / 고정할 것\n\n디자인 시스템을 남의 레포에 입힐 때 \"전부 조절 가능\"은 시스템이 아니라 설정 화면이다. 반대로 전부 고정하면\n그건 우리 브랜드지 그들의 제품이 아니다. 경계는 **정체성 축은 열고, 일관성 축은 닫는다**로 긋는다.\n\n**연다 (받는 사람의 정체성)**\n\n- **톤** — 지면의 성격(차분/따뜻/기술적). 색 하나가 아니라 배경·전경·카드·muted·border 가 **한 벌로** 움직인다.\n- **강조색** — 브랜드가 가장 먼저 드러나는 자리.\n- **타이포 방향** — 서체 *한 가족*. 위계는 굵기·크기로 만들고, 가족을 늘려 만들지 않는다.\n\n**닫는다 (시스템의 일관성)**\n\n- 의미 토큰의 **이름과 역할**(`--background`·`--muted-foreground`·`--scrim` …). 이름이 흔들리면 이식 계약이 깨진다.\n- 간격 스케일, 상태(hover/focus/disabled)의 강도 규칙, 대비 하한.\n- 컴포넌트가 **의미 토큰만** 참조한다는 규칙(원시값·리터럴 금지).\n\n## 2. 프리셋은 색만 묶지 않는다 — radius 를 함께 묶는다\n\n톤을 고르면 **모서리 반경도 함께 정해진다.** 색만 바꾸고 radius 를 고정하면 \"차분한 톤\"과 \"기술적인 톤\"이\n같은 얼굴을 갖는다 — 톤은 색의 문제가 아니라 *형태의 온도*까지 포함하기 때문이다.\n같은 이유로 그림자 강도·테두리 대비도 톤 프리셋의 구성원이지 별도 질문이 아니다.\n\n> 우리 구현: `packages/cli/src/kickstart.ts` 의 `CANVASES` 3종이 light/dark 쌍 + `radius` 를 한 덩어리로 갖는다.\n> 질문은 3개(톤·강조색·타이포)이고, 그 3개가 브랜드 CSS 변수 전체를 결정한다.\n\n## 3. 생성 결과는 \"복사 가능\"해야 한다\n\ncustomizer 의 값어치는 미리보기가 아니라 **가져갈 수 있는 산출물**에 있다. 규칙:\n\n- 산출은 **읽고 고칠 수 있는 파일 하나**로 나온다(우리: `src/askewly-brand.css`). 설정 UI 안에만 사는 상태는 이식물이 아니다.\n- 산출에는 **라이트·다크 두 면이 함께** 들어간다. 한 면만 주면 받는 쪽에서 반드시 다른 면을 손으로 채우게 되고,\n 그 순간 시스템이 갈라진다.\n- 변수 **정의**와 유틸 **매핑**은 한 몸으로 나간다 — 정의만 주면 유틸이 조용히 사라지고 빌드는 통과한다\n (M31 실증: `@theme inline` 누락 → `bg-scrim` 소멸). 이식 계약을 기계가 대조한다(`block-contract.md` §4).\n\n## 4. 아직 없는 것 (의도적 미보유 — 상한 표시)\n\nAround 는 갖고 우리는 없는 것 둘. 필요해지면 여기부터 검토한다.\n\n- **실시간 시각 프리뷰** — 우리는 질문에 답한 뒤 파일이 나온다. 고르는 동안 화면이 바뀌지 않는다.\n- **타이포 스케일 배수 조절** — 우리는 서체 스택만 고르고 배수는 고정이다(Around 는 `.75`~`1.375` 선택).\n 배수를 열면 대비·오버플로 검증을 그 축으로도 돌려야 한다 — 그 검증 없이 열지 않는다.\n\n## 5. 패키지 클레임 — 받는 사람에게 하는 약속\n\n\"이 시스템을 받으면 무엇이 따라오는가\"를 **한 자리에** 적는다. 절차 안내(next steps)는 약속이 아니다.\n최소 네 항목: **스택**(무엇 위에서 도는가) · **자산**(무엇이 실제로 들어오는가) · **검증**(무엇이 기계로 보장되는가) ·\n**왕복**(디자인 도구와 어떻게 오가는가). 우리 제품 문서의 해당 절은 `docs/PRD.md` 「제품 약속」이다.\n\n약속에 넣지 않는 것: 아직 실증되지 않은 능력, 결제·라이선스 강제(공개 탐색 경험과 에셋 모델이 정합해지기 전에는 붙이지 않는다)."},{"id":"dark-mode","title":"Dark Mode","description":"Dark mode judgment rules — definition (a redesigned palette, NOT an inversion), boundary vs night shift / forced-colors / inverted colors, design gates (no pure black, elevation-by-brightness, desaturation, semantic-token-only color), and web implementation gates (3-state model, FOUC head inline script, color-scheme, theme-color media split)","tags":["dark-mode","color","tokens","theming"],"source_path":"knowledge/dark-mode.md","body":"# Dark Mode — 정의·경계·판정 규칙\n\nDate: 2026-07-31\nMilestone: DM1 (plan: `plans/2026-07-31-dm1-dark-mode-knowledge.md`)\n지위: 살아있는 정본. \"다크모드가 무엇이고 무엇이 아닌가, 어떤 다크가 좋은 다크인가\"를 판정한다 — 특정 사이트에 어떻게 심는가(치환·배선 절차)는 각 구현 계획서 소관.\n근거: `research/2026-07-31-dark-mode-goal-dark-mode.md` (Apple HIG·MDN·Material 3·Primer·Tailwind 웹 리서치, 출처 URL+접근일 전건).\n\n## 1. 정의 — 다크모드는 \"반전\"이 아니라 별도 팔레트다\n\n다크모드는 **시스템 또는 사이트 전역의 외형 스킴**으로, 저조도 환경의 편안한 시야를 위해 **어두운 팔레트로 재설계된 별도 테마**다. 세 요소가 함께 있어야 다크모드다:\n\n1. **전역성** — 화면·뷰·메뉴·컨트롤 전반에 일관 적용되는 전역 상태 (Apple HIG는 systemwide appearance 로 정의).\n2. **목적** — 저조도 편의·눈부심 감소. 사용자 다수가 이를 기본값으로 선택하며, 앱이 이 선호를 존중하길 기대한다.\n3. **재설계** — 색만 뒤집는 게 아니라 대비 관계·표면 위계(elevation)·채도까지 다시 설계한 팔레트. \"배경을 검게, 글자를 희게\"의 기계적 반전은 다크모드가 아니다.\n\n(Apple HIG https://developers.apple.com/design/human-interface-guidelines/foundations/dark-mode/ · MDN https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme · Material 3 https://m3.material.io/blog/android-dark-theme-tutorial, 접근일 2026-07-31)\n\n## 2. 경계 — 다크모드가 아닌 것들\n\n혼동 빈도가 높은 인접 개념 4종. 축이 다르면 다른 물건이다.\n\n| 개념 | 축 | 다크모드와의 차이 |\n|---|---|---|\n| **Night Shift (색온도)** | 스펙트럼 | 색온도를 웜톤으로 이동해 블루라이트를 줄인다. 다크모드는 총 휘도를 낮추지만 스펙트럼은 안 바꾸고, Night Shift 는 스펙트럼을 옮기되 밝기는 안 낮춘다 — 독립 축, 동시 사용 가능 |\n| **High contrast / forced-colors** | 접근성 강제 | 사용자 지정 제한 팔레트를 페이지 전체에 **강제**하는 렌더링 모드. 저자 CSS 색을 대부분 무시한다. 다크모드 대응이 이걸 커버한다고 가정하면 안 된다 — `@media (forced-colors: active)` 별도 축 |\n| **Inverted colors (Smart/Classic Invert)** | 사후 반전 | OS 가 앱 지원 여부와 무관하게 색을 뒤집는 범용 오버라이드. 다크모드 = **개발자가 의도 설계한 테마**, Invert = 설계 없는 사후 처리 — 완성도 차이가 본질 |\n| **Dark theme** | 어휘 | 사실상 동의어. 굳이 가르면 mode=전역 상태 전환, theme=그 상태에 대응하는 팔레트·토큰 산출물 (추정 — 공식 표준이 엄격히 구분하지 않음) |\n\n(MDN forced-colors https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors · Apple https://support.apple.com/en-us/102191 · Smart Invert https://useyourloaf.com/blog/accessibility-smart-invert/, 접근일 2026-07-31)\n\n**따름 정리**: 다크모드는 선호(preference) 기능이지 접근성 기능이 아니다. WCAG 대비 검증은 라이트/다크 **각 테마에서 독립적으로** 통과해야 한다 — 라이트 통과가 다크 통과를 의미하지 않는다. (https://www.boia.org/blog/offering-a-dark-mode-doesnt-satisfy-wcag-color-contrast-requirements, 접근일 2026-07-31)\n\n**일반화 (2026-08-10 BK2)**: 이 규칙은 다크모드 전용이 아니다 — **팔레트를 스왑하는 모든 테마 시스템**(멀티 브랜드 테마·섹션별 컬러 테마 포함)에서 테마마다 독립 대비 검증이 의무다. 실물 반례 2건: ① 다크 팔레트에서 `--progress-color #172f35` 가 배경 `--section-color #162f35` 위에 놓여 사실상 비가시(토큰 2계층 구조는 옳았는데 **값을 테마별로 검증하지 않아** 무너짐) ② **라이트 테마 5종**에서 같은 실패 — `banana` 테마가 `#fcc000` 텍스트를 `#fff2d4` 배경에 + `opacity: .5` 중첩. (『인터랙티브 웹 애니메이션』 8·9장, `github.com/Youngjin-com/interactive_web@a2dfc627` 접근 2026-08-08 — 장부 `research/2026-08-10-bk2-promotion-verdicts.md` V25·V31)\n\n## 3. 좋은 다크의 판정 규칙 (디자인)\n\n1. **순수 검정 금지** — 배경 기본값은 `#000` 이 아니라 짙은 회색(Material 기준 `#121212`). 극단 대비는 halation(빛번짐)을 악화시키고, 비점수차 사용자(인구 약 47%가 어떤 형태로든 보유)에게 흰 텍스트가 번져 보인다. 텍스트도 순백 대신 오프화이트.\n2. **Elevation 은 그림자가 아니라 표면 밝기** — 라이트에서 그림자로 표현하던 높이를, 다크에서는 표면에 밝은 오버레이를 합성해 \"높을수록 밝게\"로 표현한다 (Material: 0dp 0% → 24dp 16% 선형).\n3. **채도를 낮춘다** — 어두운 배경 위 고채도 원색은 진동·번짐. 브랜드 색을 그대로 옮기지 말고 다크 전용으로 재조정한 값을 semantic 계층에 매핑한다.\n4. **모든 색은 semantic 토큰을 거친다** — 원시 hex·리터럴 팔레트 클래스를 컴포넌트에 직접 쓰면 다크는 구조적으로 불가능해진다. Primer 원칙: primitive → semantic 2계층 참조 강제, `:root` 스코프 스왑만으로 전체 전환. 본 레포 3-tier 토큰([[theme-token]] 참조 구조)과 동일 패턴.\n5. **이미지·일러스트는 별도 판정** — 라이트 전제 에셋(흰 배경 스크린샷·검정 텍스트 투명 PNG)은 다크에서 깨진다. 다크 전용 에셋 또는 SVG+`currentColor`.\n\n(Material 3 https://m3.material.io/blog/android-dark-theme-tutorial · Primer https://primer.style/product/getting-started/foundations/color-usage/ · astigmatism https://www.boia.org/blog/dark-mode-can-improve-text-readability-but-not-for-everyone, 접근일 2026-07-31)\n\n## 3-1. 재설계를 손으로 하지 않는다 — 생성 규칙 (M48 보강)\n\n§1-3 이 \"색만 뒤집는 게 아니라 재설계한 팔레트\"라고 못박았다. 이 절은 그 재설계를 **어떻게** 하느냐를 한 단계 위에서 정한다: 테마마다 값을 손으로 정의하지 말고, **소수의 입력값에서 나머지를 계산한다.**\n\n1. **테마 수에 비례해 정의가 늘면 그것은 값 문제가 아니라 생성 규칙 부재의 신호다.** semantic 층을 더 쪼개는 것은 곱셈의 밑을 키우는 대응이라 문제를 키운다. Linear 는 테마당 98개 변수 정의를 3개(base·accent·contrast)로 줄였다 — \"Instead of having to define 98 specific variables for each theme, we defined three: base color, accent color, and contrast.\" (https://linear.app/now/how-we-redesigned-the-linear-ui, 접근일 2026-08-12)\n2. **계산으로 옮기려면 색공간부터 지각 균일한 것으로 바꾼다.** sRGB·HSL 에는 \"같은 밝기 값 = 같아 보이는 밝기\"가 성립하지 않아, 계산 결과를 색상마다 손보다 결국 손 정의로 되돌아온다. Linear 는 LCH 로, Stripe 는 Lab 으로 갔다 — \"At the same level of mathematical lightness, yellow appears lighter than blue.\" · \"By translating our color scales into the Lab color space, we can adjust our colors based on their perceptual contrast\" (https://stripe.com/blog/accessible-color-systems, 접근일 2026-08-12)\n3. **대비는 검사가 아니라 구성으로 보장한다.** 스케일에 대비 규칙을 심으면 조합마다 재검사할 필요가 없다 — \"Any two colors are guaranteed to have sufficient contrast for small text if they are at least five levels apart.\" (Stripe, 같은 글)\n4. **접근성 축(대비)은 별도 테마가 아니라 생성 입력값으로 둔다.** 그래야 테마가 늘어도 고대비 대응이 따라 늘지 않는다 — \"The theme generation system also supports a contrast variable … This allows us to automatically include super high-contrast themes for users who need it for accessibility reasons.\" (Linear, 같은 글). Apple 도 대비 증가 설정까지를 색 정의 범위로 본다: \"Make sure all your app's colors work well in light, dark, and increased contrast contexts.\" (Apple HIG Color, 접근일 2026-08-12)\n5. **색은 의미 채널이다** — 같은 색에 두 뜻을 싣지 않는다. \"Avoid using the same color to mean different things.\" (Apple HIG Color)\n6. **탐색 단계에서는 색을 나중에 얹는다.** 구조가 흑백 불투명도만으로 읽히면 색은 계산이 채운다 (Linear 탐색 절차, 같은 글).\n\n> 이 절은 **채택 지시가 아니라 판정 규칙**이다. 색공간 전환·기존 토큰 마이그레이션 비용은 프로젝트가 따로 판단한다. 배경 지식은 kg 노드 `theme-variable-collapse-via-generative-color`.\n> 근거 장부: `research/2026-08-12-visual-benchmark-references.md` §5-1·§5-2. 소비처 = M44 자산 전수 점검 — **미실증**.\n\n## 4. 웹 구현 판정 규칙\n\n1. **상태 모델은 3-상태가 표준** — 라이트/다크/시스템. 명시 선택은 `localStorage` 저장, \"시스템\"은 키 제거 + `matchMedia(\"(prefers-color-scheme: dark)\")` 추종(+change 리스너). Tailwind 는 class/selector 전략(`@custom-variant dark`)과 이 패턴을 공식 레퍼런스로 문서화.\n2. **FOUC 는 head 인라인 스크립트로만 막힌다** — SSG/prerender 사이트에서 hydration 후 테마를 적용하면 첫 페인트에 잘못된 테마가 번쩍인다(FOUC, \"FART\"). `<head>` 안 동기 인라인 스크립트가 렌더 전에 `<html>` 클래스를 결정해야 한다 — 컴포넌트 코드로는 구조적으로 불가능.\n3. **`color-scheme: light dark` 선언** — 스크롤바·폼 컨트롤·시스템 색을 브라우저가 자동 대응(공짜 다크). 커스텀 컴포넌트는 커버 안 됨.\n4. **`meta theme-color` 는 media 분기** — `media=\"(prefers-color-scheme: dark)\"` 이중 meta 로 모바일 브라우저 크롬까지 테마 대응.\n5. **전환 transition 은 토글 시에만** — 상시 transition 은 초기 로드(FOUC 스크립트가 클래스를 붙이는 순간)에 불필요한 페이드를 만든다 (관행, 추정).\n6. **수동 토글과 OS 감지의 정합** — `<picture media=\"(prefers-color-scheme: dark)\">` 류 CSS/HTML 분기는 OS 설정만 본다. 사이트가 자체 토글로 테마를 강제하면 어긋난다 — 이미지 분기도 사이트 테마 상태를 따라야 한다.\n\n(Tailwind https://tailwindcss.com/docs/dark-mode · FOUC https://css-tricks.com/flash-of-inaccurate-color-theme-fart/ · color-scheme https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme · theme-color https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/theme-color · 이미지 https://rhyslloyd.me/serve-dark-mode-images-natively/, 접근일 2026-07-31)\n\n## 5. 참조 구현\n\n- **GitHub Primer** — semantic 토큰 강제·`:root` 스왑 전환의 정본. https://primer.style/product/getting-started/foundations/color-usage/\n- **Linear** — dark-first: 다크에서 elevation·표면 위계를 먼저 확정하고 라이트를 파생 — \"라이트 먼저, 다크는 나중에 끼워 맞추기\"의 실패를 구조적으로 회피. https://chyshkala.com/blog/why-linear-design-systems-break-in-dark-mode-and-how-to-fix-them\n- **Stripe Connect embedded** — 임베드 컴포넌트에 호스트 테마를 파라미터로 명시 주입·동적 동기화 — 격리된 콘텐츠 표면(데모 카드·iframe)에 테마를 전달하는 패턴. https://docs.stripe.com/connect/embedded-appearance-support-dark-mode\n- **Tailwind 공식 문서** — 3전략·3-상태 토글·FOUC 스니펫의 웹 구현 정본. https://tailwindcss.com/docs/dark-mode\n\n(전건 접근일 2026-07-31)\n\n## Changelog\n\n- 2026-08-12: M48 — §3-1 신설(생성 규칙으로의 재설계: 소수 입력값 파생·지각 균일 색공간·대비를 구성으로 보장·대비를 접근성 입력값으로). 근거 Linear·Stripe 공개 문서 + Apple HIG Color.\n- 2026-08-10: BK2 — §2 따름정리에 일반화(팔레트 스왑 전체) + 실물 반례 2건. V25·V31."},{"id":"dashboard-density","title":"Dashboard Density","description":"Dashboard density & low-noise hierarchy rules (Linear 2026 refresh x Geist): density is not noise — keep information, cut borders/icon excess/brightness competition; attention budget (nav recedes after arrival, compact chrome), whitespace-before-borders order, two-tier page background cap, hover/active from adjacent scale steps (never new hues), and the two-step text hierarchy rule","tags":["dashboard","density","hierarchy","application-ui"],"source_path":"knowledge/dashboard-density.md","body":"# Dashboard Density & Low-Noise Hierarchy — 밀도·소음 판정 규칙\n\nDate: 2026-08-01\nMilestone: M8 (plan: `plans/2026-08-01-m8-saas-exemplar-absorption.md`)\n지위: 대시보드·운영 도구처럼 정보 밀도가 높은 화면의 밀도·위계·소음 판정 정본. 근거 원문은 `research/2026-08-01-m8-dashboard-density-capture.md` 에 동결(Linear 2026-03 리프레시 원문 · Geist Colors) — 이 문서는 판정 규칙만 담는다. 모바일 내비게이션·모달 선택은 [[mobile-navigation]](`knowledge/mobile-navigation.md`) 소관 — 이 문서는 화면 안 밀도·위계만 다루고 컨테이너 선택은 다루지 않는다.\n\nAudience: 에이전트(대시보드·데이터 밀집 화면 생성 시 필수 참조) + 사람.\n\n## 0. 대원칙 — 밀도 ≠ 소음\n\n밀도가 높다는 이유로 정보를 빼지 않는다. 줄이는 것은 **소음**(보더·아이콘 과잉·밝기 경쟁)이고 지키는 것은 **정보 밀도**다. \"조밀해서 어수선하다\" 진단에 정보 삭제로 답하는 것은 오답이다.\n\n## 1. 주의 예산 (attention budget)\n\n| 요소 | 규칙 |\n|---|---|\n| 본문 작업 영역 | 화면에서 가장 높은 시각적 우선순위 — 항상 |\n| 사이드바·내비 | 도착 후에는 후퇴 — 본문보다 어둡게/저대비로 (\"earned attention\" — 주의는 얻은 만큼만) |\n| 앱 셸 탭·크롬 | 콤팩트하게 — 전폭 점유 대신 축소·라운드, 콘텐츠 밀도는 유지하고 크롬 밀도만 감축 |\n| 아이콘 | 엔티티 식별(프로젝트·상태 스캔)에만 — 장식 컬러 배경 금지·개수와 크기 절제 |\n\n## 2. 구조 표현 (structure felt, not seen)\n\n1. **구분의 기본값 = 여백.** 보더·구분선은 이유 없이 증식한다 — 여백 → 배경 미차 → 라운딩 순으로 먼저 시도하고 선은 최후 수단.\n - 이 순서는 우리 관측만의 것이 아니다. Apple 도 그룹 표현 수단을 같은 순서로 열거한다 — \"you might use **negative space, background shapes, colors, materials, or separator lines** to show when elements are related\" (Apple HIG Layout, 접근일 2026-08-12). 여백이 첫째고 선이 마지막이다.\n2. 선을 쓸 때는 저대비 + 그룹 경계에만(항목마다 긋지 않는다).\n3. 페이지 배경은 **2단 상한** — 2단째는 미묘한 구분이 필요할 때만 아껴서 (Geist Background 1/2).\n\n### 2-가. 저대비를 만드는 수단 — 색이 아니라 알파 (M48 보강)\n\n보더를 회색 **값**으로 잡으면 그 값은 특정 배경 위에서만 맞는다. 배경이 바뀌면 대비 관계가 깨지고, 다크 테마에서 다시 정의해야 한다. 검정·흰색의 **낮은 불투명도**로 잡으면 관계가 배경을 따라간다.\n\n실측: Attio 는 보더 색 최빈이 `rgba(0,0,0,0.05)`·`rgba(0,0,0,0.06)` 로 회색 값이 아니라 검정의 5~6% 다(https://attio.com, 접근일 2026-08-12). 반대로 Stripe 문서 표면은 저대비 청회색 3단으로 같은 목적을 값으로 달성한다(https://docs.stripe.com/payments, 접근일 2026-08-12) — 둘 다 성립하며, **알파 쪽이 테마가 늘 때 유지 비용이 낮다.**\n\n> 두께는 변수가 아니다. 관측된 저작값은 두 표면 모두 1px 단일이었고, 「subtle」을 만든 것은 두께가 아니라 대비다.\n\n### 2-나. 보더·반경·그림자를 따로 정하지 않는다 — 고도 프리셋 (M48 보강)\n\n반경과 보더를 각각 독립 축으로 고르면 조합이 폭발하고 \"이 카드는 왜 저 카드와 다른가\"에 답할 수 없게 된다. Vercel Geist 는 이 넷을 **하나의 고도(elevation) 역할**로 묶는다.\n\n> \"Presets for radii, fills, strokes, and shadows.\" · \"Use Material instead of hand-rolling radii, fills, strokes, and shadows on a surface; **the type encodes the elevation role.**\" (https://vercel.com/geist/materials, 접근일 2026-08-12)\n\n- **반경은 값이 아니라 단계다.** Geist 의 사다리는 3단이다 — 바닥·낮은 부상·툴팁이 한 단, 메뉴·모달이 한 단, 전체화면이 한 단. 값 자체는 프로젝트 소유이고, 규율은 **단계 수를 셀 수 있게 유지하는 것**이다.\n- **고도는 낮을수록 좋다.** \"Favor the lowest elevation that still reads as elevated against its background; **over-elevating is a common source of visual noise.**\" (같은 문서) — §0 의 \"소음을 줄인다\"와 같은 말이다.\n- **한 요소에 두 고도를 겹치지 않는다.** 자식에 더 높은 고도가 필요하면 자식을 자기 표면으로 올린다(같은 문서).\n- **반경의 절대값은 규범이 아니다.** 실측 최빈이 2px에서 12px까지 흩어졌다(§M48 장부 §7-4). Apple 도 값이 아니라 관계로 말한다 — \"make sure it harmonizes with the curvature of the hardware and aligns with adjacent safe areas\" (Apple HIG Layout, 접근일 2026-08-12). 톤 프리셋이 반경을 함께 묶는 우리 쪽 선례는 [[customizer-and-packaging]] §2.\n\n> 근거 장부: `research/2026-08-12-visual-benchmark-references.md` §5-3·§6·§8. 소비처 = M44 자산 전수 점검 — **미실증**.\n\n## 3. 상태·위계의 색 규칙\n\n1. **hover/active 는 새 색이 아니다** — 같은 중립 스케일의 인접 단계로 표현한다 (Geist: 배경 1–3 밴드·보더 4–6 밴드). 우리 3-tier 토큰에서는 semantic 단계 인접값 참조로 구현.\n2. **텍스트 위계는 2단** — primary/secondary. 세 번째 회색이 필요해 보이면 위계 설계 실패 신호로 읽고 구조를 다시 본다.\n3. 저소음 밀집 UI 의 중립색은 저채도로 — 채도는 상태·강조 채널에 아껴 쓴다.\n\n## 판정 절차 (에이전트 의무)\n\n1. 대시보드·밀집 화면을 만들기 전에 §0 을 선언한다 — 정보를 줄일지 소음을 줄일지 구분.\n2. 크롬(내비·탭·툴바)과 콘텐츠를 나눠 §1 주의 예산을 배분한다 — 크롬이 콘텐츠와 밝기 경쟁하면 FAIL.\n3. 구분선을 넣기 전에 §2-1 순서(여백→배경→라운딩→선)를 거쳤는지 확인한다.\n4. hover/active/보더 색이 스케일 밖 새 색이면 §3-1 위반 — 토큰 스케일 인접 단계로 교체.\n5. 스타일 수치(Linear 그레이 값·Geist 색 값)는 흡수 대상이 아니다 — look 은 프로젝트 토큰 소유.\n6. 보더를 넣기로 했으면 §2-가 대로 **알파를 먼저 검토**하고, 반경·그림자를 함께 정하고 있으면 §2-나 대로 **고도 단계 하나**로 묶었는지 확인한다. 화면의 고도 단계를 셀 수 없으면 FAIL.\n7. 여백·정렬 판정은 [[spacing-and-alignment]], 글자 위계는 [[type-hierarchy]] 가 소유한다 — 이 문서는 밀집 화면의 소음만 다룬다.\n\n## Changelog\n\n- 2026-08-12: M48 — §2-가(저대비는 색이 아니라 알파)·§2-나(보더·반경·그림자를 고도 프리셋 하나로) 신설, §2 구분 순서에 Apple HIG 교차 근거 추가, 판정 절차 6·7 추가.\n- 2026-08-01: 초판 (M8) — Linear 리프레시 원문(2026-03-12)·Geist Colors 실브라우저 캡처 기반."},{"id":"deck-layout-vocabulary","title":"Deck Layout Vocabulary","description":"Slide layout vocabulary harvested from the /pt production skill: 19 layouts classified by density (low/medium/high/portfolio) and kind (static/interactive), with selection rules — pick by information density first, split when over budget, interactive only when the interaction itself is the evidence","tags":["slides","deck","layout","vocabulary"],"source_path":"knowledge/deck-layout-vocabulary.md","body":"# Deck Layout Vocabulary — 슬라이드 레이아웃 어휘\n\nDate: 2026-08-10\nMilestone: M43 (plan: `archive/plans/2026-08-10-m43-pt-surface-audit.md`)\n지위: 덱을 만들 때 **어떤 슬라이드에 어떤 레이아웃을 쓰는가**의 어휘 정본. 콘텐츠·구조 원칙은\n[[slide-principles]], 캔버스·대비 게이트는 slide-spec(`docs/design-system/slide-spec.md`) 소관 —\n이 문서는 그 사이, \"구조가 정해진 메시지를 어떤 틀에 앉히는가\"를 답한다.\n출처: `/pt` 스킬 `templates/layout-meta.json` + 렌더러 실사(2026-08-10, M43 판정표\n`research/2026-08-10-m43-pt-surface-verdict.md`). **구현 정본은 /pt 스킬** — 여기서는 어휘만.\nharvest: 자기 산출물(custom-skills /pt), 실전 덱 다수 통과 운영 코드.\n\n## 분류표 — 밀도 × 종류\n\n밀도(density)는 한 슬라이드에 앉힐 수 있는 정보량의 예산이다. 레이아웃마다 항목 상한이 있고,\n**상한을 넘으면 레이아웃을 바꾸는 게 아니라 슬라이드를 쪼갠다**([[slide-principles]] 원칙 1 —\n메시지가 둘이면 슬라이드도 둘).\n\n| 밀도 | 레이아웃 | 항목 상한 | 언제 쓰나 |\n|---|---|---|---|\n| low | cover | 3 | 표지 — 제목·부제·소속. 진입 효과는 초점(focus) |\n| low | closing | 3 | 맺음 — 요약 한 줄·연락처. 페이드 |\n| low | hero-cards | 3 | 핵심 주장 1~3개를 카드로 크게 |\n| low | hero-motion | — | 모션이 곧 메시지인 히어로(효과 없음 선언 — 모션 자체가 효과) |\n| low | svg-filter-scene | — | SVG 필터 질감 씬 — 분위기 전환 |\n| medium | comparison-2col | 4 | 전/후·A/B 두 축 대조 |\n| medium | step-flow | 5 | 순서 있는 단계 흐름 |\n| medium | diagram-box | 7 | 박스·화살표 구조도 |\n| medium | summary-grid | 6 | 병렬 요약 격자 |\n| high | bento-grid | 8+ | 고밀도 벤토 — 크기 위계로 주(主)·부(副)를 가른다 |\n| portfolio | case-map | — | 사례를 지도·좌표로 배치 |\n| portfolio | split-screen | — | 좌우 분할 — 이미지 절반·텍스트 절반 |\n| portfolio | timeline-cards | — | 시간축 카드 나열 |\n| portfolio | pipeline-lanes | — | 레인별 파이프라인(역할·단계 교차) |\n| portfolio | result-transitions | — | 결과 장면 전환 시퀀스 |\n\n인터랙티브 4종은 **상호작용 자체가 증거일 때만** 쓴다([[slide-principles]] 원칙 3의 연장 —\n시각 증거의 강화형이지 장식이 아니다):\n\n| kind | 레이아웃 | 언제 쓰나 |\n|---|---|---|\n| interactive·data | chart-interactive | 실데이터 차트(Chart.js) — 숫자가 주장을 증명할 때 |\n| interactive·visual | three-scene | 3D 씬 — 공간·형태가 내용일 때 |\n| interactive·visual | before-after | 전/후 슬라이더 — 차이 자체가 메시지일 때 |\n| interactive·action | qr-embed | QR — 청중을 즉석에서 행동시킬 때 |\n\n## 선택 규칙\n\n1. **밀도 먼저** — 메시지의 항목 수를 세고 그 예산에 맞는 밀도 군에서 고른다. 항목이 상한을\n 넘으면 슬라이드를 쪼갠다(레이아웃 승급이 아니라 분할).\n2. **static 이 기본값** — interactive 는 조작이 증거일 때만. 발표장에서 조작이 실패하면\n static 폴백이 있는가를 먼저 묻는다.\n3. **진입 효과는 레이아웃이 이미 안다** — cover=focus·closing=fade·대부분=rise. 슬라이드마다\n 효과를 새로 고르지 않는다(리듬 일관 — [[motion-principles]] 시퀀싱 원칙과 같은 이유).\n4. **portfolio 군은 서사 슬라이드다** — 사례·경과·결과처럼 \"보여주며 말하는\" 자리에만. 논증\n 슬라이드(주장+증거)에 쓰면 밀도가 흩어진다.\n\n## Changelog\n\n- 2026-08-10: 초판 — M43, /pt layout-meta 19종 회수 (그릇 판정 A안: knowledge 1편, 사용자 확정)."},{"id":"dictionary-site-ia","title":"Dictionary Site IA","description":"Dictionary-site IA: dual YAML source-of-truth (content vs navigation), build-time cross-validation gate, reduced core schema, in-memory weighted search — harvested from a production dictionary site","tags":["ia","navigation","search","yaml"],"source_path":"knowledge/dictionary-site-ia.md","body":"# Dictionary-Site IA — 사전형 사이트 정보 구조\n\n> 지위: 용어·개념 사전형 사이트의 데이터→화면 구조 판정 규칙. 출처 = development-dictionary(dev.askewly.com) 실구현 채굴(harvest, 2026-08-04 — `research/2026-08-04-m22-harvest-mining-ledger.md` ⑩ 카드). 코드 벤더링이 아니라 구조 계약을 이식한다.\n\n## 핵심 계약 — 이중 SoT + 빌드 타임 교차검증\n\n| 층 | 정본 | 규칙 |\n|---|---|---|\n| 콘텐츠 | `terms.yml` (항목당 1레코드) | 화면이 아니라 데이터가 정본. 코어 필드 = `id`·`name(ko/en)`·`one_liner`·`category`·`related` — 그 외(설명 섹션·visual model·출처)는 선택 확장. 15필드 전체 복사는 과잉(원 구현 실측 소견) |\n| 내비 | `navigation.yml` (그룹·배치 별도 파일) | 콘텐츠와 **분리** — 같은 항목을 다른 축으로 재배치할 수 있어야 한다 |\n| 검증 | 빌드 스크립트 2본 | ① 전 필드·enum·출처 id 런타임 단언 ② **교차검증**: 모든 term 이 nav 에 배치됐는가 + 모든 nav 참조가 실존하는가 — 하나라도 어긋나면 빌드 FAIL (조용한 누락 불가) |\n\n## 화면 규칙\n\n- 상세 렌더는 데이터 분기: 설명 섹션이 있으면 커스텀 아티클, 없으면 제네릭 섹션 — 템플릿 강제가 아니라 데이터가 형태를 고른다. visual model(노드 배열)은 데이터에서 다이어그램을 직접 렌더.\n- 검색은 빌드 인덱스 없이 인메모리 가중 필드 스코어링(name > alias > category > one_liner)이면 수백 항목까지 충분. 한국어 자연어 질의는 수기 부스트 표로 보정.\n- 빈 검색 결과는 회복 UX(되돌리기 액션 + 추천 질의)를 붙인다 — 막다른 화면 금지.\n\n## 판정 절차\n\n새 사전형 사이트 의뢰 시: ① 코어 5필드로 terms 정의 ② 내비를 별도 파일로 ③ 교차검증 빌드 게이트 먼저 배선 ④ 확장 필드는 실수요 발생 시. 본 레포 실구현 선례 = `docs/ui-vocabulary/`(terms.yml·groups.yml) — 같은 사상.\n\n## Changelog\n\n- 2026-08-04: 초판 — M23 harvest 배치 2 (B2). development-dictionary 실구현 채굴 착지."},{"id":"empty-and-error-states","title":"Empty & Error States","description":"Empty-state and error-recovery judgment rules: an empty state must say why it is empty and what to do next (never just 'no data'), empty and error are different states and must not be collapsed, error copy names the cause in user language with a recoverable action, input is preserved across failures, and retry is offered only when retry can actually succeed","tags":["empty-state","error","recovery","states","forms"],"source_path":"knowledge/empty-and-error-states.md","body":"# Empty & Error States, 빈 상태와 오류 복구 판정 규칙\r\n\r\nDate: 2026-08-17\r\nMilestone: M57 (plan: `plans/2026-08-17-m57-ux-dictionary-absorb.md`)\r\n지위: 화면에 보여줄 것이 없거나 동작이 실패했을 때의 판정 정본. 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #5·#6 에 동결(원본 `ux-dictionary@60cbab3` `docs/ux-dictionary/methods.yml` 의 `empty-state-ux`·`error-recovery-ux`). 결제 플로우 고유의 실패 처리는 [[checkout-flow]] 소관이고, 폼 필드 자체의 계약은 [[form-fields]] 소관이다. 이 문서는 두 곳에 걸치지 않는 일반 규칙만 담는다.\r\n\r\nAudience: 에이전트(빈 목록·검색 결과 없음·실패 상태를 만들 때 필수 참조) + 사람.\r\n\r\n## 0. 대원칙, 빈 상태는 정보가 아니라 다음 행동이다\r\n\r\n\"데이터가 없습니다\"는 사용자가 이미 아는 사실을 반복하는 문장이다. 빈 상태가 답해야 하는 것은 셋이다. 왜 비었는가, 지금 무엇을 할 수 있는가, 어떻게 되돌리는가.\r\n\r\n이 레포의 `terms.yml` 에는 빈 상태 계열 컴포넌트가 29종 있다(`empty-state`, `empty-search-result`, `empty-table`, `mobile-empty-feed`, `location-permission-empty` 등). 물건은 충분하고 규칙이 없었다.\r\n\r\n## 1. 빈 상태와 오류를 같은 화면으로 처리하지 않는다\r\n\r\n가장 흔한 결함이다. 서버가 500 을 냈는데 \"결과가 없습니다\"를 띄우면 사용자는 조건을 바꾸며 시간을 버린다.\r\n\r\n| 원인 | 사용자에게 말할 것 | 주는 행동 |\r\n|---|---|---|\r\n| 아직 만든 것이 없다(최초 사용) | 이 화면이 무엇으로 채워지는지 | 첫 항목 만들기 |\r\n| 필터·검색이 좁다 | 어떤 조건이 걸려 있는지 | 조건 해제, 조건 완화 |\r\n| 권한이 없다 | 무엇을 허용해야 보이는지 | 설정으로 이동 ([[permission-and-notification]] §3) |\r\n| 로드 실패 | 실패했다는 사실 | 재시도 |\r\n| 정상적으로 0건이다(예: 읽지 않은 알림 없음) | 정상 상태임 | 없음, 축소 표시로 충분 |\r\n\r\n마지막 행이 중요하다. 모든 0건이 문제인 것은 아니다. 정상 0건에 큰 일러스트와 CTA 를 붙이면 화면이 사용자를 다그친다.\r\n\r\n## 2. 빈 상태의 시각 무게는 원인에 비례한다\r\n\r\n- **최초 사용**은 화면 전체를 써도 된다. 그 시점에는 그것이 유일한 콘텐츠다.\r\n- **필터 결과 0건**은 목록 자리 안에서 인라인으로 처리한다. 필터 UI 를 밀어내거나 가리면 되돌릴 수단을 뺏는 것이다.\r\n- **일러스트가 행동을 가리면 뺀다.** 장식이 CTA 보다 눈에 먼저 들어오는 배치는 실패다.\r\n\r\n## 3. 오류 문구는 원인을 사용자 언어로 지목한다\r\n\r\n- **기술 코드를 그대로 노출하지 않는다.** `ERR_CONN_RESET` 은 사용자가 할 수 있는 일을 알려주지 않는다. 지원 문의용 식별자가 필요하면 본문이 아니라 접힌 보조 영역에 둔다.\r\n- **사용자가 할 수 없는 행동을 CTA 로 주지 않는다.** 서버 장애에 \"다시 시도\"만 있고 실제로는 5분간 계속 실패한다면 그 버튼은 거짓말이다. 재시도로 풀리지 않는 실패에는 대체 경로나 예상 복구 시점을 준다.\r\n- **비난하지 않는다.** \"잘못된 입력입니다\"보다 \"전화번호는 숫자만 넣어 주세요\"가 짧고 정확하다.\r\n\r\n## 4. 실패는 입력을 삼키지 않는다\r\n\r\n제출이 실패했을 때 사용자가 친 값이 사라지면, 그것은 오류 하나가 아니라 오류 더하기 재입력이다. [[checkout-flow]] §0 이 \"마찰의 본체는 재입력\"이라고 말하는 것과 같은 규칙이 실패 경로에도 적용된다.\r\n\r\n- 실패 후에도 입력값을 유지한다. 비밀번호처럼 유지하면 안 되는 필드만 예외로 두고, 그 사실을 화면에서 알린다.\r\n- 실패 지점으로 포커스를 옮긴다. 사용자가 어디가 문제인지 스크롤로 찾게 하지 않는다. 포커스 이동 계약은 [[focus-keyboard]] 소관이다.\r\n- 부분 성공은 부분 성공이라고 말한다. 10건 중 3건이 실패했으면 어느 3건인지 지목한다.\r\n\r\n## 5. 판정 절차\r\n\r\n빈 상태나 실패 화면을 만들거나 리뷰할 때 차례로 묻는다.\r\n\r\n1. 이 화면의 0건 원인이 §1 표의 어느 행인가. 두 행 이상이 같은 화면으로 렌더되고 있지 않은가.\r\n2. 원인 설명과 다음 행동이 둘 다 있는가. 하나라도 없으면 미완이다.\r\n3. 그 다음 행동을 사용자가 실제로 수행할 수 있는가.\r\n4. 실패 경로에서 입력이 보존되는가.\r\n5. 정상 0건에 과한 무게를 주고 있지 않은가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 (M57). `ux-dictionary@60cbab3` 의 `empty-state-ux`·`error-recovery-ux` 두 항목을 합본으로 흡수. 착지 게이트 통과 근거는 판정 장부 §3-1 참조. 원문은 항목 나열 수준이라 그대로 옮기지 않고 판정 규칙으로 재작성했으며, 원문의 2차 출처(`material-search`·`nng-heuristics`)는 1차 확인 전이라 인용하지 않는다."},{"id":"expressive-stack","title":"Expressive Stack","description":"Expressive stack tier map: which rendering tier (CSS/SVG, JS motion orchestration, Canvas 2D + physics, WebGL/three.js) a visual effect belongs to, with a technique-to-tier decision table and mandatory judgment procedure (lower tier first, reduced-motion gating, lazy-load for GPU)","tags":["motion","rendering","webgl","canvas","tiers"],"source_path":"knowledge/expressive-stack.md","body":"# Expressive Stack — 표현 기술 티어 지도\r\n\r\nDate: 2026-07-17\r\nMilestone: VI1 (plan: `archive/plans/2026-07-17-vi1-expressive-stack-map.md`)\r\n지위: 살아있는 정본. 근거·출처 전체는 `research/2026-07-17-expressive-stack-genealogy.md`(동결 기록, 전 항목 출처 URL+접근일) — 이 문서는 판정 규칙만 담고 근거를 중복 서술하지 않는다.\r\n\r\nAudience: 코드를 생성하는 에이전트(Codex, Claude Code). \"이 시각 효과는 무엇으로 만드는가\"를 판정하는 데 쓴다.\r\n\r\n## 4개 렌더링 티어\r\n\r\n| 티어 | 정의 | 판별 신호 |\r\n|---|---|---|\r\n| **① CSS·SVG** (선언) | 브라우저가 선언만으로 렌더·애니메이션. JS 개입 없음 | 상태 A→B 전환, 정적/반복 시각, 스크롤 진행률 매핑 |\r\n| **② 모션 오케스트레이션** (JS 지휘 + CSS/DOM 렌더) | JS가 입력·측정·시퀀스를 계산하고 최종 렌더는 CSS transform/opacity | 커서 좌표·요소 측정·동적 시퀀스가 필요, 그러나 그리는 대상은 여전히 DOM |\r\n| **③ Canvas 2D·물리** | JS가 픽셀 버퍼에 직접 그림. 물리 엔진 포함 | 수백 개 이상 개체, 경로 이력, 상호 충돌 — DOM 개체로는 성능 불가 |\r\n| **④ WebGL·three.js** (GPU) | 셰이더 파이프라인. 3D·프레임 단위 절차적 픽셀 계산 | 조명·깊이·유기적 노이즈 애니메이션·유체 — CPU 픽셀 루프로 불가 |\r\n\r\n## 기법→티어 결정 표\r\n\r\n| 원하는 효과 | 티어 | 도구·기법 (근거: research doc 해당 절) |\r\n|---|---|---|\r\n| hover 시 색·그림자·크기 전환 | ① | CSS `transition` — JS 불필요 |\r\n| 유리 질감(글래스모피즘)·배경 블러 | ① | `backdrop-filter` + `mix-blend-mode` |\r\n| 메시/원뿔형 그라디언트 배경 | ① | 다중 radial-gradient 레이어링, `conic-gradient`, `@property`로 색 애니메이션 |\r\n| 형태 잘라내기·리빌 | ① | `clip-path: path()`, `mask-image` |\r\n| 스크롤 진행률에 매핑된 애니메이션 | ① | CSS scroll-driven animations — **두 갈래를 구분**: `animation-timeline: scroll()`(익명 — 컨테이너 전체 진행률) vs `view-timeline: --name`(요소가 뷰포트를 지나는 진행률 — 섹션·항목별 독립 타임라인은 이쪽). 단순 매핑이면 JS 불필요 (BK2 9장 실측) |\r\n| 그레인/필름 노이즈 텍스처 | ① | SVG `feTurbulence` 오버레이 (CSS filter 단독으론 노이즈 생성 불가) |\r\n| 텍스트·이미지 왜곡 | ① | SVG `feTurbulence`+`feDisplacementMap` |\r\n| 커서 추종·마그네틱 hover | ② | 포인터 좌표→거리 계산→CSS transform 갱신 (CSS `:hover`는 커서 위치를 모름) |\r\n| 스프링·관성 있는 UI 모션 | ② | Framer Motion / react-spring (물리 파라미터 보간은 JS 런타임) |\r\n| 핀 고정·스크럽·스크롤 스토리텔링 | ② | GSAP ScrollTrigger — 사유는 둘로 갈린다: (a) CSS scroll-driven 보다 세밀한 제어(*정도*), (b) **애니메이션 대상이 CSS 속성이 아닐 때(WebGL 씬 그래프·Canvas 상태·JS 객체 프로퍼티)는 CSS scroll-driven 이 애초에 대체재가 아니다(*종류* — 티어 비교 자체가 성립 안 함)** (BK2 11장 실측) |\r\n| 리스트 재정렬·카드 확장 전환 | ② | FLIP 기법 (측정은 JS, 재생은 CSS transform) |\r\n| 페이지·상태 전환 모핑 | ①+② | View Transitions API (트리거 JS 1줄 + `::view-transition-*` CSS) |\r\n| 벡터 일러스트 애니메이션 | ② | Lottie (AE 제작 → lottie-web 재생) |\r\n| 다요소 등장 안무(stagger) | ①/② | 정적이면 CSS `animation-delay`, 동적 개수·인터럽트면 JS stagger |\r\n| 수백 개 파티클·커서 트레일 | ③ | Canvas 2D + rAF (DOM 개체로는 레이아웃 비용 폭발) |\r\n| 개체 간 충돌·낙하·물리 | ③ | matter-js (트윈으론 상호작용 불가) |\r\n| 제너러티브 아트·노이즈 드로잉 | ③ | p5.js / Canvas API |\r\n| 3D 씬·조명·제품 뷰어 | ④ | three.js / R3F + drei (CSS `perspective`는 조명·깊이버퍼 없음 — 유사 흉내만) |\r\n| 애니메이팅되는 유기적 셰이더 그라디언트 | ④ | GLSL / Paper Shaders (CSS 그라디언트는 프레임 단위 노이즈 불가) |\r\n| 유체 시뮬레이션 | ④ | WebGL-Fluid-Simulation (Navier-Stokes는 GPU 병렬 필수) |\r\n| 화면 전역 글로우·색수차 | ④ | @react-three/postprocessing |\r\n| 높이가 내재적인 콘텐츠 열림/닫힘(아코디언·필터 패널·\"더 보기\") | ① | 1-row grid `grid-template-rows` 0fr↔1fr 보간 + 내부 `overflow: hidden`(+가벼운 opacity) — 취약한 height:auto/명시 height 전환 회피. 부적합: 대영역(지연 유발)·가상화 콘텐츠(높이 불안정). (KG `css-disclosure-transition-pattern` · WAI-ARIA APG accordion, 접근일 2026-07-28) |\r\n| 무한히 이어지는 절차적 지형·배경의 결 | ③/④ | **다층 해상도 노이즈** — 층마다 `resolution += upresFactor*(층수)` 를 누적해 성긴 굴곡 위에 잔결을 얹는다(예: `resolution 5·upresFactor 2·depth 3` → `[5,7,11]`). 기준선(도로·바닥) 주변은 임계값 아래에서 높이를 완충(`temper`)해 정합을 지킨다. 단일 노이즈 한 겹으로는 「층이 겹쳐 보이는」 결이 안 나온다. 부적합: 결정론적 재현이 필요 없고 텍스처 한 장으로 되는 정적 배경. (출처 `research/2026-08-17-m68-ledger.md` SLW-05·SLW-04 — slowroads.io, 접근일 2026-08-17 · **소비처**: 데모 `layered-noise-terrain`) |\r\n| 수천~수만 개 식생·군중이 바람에 흔들리는 배경 | ④ | **인스턴싱 + 정점 셰이더 스웨이** — 한 드로우로 깔고 정점 단계에서 흔든다. x·z 축에 **다른 주파수와 위상 오프셋**을 줘야 전체가 한 덩어리로 밀리지 않고(관측: x `.05` / z `.1`+phase), 흔들림 세기는 정점 높이에 비례시켜 뿌리는 고정한다. 포인터·캐릭터 위치를 uniform 으로 넘기면 「밟고 지나간 자리가 눕는」 상호작용이 같은 셰이더 안에서 난다. 오브젝트군마다 인스턴스 상한·LOD 전환 거리를 따로 잡는다. 부적합: 개체가 수십 개 이하(그때는 ②로 충분). (출처 SMA-12·SMA-11 — summer-afternoon.vlucendo.com, 접근일 2026-08-17 · **소비처**: 데모 `interactive-grass-sway`) |\r\n| 실시간 3D 비용을 감당 못 하는 자리에서 3D 인상만 필요할 때 | ①/② | **오프라인 베이킹** — 런타임 렌더 대신 미리 구운 이미지를 넘긴다. 회전이 필요하면 임포스터 스프라이트 아틀라스(관측: 16회전각 × diffuse/normal 2채널 webp), 한 축 스크럽이면 프레임 시퀀스. 비용이 GPU 에서 전송량·디스크로 옮겨 가는 교환이므로 **각도 수·해상도가 곧 용량**이다. 부적합: 조명·반사가 실시간으로 변해야 하는 장면. (출처 SLW-07 — slowroads.io `/dev/imposters`, 접근일 2026-08-17 · **소비처**: recipe `baked-frame-sequence-scrub` 이 같은 원리의 프레임 시퀀스 판이다) |\r\n| 선·경계를 「손으로 그은 자국」처럼 낡게 보이려 할 때 | ④(또는 ① SVG) | **SDF + 다중 옥타브 노이즈 풍화 + 존 임계값 침식** — 선을 거리 함수로 그리고 그 **거리값 자체에** 노이즈를 더해 가장자리를 흐트러뜨린 뒤, 저주파 존 노이즈가 만든 임계값(`mix(.3,.7,zone)`)을 고주파 샘플이 넘는 자리에서 선을 지운다. ⚠ **노이즈 진폭은 선 폭보다 작아야 한다** — 크면 흐트러지는 것이 아니라 선이 통째로 사라진다(M68 실측 결함). 기존 「그레인/필름 노이즈 ①」·「텍스트·이미지 왜곡 ①」과 대조: 그 둘은 표면에 텍스처를 얹거나 이미지를 변위시키고, 이 행은 **형태 자체의 경계를 갉는다**. (출처 LAC-11 — Lacoste Ace Breaker, 접근일 2026-08-17 · **소비처**: 데모 `procedural-court-line`) |\r\n| 진입 대기 시간을 연출로 채워야 할 때 | ①/② | **길이가 다른 단계를 겹쳐 바통을 넘긴다** — 순차가 아니다. 관측 3단은 로더 안무(750ms, 회전과 대시 길이 두 애니메이션 동시) · 셰이더/오버레이 크로스페이드(4s) · 카메라 풀백(6s)이고, **본 콘텐츠는 로더가 사라지는 동안 이미 같은 자리에 마운트돼 겹쳐 있다**. 순차로 이으면 총 시간이 합이 되고 이음매가 보인다. 폰트 로드 게이트를 함께 두어 늦게 온 서체가 레이아웃을 튀게 하지 않는다. (출처 SMA-02·SMA-04·SMA-05 — summer-afternoon.vlucendo.com, 접근일 2026-08-17 · **소비처**: 데모 `entrance-three-stage`, recipe `multi-stage-entry-loader`) |\r\n| 가로·세로가 함께 제약된 화면(게임 UI·키오스크·풀스크린 캔버스)의 타이포 | ① | **폭 clamp 에 뷰포트 높이 배수를 곱한다** — 폭 기준 유동 타이포만 쓰면 넓고 낮은 창에서 글자가 세로로 넘친다. 관측 구현은 `--base-text: calc(22px * var(--h-demult))` 처럼 폭에서 나온 값에 높이 배수를 한 번 더 곱하는 3중 체계(고정 px 표 · vw 선형식 · 높이 배수)다. 기존 「이중 클램프 플루이드 타이포」의 폭 축을 높이 축으로 보강한다. 부적합: 세로 스크롤 문서(높이 제약이 없다). (출처 LAC-13 — Lacoste Ace Breaker, 접근일 2026-08-17 · **소비처**: recipe `fluid-type-dual-clamp` 보강) |\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. **하위 티어 우선.** 같은 효과가 두 티어로 가능하면 항상 낮은 티어를 쓴다 — 예: 단순 hover에 JS 금지, 단순 스크롤 리빌에 GSAP 금지, 유사 3D(coverflow류)에 three.js 도입 금지(CSS `perspective`+`rotateY`로 충분한지 먼저 판정).\r\n - **예외 축 — 이미 로드된 상위 도구**: 상위 티어 도구가 *다른 정당한 이유로* 이미 번들에 있으면(예: WebGL 씬 애니메이션용 GSAP), 그 도구로 하위 티어 효과(DOM 페이드인)를 함께 처리하는 것은 허용한다 — 한계비용 0 + 타임라인 문법 통일 이득. 단 **도구 도입 자체의 근거는 여전히 상위 티어 쪽 작업이 져야 하고**, 그 작업이 사라지면 하위 효과도 CSS 로 회귀한다(무임승차 금지). (BK2 11장 — 이 축이 비어 있어 실무 최빈 판정에 답이 없었다)\r\n2. **티어 상승에는 근거를 적는다.** ②→③→④로 올릴 때는 \"하위 티어로 안 되는 이유\"(커서 이력 필요, 상호 충돌 필요, 조명/노이즈 필요 등)를 코드 리뷰어가 볼 위치(PR/변경 기록)에 남긴다.\r\n3. **③·④ 티어는 reduced-motion 분기가 의무.** canvas/WebGL은 CSS와 달리 자동 억제되지 않는다 — `matchMedia('(prefers-reduced-motion: reduce)')`로 렌더 루프·자동 회전을 명시적으로 끄고, 스크린리더용 병렬 DOM 레이어를 제공한다.\r\n4. **④ 티어는 lazy-load가 의무.** three.js는 gzip ~155KB 고정 비용 — dynamic import + Suspense로 격리하고 초기 번들에 넣지 않는다.\r\n5. **시그니처 접점**: 실험적 표현(③·④ 티어 대부분, ②의 커서/마그네틱)은 운용 원칙 5 \"실험적 터치는 수동\" — 사용자가 명시로 요청했을 때만 도입하고, 기본 제품 UI에는 ① 티어와 ②의 실무 패턴(스프링·FLIP·stagger)까지만 기본값으로 쓴다.\r\n6. **모션은 전환 규모에 비례한다.** 작은 상태 변화(토글·선택)는 짧고 담백하게, 크고 주목할 전환(시트 확장·전면 이동)은 길고 표현적으로 — 작은 것에 과장 모션, 큰 것에 인색한 모션이 둘 다 실패다 (M3 easing/duration 판단 diff 채택, taste ledger T-16).\r\n\r\n7. **숨김 모션에는 접근성 상태를 짝짓는다.** disclosure 류(아코디언·접이 패널·필터 서랍·확장 행·\"더 보기\") 애니메이션은 컨트롤의 `aria-expanded` 와 짝이 의무이고, 접힌 콘텐츠가 애니메이션을 위해 DOM 에 남아 있으면 `aria-hidden`·pointer guard·focus guard 로 상호작용을 함께 끈다 — \"모션은 상태 변화를 설명하고 접근성 의미론을 보존할 때 안전하며, 시각적 숨김과 상호작용 상태가 어긋나는 순간 위험해진다.\" 또한 reduced-motion 은 ③·④ 티어만의 의무가 아니다 — ①·② 티어라도 장식적이거나 큰 움직임이면 `prefers-reduced-motion` 분기를 둔다. (KG `frontend-motion-accessibility-source-map` · MDN prefers-reduced-motion, 접근일 2026-07-28)\r\n8. **모션의 \"무엇을·왜\"는 [[motion-principles]] 가 정본이다.** 이 문서는 \"무엇으로 만드나\"(티어·도구)만 판정한다 — 전환의 목적 선택·still appeal 게이트·타이포 가독성 등 품질 원칙은 `knowledge/motion-principles.md` 를 먼저 통과한다.\r\n9. **티어 ① 도 공짜가 아니다.** 뷰포트 크기 요소에 스크롤 연동 `filter: blur()`·대면적 그림자를 걸면 페인트 비용이 프레임마다 발생한다 — 티어 판정과 별개로 속성 비용(Layout/Paint/Composite)을 본다. 전환 속성은 `transition-property: all` 금지 — 실제로 바꾸는 속성(`transform`, `opacity`)만 명시한다. (BK2 9장·4장 실측)\r\n10. **폴백 계약.** ①에서 신형 기능(scroll-driven animations 등)을 쓰면: (a) **지원 대상을 먼저 정한다** — 대상 없이는 폴백 범위를 판단할 수 없다(점유율 판단은 caniuse, `toolshelf/cards/caniuse.md`) (b) `@supports not (...)` 가드를 의무로 두되 (c) **폴백은 장식을 끄는 것이지 기능을 끄는 것이 아니다** — 반례 실측: 미지원 브라우저에서 네비게이션을 통째로 숨긴 폴백(BK2 9장). 앵커 이동·콘텐츠 도달은 애니메이션 지원과 무관하게 살아 있어야 한다.\r\n\r\n## 인접 게이트 — ④ 티어가 외부 3D 에셋을 실을 때\r\n\r\n3D **씬 구현**(three.js/R3F)은 이 문서 소관이지만, 씬에 싣는 **에셋**(GLB/glTF·Gaussian splat)은 별도 게이트를 통과해야 한다 — 요지와 정본 위치만 적는다 (VI8 three.js recipe 확장 시 필수 참조):\r\n\r\n- **에셋 파이프라인 게이트**: export→최적화→검증→런타임 스모크. 핵심 규칙 — *Khronos validator 0 errors 가 제품 계약 보존을 증명하지 않는다* (실측: Khronos-clean 최적화 GLB 가 mesh name 13개·hitbox 13개를 잃고 앱 계약을 깨뜨림) — named mesh·hitbox·extras 를 앱 전용 contract validator 로 따로 검증. → KG `nodes/개발/web-3d-asset-pipeline-quality-gate.md` (접근일 2026-07-28)\r\n- **텍스처 포맷 게이트**: 바닥·환경 텍스처를 무압축 PNG 로 싣지 않는다 — 후보 순서는 GPU 압축(KTX2/Basis) → WebP → (사진류 한정) JPG. 반례 실측: 2.6MB PNG 바닥 텍스처(BK2 12장, `Youngjin-com/interactive_web@a2dfc627` 접근 2026-08-10). 에셋 총량이 수 MB 를 넘으면 lazy-load(절차 4)와 로딩 표시가 함께 의무다.\r\n- **AI 3D 생성 도구 선택**: 생성기는 완결 에셋이 아니라 upstream 후보 소스 — 입력 유형·품질·하드웨어·텍스처·배포 요건으로 레인을 고른 뒤 위 게이트에 반드시 짝지운다. → KG `nodes/AI/ai-3d-asset-generation-model-selection-gate.md` (접근일 2026-07-28)\r\n\r\n## 외부 기준선 — \"CSS 로 여기까지 된다\"의 실물 목록\r\n\r\n티어 상승 판정(절차 1 \"충분한지\")의 외부 대조군: 『인터랙티브 웹 애니메이션』(김영민, 영진닷컴) 4장은 JS 0줄로 3D 카드 플립(`perspective`+`preserve-3d`+`backface-visibility`)·3D 회전목마(`@keyframes`+`transform-style`)·SVG 모션·반응형까지 만든다 — 이 목록에 있는 효과에 티어 ② 이상을 제안하면 근거가 필요하다. 소스: `github.com/Youngjin-com/interactive_web@a2dfc627` `public/css/chapter_4/` (접근 2026-08-08, 판독 `research/books/interactive-web-animation/ch04.md`).\r\n\r\n## 자체 쇼케이스 역산 (참조 구현)\r\n\r\naskewly.com 랜딩 Showcase Atlas 12종의 티어 분포 — 실코드 근거는 research doc 역산 표:\r\n① CSS 다수(Image Treatment·Hero 등) + CSS 3D 유사물(Coverflow, Agent 카드 tilt) · ② Cursor-Reactive Field, Motion Choreography · ③ Physics(matter-js) · ④ Shader Gradient(Paper Shaders). three.js는 미사용 — GPU 티어 확장은 VI4.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: M68 — 결정표에 배치 3(웹게임·구조 메타포) 축 **6행 추가**(절차적 지형 다층 해상도 · 인스턴싱 정점 스웨이 · 오프라인 베이킹 · SDF 노이즈 풍화 · 진입 3단 겹침 · 높이 제약 타이포). 행마다 `absorption-criteria.md` 「knowledge 착지 게이트」 3조건(출처 백링크 · 기존 행 중복 대조 · 소비처 실증)을 통과시켰다. 기존 22행 무손실. 근거 `research/2026-08-17-m68-ledger.md` §1·§5.\r\n- 2026-08-10: BK2 — 결정표 스크롤 행 view-timeline/scroll() 구분·GSAP 사유 이원화(정도/종류), 판정 절차 1 「이미 로드된 상위 도구」 예외 축, 절차 9(티어① 속성 비용·transition-all 금지)·10(폴백 계약 — 지원 대상 선행·장식만 끈다) 신설, 인접 게이트 텍스처 포맷, 외부 기준선 절. 근거 `research/2026-08-10-bk2-promotion-verdicts.md` V09·V30·V40·V50·V52.\r\n- 2026-07-28: VI6 — KG 흡수: 결정표에 disclosure grid-track 행 추가, 판정 절차 7(접근성 짝규칙·전 티어 reduced-motion)·8(motion-principles 위임) 추가, ④ 티어 인접 게이트(3D 에셋 파이프라인·AI 생성 선택) 링크 절 신설. 근거 `research/2026-07-28-vi6-kg-crosswalk.md`.\r\n- 2026-07-17: 초판 (VI1 — 리서치 30기법 계보 + 쇼케이스 12종 역산에서 증류)."},{"id":"focus-keyboard","title":"Focus & Keyboard","description":"Focus & keyboard discipline for custom widgets and overlays (Radix/WAI-ARIA APG): APG is canon — never invent keyboard behavior; modal contract (trap + focus return to trigger + first-logical-item initial focus + title/description announcement); composite widgets are one Tab stop with roving tabindex, arrows, Home/End, and typeahead; explicit dismiss layer contract (Esc closes top layer only); no unnamed controls","tags":["accessibility","keyboard","focus","aria"],"source_path":"knowledge/focus-keyboard.md","body":"# Focus & Keyboard — 포커스·키보드 규율 판정 규칙\r\n\r\nDate: 2026-08-01\r\nMilestone: M10 (plan: `plans/2026-08-01-m10-radix-focus-keyboard-absorption.md`)\r\n지위: 커스텀 위젯·오버레이의 포커스·키보드 행동 판정 정본. 근거 원문은 `research/2026-08-01-m10-radix-focus-keyboard-capture.md` 에 동결(Radix Primitives, 접근 2026-08-01). 어떤 오버레이 **컨테이너를 고를지**는 [[mobile-navigation]] 소관, 이 문서는 고른 뒤의 **행동 계약**만 담는다. 포커스 링의 시각 규격은 terms(`focus-ring`), 감사 함정(programmatic focus 오탐)은 kg 노드 `focus-visible-audit-real-tab-key` 참조.\r\n\r\nAudience: 에이전트(커스텀 위젯·오버레이 생성 시 필수 참조) + 사람.\r\n\r\n## 0. 대원칙 — 정답은 WAI-ARIA APG, 발명하지 않는다\r\n\r\n네이티브 요소를 div 로 대체하는 순간 시맨틱(role·aria)과 **행동**(키보드·포커스) 재구현이 전부 우리 책임이 된다. 각 패턴의 행동 정답은 WAI-ARIA authoring practices 에 이미 있다 — 키보드 동작을 창작하지 말고 APG 패턴을 찾는다.\r\n\r\n## 1. 모달 오버레이 계약 (트랩 + 반환)\r\n\r\n1. modal 이면 **포커스 트랩** — Tab 순환이 내부에 갇힌다. non-modal(popover 등)엔 트랩을 걸지 않는다.\r\n2. **닫힐 때 포커스는 연 트리거로 반환** — Esc·버튼·scrim 어느 해제 경로든 동일. 반환 없는 트랩은 미완성.\r\n3. **열림 직후 첫 논리 항목에 포커스** — 메뉴 = 첫 항목, 파괴적 확인 = 안전한 버튼(Cancel), 폼 모달 = 첫 필드. 포커스 이동이 곧 스크린리더 공지 채널이다.\r\n4. Title/Description 을 배선해 열림이 \"무엇인지\" 공지되게 한다.\r\n\r\n## 2. 복합 위젯 계약 (roving tabindex)\r\n\r\n탭 목록·메뉴·툴바·라디오 그룹 같은 복합 위젯은 **Tab stop 1개**다:\r\n1. Tab 은 위젯에 들어왔다(활성 항목) 다음 Tab 에 밖으로 나간다 — 항목마다 Tab 을 먹으면 FAIL.\r\n2. 내부 이동은 방향키(orientation 기준) + Home/End.\r\n3. 항목 목록엔 **typeahead** — 타이핑으로 매칭 항목 점프.\r\n4. 이동-활성화 결합(automatic activation)이 기본, 비용 큰 패널 전환만 Enter 분리(manual).\r\n\r\n## 3. 해제(dismiss) 계층 계약\r\n\r\n1. 오버레이 해제 경로는 **계약으로 명시**: Esc · 외부 클릭/scrim · 트리거 재활성. 셋의 동작이 예측 가능해야 한다.\r\n2. 겹친 레이어의 Esc 는 **최상위 하나만** 닫는다 — 스택 순서 명시.\r\n3. 모달 깊이·중첩 허용 예외는 [[mobile-navigation]] §4 를 따른다.\r\n\r\n## 4. 라벨 계약\r\n\r\n1. **이름 없는 커스텀 컨트롤 금지** — 시각 라벨이 있으면 연결(label/aria-labelledby), 없으면 aria-label. 프리미티브가 배선을 추상화해도 라벨 내용은 제품 책임이다.\r\n2. 상태 변화로 화면이 바뀌면 포커스 이동 + 라벨로 새 맥락을 공지한다(라이브 리전 남발 대신 포커스 채널 우선).\r\n3. **입력 그룹은 `<fieldset>`+`<legend>` 로 묶는다** — 라디오·체크박스 묶음을 제목 태그(`<h2>` 등)로 감싸면 시각적으로만 묶이고 접근성 트리에서는 안 묶인다 — 스크린리더 사용자는 \"인천 라디오버튼\"이 무슨 질문의 선택지인지 모른다. 반례 실측: BK2 2장(`research/books/interactive-web-animation/ch02.md`).\r\n4. **포커스 표시의 시각 최소선** — 배경색 틴트만 바꾸는 것은 포커스 표시가 아니다(저시력 가시성·비인접 대비 모두 미달). 최소선은 `:focus-visible` outline(시각 규격은 terms `focus-ring` 소유). 리셋이 outline 을 지우는 문제와 복구 의무는 [[css-reset]] §2. 반례 실측: `all: unset` 리셋 후 `:focus { background-color }` 만 복구(BK2 3장).\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. 커스텀 위젯을 만들기 전에 APG 에 해당 패턴이 있는지 먼저 확인한다(§0) — 있으면 그 키보드 표가 스펙이다.\r\n2. 모달을 만들면 §1 4항(트랩·반환·초기 포커스·공지) 전부 체크 — 하나라도 빠지면 미완성.\r\n3. 항목형 위젯이면 §2 로 Tab stop 수를 센다 — 위젯당 1개.\r\n4. 포커스 가시성 검증은 실키 Tab 으로 한다 — 프로그램적 focus() 는 :focus-visible 을 안 태워 오탐을 만든다(kg 실측).\r\n5. Radix 등 프리미티브의 기본 비주얼은 흡수 대상이 아니다 — 행동 계약만 가져오고 look 은 토큰 소유.\r\n\r\n## Changelog\r\n\r\n- 2026-08-10: BK2 — §4 에 fieldset/legend 그룹 계약(3)·포커스 표시 시각 최소선(4) 추가. 근거 `research/2026-08-10-bk2-promotion-verdicts.md` V02·V05.\r\n- 2026-08-01: 초판 (M10) — Radix Primitives(Introduction·Accessibility·Dialog·DropdownMenu·Tabs) 실브라우저 캡처 기반."},{"id":"form-fields","title":"Form Fields","description":"Baseline contract every form field must meet: a visible label (placeholder is not a label), a name attribute, POST for credentials/sensitive data, and correct autocomplete tokens","tags":["forms","accessibility","autocomplete","labels"],"source_path":"knowledge/form-fields.md","body":"# Form Fields — 폼 필드 기본 계약\r\n\r\nDate: 2026-08-10\r\nMilestone: BK2 (plan: `plans/2026-08-10-bk2-batch-promotion.md` step-3)\r\n지위: 살아있는 정본. **모든 폼 표면**(recipe·블록·데모·아티팩트)의 필드 최소 계약 — 특정 recipe 종속이 아니라서 knowledge 에 있다. 라벨-컨트롤 연결·이름 계약의 상위 정본은 [[focus-keyboard]] §4, 입력 그룹 묶기(fieldset/legend)도 그쪽 소관 — 이 문서는 필드 하나가 갖춰야 할 나머지를 담는다.\r\n근거: 『인터랙티브 웹 애니메이션』 7장 로그인(반례 4건이 한 파일)·8장 검색 폼(정답 실물) 실측 — `github.com/Youngjin-com/interactive_web@a2dfc627`, 접근 2026-08-08. 장부 `research/2026-08-10-bk2-promotion-verdicts.md` V22.\r\n\r\nAudience: 에이전트(폼 생성 시 필수 참조) + 사람.\r\n\r\n## 계약 4조\r\n\r\n1. **보이는 `<label>` 텍스트 — placeholder 는 라벨이 아니다.** placeholder 는 입력을 시작하면 사라진다 — 그 순간 \"이 칸이 뭐였는지\"를 확인할 방법이 없어진다(길수록·수정할 때일수록). 접근 가능한 이름으로도 계약이 안 된다(보조기술 구현마다 갈림). placeholder 는 형식 예시(`you@example.com`)만.\r\n2. **`name` 필수.** `name` 없는 컨트롤은 제출에서 제외된다 — 폼이 시각적 소품이 된다. 반례 실측: 로그인 폼 전 필드 `name` 부재 → 제출해도 아무 값도 안 나감(7장).\r\n3. **자격증명·민감 데이터는 `method=\"POST\"`.** `method` 생략 = GET = **값이 URL 쿼리스트링에 실린다** — 브라우저 기록·서버 로그·리퍼러에 남는다. 검색처럼 URL 공유가 가치인 폼만 GET(8장 검색 폼이 정답 실물: `method=\"get\"` + `name=\"searchWords\"` + `URLSearchParams` 복원).\r\n4. **`autocomplete` 토큰.** 로그인 `username`+`current-password`, 가입 `new-password`, 그 외 `email`·`name`·`tel` 등. 없으면 비밀번호 관리자가 안정적으로 못 채운다 — **보안 도구를 쓰는 사용자를 밀어내는 결함**이다. 끄기(`off`)는 계약 위반의 명시 사유가 있을 때만.\r\n\r\n## 부속 규칙\r\n\r\n- 비밀번호 표시 토글은 `<button type=\"button\" aria-pressed>` — `<i>` 클릭 리스너 금지([[input-modality]] §3).\r\n- 라디오·체크박스 그룹은 `<fieldset>`+`<legend>` — 제목 태그로 묶으면 접근성 트리에서 안 묶인다([[focus-keyboard]] §4).\r\n- 제출·초기화 버튼에 빈 `<label>` 을 만들지 않는다 — `value`/텍스트가 이미 이름이다(빈 라벨 반례 3회 실측: 2·7·8장).\r\n\r\n## 폼 구성 규칙 (계약 위, 화면 판정)\r\n\r\n위 4조가 필드 하나의 최소 계약이라면 이 절은 **필드를 모아 폼을 만들 때**의 판정이다. 계약은 통과했는데도 쓰기 힘든 폼이 여기서 갈린다.\r\n\r\n1. **필드 수를 먼저 깎는다.** 각 필드에 \"지금 받아야 하는가, 나중에 받아도 되는가, 우리가 이미 아는가\"를 묻는다. 나중에 받아도 되는 것은 첫 제출에서 뺀다. 폼 개선의 대부분은 배치가 아니라 삭제다.\r\n2. **순서는 내부 스키마가 아니라 사용자의 머릿속 순서를 따른다.** DB 컬럼 순이나 관리자 화면 순으로 늘어놓으면 사용자는 맥락을 계속 갈아탄다. 같은 주제끼리 묶고, 묶음마다 왜 필요한지 한 줄로 설명할 수 있어야 한다.\r\n3. **모바일 키보드 타입을 지정한다.** 숫자에는 숫자 키패드, 이메일에는 `@` 가 있는 배열이 뜨게 한다(`inputmode`·`type`). 지정하지 않으면 사용자가 매 필드마다 키보드를 전환한다. 이것은 §계약 4조의 `autocomplete` 와 별개 축이다.\r\n4. **검증 시점은 필드를 떠날 때다.** 타이핑 중에 빨간 글씨를 띄우면 아직 다 치지도 않은 사용자를 비난하게 되고, 제출 시점에 몰아 띄우면 어디가 틀렸는지 찾게 만든다.\r\n5. **제출 실패가 입력을 삼키지 않는다.** 값 보존·포커스 이동·부분 실패 지목은 [[empty-and-error-states]] §4 소관이다.\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n폼을 만들거나 검토할 때 **필드 전수**로: ① 보이는 라벨 텍스트 ② `name` ③ 민감 데이터면 POST ④ `autocomplete` 토큰. 4개가 다 있는가. 하나라도 없으면 그 폼은 \"동작하는 폼\"이 아니라 \"폼처럼 보이는 그림\"일 수 있다. 반례 실측이 정확히 그것이었다(7장: 아이디어는 이 책 최고였는데 폼은 소품).\r\n\r\n이어서 **폼 단위**로: ⑤ 뺄 수 있는 필드를 뺐는가 ⑥ 순서가 사용자 기준인가 ⑦ 모바일 키보드 타입이 맞는가 ⑧ 실패 시 입력이 보존되는가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: M57 — §폼 구성 규칙 신설(필드 수 삭감·입력 순서·모바일 키보드·검증 시점·실패 보존)과 판정 절차 4항 추가. `ux-dictionary@60cbab3` `form-ux` 흡수분이며, 기존 4조가 마크업 계약만 덮고 구성 축은 비어 있던 것을 메운다. 판정 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #2.\r\n- 2026-08-10: 초판 — BK2 V22. 근거 장부 `research/2026-08-10-bk2-promotion-verdicts.md`."},{"id":"graph-content-schema","title":"Graph Content Schema","description":"Graph content schema: independent type axis (7 node types with fixed visual vocabulary) and 3-level hierarchy axis, directed/undirected relation split, provenance.reviewed tracking for machine-generated edges, data-declared overview visibility — harvested from a production knowledge graph","tags":["graph","schema","data-viz"],"source_path":"knowledge/graph-content-schema.md","body":"# Graph Content Schema — 지식 그래프 콘텐츠 스키마\n\n> 지위: 지식·콘텐츠 그래프 화면(연결 시각화, 세컨드브레인, 관계 탐색 UI)을 만들 때의 데이터 스키마 판정 규칙. 출처 = second-brain/poc-graph(brain.askewly.com) 실구현 채굴(harvest, 2026-08-04 — `research/2026-08-04-m22-harvest-mining-ledger.md` ② 카드). 코드 벤더링이 아니라 스키마 계약을 이식한다. 뷰어 코드(three.js 씬·force 어댑터)는 별도 이월(같은 장부 C1).\n\n## 핵심 계약 — 타입 축과 계층 축을 동시에 갖는다\n\n노드는 **분류 축 두 개를 독립으로** 가진다 — 하나로 합치면 \"무엇인가\"와 \"어디에 속하는가\"가 서로를 오염시킨다.\n\n| 축 | 필드 | 값 | 규칙 |\n|---|---|---|---|\n| 타입(무엇) | `type` | 7종: `semantic`(개념)·`reflective`(통찰)·`procedural`(절차)·`episodic`(사건)·`thesis`(주장)·`topic`(주제)·`unclassified`(미분류) | 타입마다 고정 컬러+글리프 매핑(시각 어휘) — `unclassified` 를 정식 타입으로 두어 미분류가 조용히 사라지지 않게 한다 |\n| 계층(어디) | `hierarchy` | 3단 `galaxy > cluster > system` + `role`(`galaxy\\|cluster\\|system\\|star`) | 계층 자체도 노드로 렌더하되 `hierarchy_virtual` 플래그로 실콘텐츠 노드와 구분 — 가상 노드가 콘텐츠 수를 부풀리지 않는다 |\n\n- 노드 정본 = 원문 1개(마크다운 파일)당 1노드. 화면이 아니라 소스 저장소가 정본이고, 그래프 데이터(`graph.json`)는 빌드 산출물이다.\n\n## 엣지 계약 — 관계 종류·방향·검수 추적\n\n| 요소 | 규칙 |\n|---|---|\n| 방향성 관계 | `rel` 11종: supports·extends·requires·refines·instantiates·contradicts·triggered-by·composes·mentions·related·contains — 방향(`dir`) 이 의미의 일부다 (`A extends B` ≠ `B extends A`) |\n| 무방향 관계 | 태그·유사 계열(near-miss·topic-tag·thesis-tag)은 방향 없음으로 분리 — 방향성 관계와 같은 시각 언어를 쓰지 않는다 |\n| 출처 추적 | `provenance: {layer, kind, reviewed}` — **기계 생성 엣지와 사람 검수 엣지를 같은 스키마 안에서 구분**한다. `reviewed:false` 엣지를 지우지 않고 표시만 다르게 |\n| 노출 제어 | `overview_visible`·`overview_emphasis` — 전체 뷰에 다 그리지 않는다. 어떤 엣지가 개요에 나올지는 데이터가 선언 |\n\n## 판정 절차\n\n새 그래프 화면 의뢰 시: ① 타입 축(고정 시각 어휘 포함)과 계층 축을 분리 정의 ② 관계는 방향성/무방향 두 부류로 나누고 방향성 관계에 어휘를 먼저 확정 ③ 기계 생성 연결에는 `provenance.reviewed` 를 처음부터 배선 ④ 개요 노출은 렌더러 휴리스틱이 아니라 데이터 필드로. 물리·레이아웃(force 설정)은 스키마와 독립 관심사 — 이 문서 범위 밖.\n\n## Changelog\n\n- 2026-08-04: 초판 — M24 harvest 배치 3 (B4). second-brain/poc-graph 스키마 채굴 착지."},{"id":"input-modality","title":"Input Modality","description":"Input-modality judgment rules -- never assume the input device or sensory channel: hover dichotomy (decorative vs reach-path), Pointer Events over mouse events, interactive elements must be real buttons/links, and state changes must reach non-visual channels (aria-pressed/checked/expanded)","tags":["input","accessibility","pointer","hover","aria"],"source_path":"knowledge/input-modality.md","body":"# Input Modality — 입력 수단을 가정하지 않는다\n\nDate: 2026-08-10\nMilestone: BK2 (plan: `plans/2026-08-10-bk2-batch-promotion.md` step-3)\n지위: 살아있는 정본. 화면 UI 가 **입력 수단(마우스·터치·펜·키보드)과 감각 채널(시각·보조기술)을 특정하지 않고 작동하는가**를 판정한다. 커스텀 위젯·오버레이의 포커스·키보드 *행동 계약*은 [[focus-keyboard]] 소관 — 이 문서는 그 앞 단계, \"어떤 입력이 오든 도달 가능한가\"의 계약이다.\n근거: 『프론트엔드 개발자를 위한 인터랙티브 웹 애니메이션』(김영민, 영진닷컴) 12장 완주 실측 — 반례 6건 + 29.8KB HTML 의 `aria-` 0건 (`research/2026-08-10-bk2-promotion-verdicts.md` V08, 소스 동결 `github.com/Youngjin-com/interactive_web@a2dfc627`, 접근 2026-08-08~10).\n\nAudience: 에이전트(화면 UI 생성 시 필수 참조) + 사람.\n\n## 0. 대원칙 — 결함은 만든 사람의 화면에 나타나지 않는다\n\n이 문서의 반례 6건은 실력 문제가 아니었다 — 코드 규율이 단단한 저자가 같은 결함을 12장 내내 반복했다. 원인은 하나다: **hover 없는 기기, 마우스 아닌 포인터, 화면을 못 보는 사용자는 개발자의 책상에 없다.** 그래서 이 계약의 각 규칙은 \"잘 만들기\"가 아니라 **\"내 화면에 안 나타나는 사용자를 목록으로 대신하기\"** 다.\n\n## 1. hover 이분법 — 장식과 도달 경로는 처방이 다르다\n\n1. **장식적 hover**(카드 들림·그림자·색 강조 — 없어도 콘텐츠 도달 가능) → `@media (hover: hover)` 로 감싼다. 터치 기기에서 첫 탭이 hover 를 소비하는 끈적임을 막는다.\n2. **도달 경로 hover**(hover 로만 드러나는 콘텐츠·컨트롤) → **대체 경로 의무.** `:hover` 에는 `:focus-within` 을 짝짓고, 터치에는 탭 동작(첫 탭 = 드러내기)을 준다. `(hover: hover)` 가드만으로는 안 풀린다 — 그건 장식을 끄는 장치지 도달 경로를 만들지 않는다.\n3. 판정 질문: **\"hover 를 영원히 못 하는 사용자가 이 내용에 도달할 수 있는가?\"** — 아니오면 그 hover 는 도달 경로다.\n\n## 2. 포인터 — 마우스 이벤트 대신 Pointer Events\n\n1. 드래그·그리기·스와이프류 상호작용은 `mousedown`/`mousemove`/`mouseup` 대신 **`pointerdown`/`pointermove`/`pointerup`** — 마우스·터치·펜을 한 코드로 덮는 상위 집합이다.\n2. **레이아웃 반응성과 입력 반응성은 다른 문제다.** `matchMedia` 로 모바일 레이아웃을 분기했다고 입력을 챙긴 게 아니다 — 반례 실측: 카메라·스케일·타임라인을 전부 `isMobile` 로 분기하면서 드래그는 마우스 전용(11장), 게임 전체가 마우스+키보드 전용에 터치 절 자체가 부재(12장).\n3. 포인터 리스너는 **상호작용 대상 요소에** 단다 — `window` 에 달면 페이지 전체가 조작면이 되어 텍스트 선택·스크롤과 충돌한다.\n4. 터치 타깃 최소 44px — 토큰 `dimension.size.touch-target-min` (기존, `docs/design-system/token-coverage-gen2.md`).\n\n## 3. 조작 대상은 조작 가능한 요소로\n\n1. **`<i>`·`<div>`·`<span>` 에 클릭 리스너를 달지 않는다.** 조작 대상은 `<button type=\"button\">`(동작) 또는 `<a href>`(이동) — 포커스·Enter/Space·role 이 공짜로 따라온다. 반례 실측: 비밀번호 표시 토글이 `<i>` 라 키보드로 누를 수 없음(7장).\n2. 장식 아이콘(옆에 텍스트가 있는 경우)은 `aria-hidden=\"true\"` — 아이콘 폰트·SVG 가 보조기술에 잡음으로 읽히지 않게.\n3. 게임·캔버스류의 키 입력은 `event.preventDefault()` 로 기본 동작(스페이스=스크롤)과 분리한다.\n\n## 4. 이름과 상태 — 둘 다 비시각 채널에 실어야 한다\n\n1. **이름(name)**: 아이콘 버튼은 SVG `<title>`·`aria-label` 로 이름을 준다. (반례의 저자도 이건 일관되게 했다 — \"모바일 메뉴\"·\"찾기\". 이름만으로는 절반이다.)\n2. **상태(state)**: 토글은 `aria-pressed`, 배타 선택은 `role=\"radiogroup\"`+`aria-checked`, 열림/닫힘은 `aria-expanded`, 오버레이는 `role=\"dialog\"`(+ [[focus-keyboard]] §1 계약). **상태 변화가 `class=\"active\"` 라는 시각 전용 채널에만 실리면, 화면을 못 보는 사용자에게 그 변화는 일어나지 않은 것이다.** 반례 실측: 대시보드 29.8KB HTML 에 `aria-` 속성 0건 — 즐겨찾기·뷰 전환·테마 토글 전부 시각 채널 전용(8장).\n3. 판정 질문: **\"방금 누른 결과를 눈 감고 알 수 있는가?\"**\n\n## 판정 절차 (에이전트 의무)\n\n화면 UI 를 만들거나 검토할 때:\n\n1. hover 전수 — 각 `:hover` 가 장식인가 도달 경로인가 분류, 후자에 `:focus-within`/탭 대체 존재 확인.\n2. `mouse*` 리스너 grep — 있으면 Pointer Events 전환 또는 사유.\n3. 클릭 리스너가 달린 비조작 요소 grep(`<i>`·`<div ... onClick>`) — 0 이어야 한다.\n4. 상태를 바꾸는 컨트롤 전수 — `aria-pressed`/`aria-checked`/`aria-expanded` 중 하나가 있는가.\n5. 이 검사들은 기계화 가능하다 — 검사 스크립트 승격은 후속(할 일 문서 B4).\n\n## 반례 코퍼스 (전건 실측 — 규칙별 근거)\n\n| 장 | 반례 | 위반 규칙 |\n|---|---|---|\n| 4 | `<article>` hover 로만 드러나는 텍스트 — 터치·키보드 도달 불가 | §1-2 |\n| 4 | 카드 플립 `:hover` 단독 — Tab 도달해도 무반응 | §1-2 |\n| 5 | 캔버스 페인터 `mouse*` 전용 — 터치·펜에서 무동작 | §2-1 |\n| 7 | 비밀번호 토글 `<i>` — 키보드 조작 불가 | §3-1 |\n| 8 | `aria-` 0건 — 상태가 시각 채널 전용 | §4-2 |\n| 11·12 | 모바일 레이아웃 분기하면서 입력은 마우스 전용 / 게임에 터치 부재 | §2-2 |\n\n(6장 `@media (hover: hover)` 는 같은 저자의 **부분 자가수정** — 장식 hover 에는 정답, 도달 경로에는 무효. §1 이분법의 근거.)\n\n## Changelog\n\n- 2026-08-10: 초판 — BK2 V08 (V20·V23·V26·V42·V47 합병). 근거 장부 `research/2026-08-10-bk2-promotion-verdicts.md`."},{"id":"landing-section-rhythm","title":"Landing Section Rhythm","description":"Landing section rhythm: alternating background bands (base/warm/ink) as scroll pacing, single ink band on the process section, identical explanation hierarchy across sections — harvested from a production landing","tags":["landing","layout","rhythm","marketing"],"source_path":"knowledge/landing-section-rhythm.md","body":"# Landing Section Rhythm — 랜딩 섹션 리듬 (밴드 교차)\n\n> 지위: 설명형 랜딩(제품·서비스 소개)의 섹션 시퀀스 판정 규칙. 출처 = upstage-sixsense(sixsense.askewly.com) 랜딩 실구현 채굴(harvest, 2026-08-04 — `research/2026-08-04-m22-harvest-mining-ledger.md` ⑬ 카드).\n\n## 계약 — 배경 밴드가 리듬을 만든다\n\n섹션 시퀀스는 내용 순서이자 **배경 톤의 교차**다. 실측 시퀀스:\n\n```\n히어로(기본 지면 + 인터랙티브 데모/증거 카운터)\n→ band-warm : 대비 카드 (문제 vs 해답 — contrast-duo-card)\n→ band-ink : 프로세스 스테퍼 (3단계 — 어두운 밴드가 \"방법\"을 분리)\n→ band-warm : 참여/제보 CTA\n→ closing : 예산 칩 등 마지막 행동 유도\n→ footer\n```\n\n| 규칙 | 근거 |\n|---|---|\n| 인접 섹션은 배경 톤을 교차한다 (기본→warm→ink→warm→기본) | 스크롤 위치 감각 — 단색 연속이면 어디까지 읽었는지 사라진다 |\n| 어두운 밴드(ink)는 1곳, \"방법/프로세스\"에 배정 | 가장 밀도 높은 정보에 가장 강한 분리 |\n| 설명 블록 위계는 전 섹션 동일: muted 라벨 → 볼드 헤드라인 → 보조 설명 | 반복되는 위계가 리듬을 읽히게 한다 (contrast-duo-card 와 동일 원리) |\n| 히어로에 실물 증거 1개(데모 카드·카운터)를 넣는다 | 주장보다 관측 — 첫 화면에서 제품이 움직이는 것을 보인다 |\n\n## 판정 절차\n\n랜딩 의뢰 시: ① 섹션 목록을 먼저 톤(기본/warm/ink)으로 배정 ② ink 1곳 초과면 재검토 ③ 설명 블록 위계 3단을 전 섹션에 강제. 조합 원리는 `research/2026-07-19-st4-composition-patterns.md`(순서·비중이 유형을 가른다)과 상보.\n\n## Changelog\n\n- 2026-08-04: 초판 — M23 harvest 배치 2 (B3). sixsense 랜딩 실구현 채굴 착지."},{"id":"mobile-navigation","title":"Mobile Navigation","description":"Mobile navigation & modality container selection rules (Apple HIG x Material 3): one primary nav per screen with a destination-count/breakpoint decision table (nav drawer deprecated by M3 Expressive — prefer expanded rail), nav behavior contract (no swipe switching, preserve-vs-reset state, no hidden tabs), modal surface choice by task weight (snackbar/dialog/modal sheet/full-screen dialog/detents), and the modal-depth-of-one rule","tags":["mobile","navigation","modality","hig","material"],"source_path":"knowledge/mobile-navigation.md","body":"# Mobile Navigation & Modality — 컨테이너 선택 판정 규칙\r\n\r\nDate: 2026-08-01\r\nMilestone: M7 (plan: `plans/2026-08-01-m7-platform-guideline-absorption.md`)\r\n지위: 모바일 화면에서 내비게이션 컨테이너와 모달 표면을 고르는 판정 규칙의 정본. 근거 원문은 `research/2026-08-01-m7-mobile-nav-sheets-capture.md` 에 동결(출처 URL·접근일 포함) — 이 문서는 판정 규칙만 담고 원문을 재서술하지 않는다. Apple HIG × Material 3 를 대조해 플랫폼 공통 규칙과 분기 지점을 가른다.\r\n\r\nAudience: 에이전트(모바일 화면 생성 시 필수 참조) + 사람.\r\n\r\n## 1. 주 내비게이션 컨테이너 선택\r\n\r\n**화면당 주 내비게이션 컴포넌트는 1개다.** drawer + bottom bar 동시 사용은 안티패턴.\r\n\r\n| 조건 | 컨테이너 | 근거 |\r\n|---|---|---|\r\n| 최상위 목적지 3~5개 · compact(폰) | bottom navigation / tab bar | M3 3~5 규칙 · HIG 탭바 |\r\n| 목적지 3개 미만 | 페이지 내 tabs (주 내비 아님) | M3 — nav bar 금지 |\r\n| 목적지 5개 초과 · medium+ | navigation rail (확장형) 또는 사이드바 전환 | M3 Expressive · HIG sidebarAdaptable |\r\n| 목적지 5개 초과 · compact | 탭 커스터마이즈(기본 ≤5) 또는 rail 로 승격 | HIG iPadOS 커스터마이즈 |\r\n| 데스크톱/expanded | rail·사이드바 — bottom bar 금지 | M3 |\r\n| **navigation drawer 신규 채택** | **비권장** — 확장형 rail 이 대체 (M3 Expressive, 2026-08-01 확인) | 기존 제품 유지만 허용 |\r\n\r\n## 2. 주 내비게이션 행동 계약\r\n\r\n1. **탭 전환은 탭/클릭만** — 좌우 스와이프로 목적지를 전환하지 않는다(스와이프는 카드 캐러셀·행 액션 몫).\r\n2. **상태 계약을 명시적으로 정한다** — preserve(스크롤·하위 탭·검색 유지) vs reset. 섹션을 자주 오가는 앱은 preserve.\r\n3. **활성 목적지 재선택 = 최상단 스크롤.**\r\n4. **탭 숨김·비활성 금지** — 내용이 없으면 빈 섹션에 이유를 보여준다.\r\n5. **라벨 필수** — 1~2 단어, 줄바꿈·축소·제거 금지. 활성 표시는 1개만.\r\n6. **스크롤 숨김은 스크린리더 활성 시 끈다.** FAB 는 바 위에, 바를 가리지 않는다.\r\n7. **배지는 critical 정보만** — 남용하면 의미가 죽는다.\r\n\r\n## 3. 모달 표면 선택 (태스크 무게로 가른다)\r\n\r\n| 태스크 | 표면 | 핵심 계약 |\r\n|---|---|---|\r\n| 저중요 알림 (액션 선택적) | snackbar/toast | 비차단·자동 소멸 — dialog 금지 |\r\n| 고중요 결정·확인 | dialog | 액션 ≤2(확인 트레일링) · 제목은 구체 질문 · 제3의 이동 버튼 금지 |\r\n| 맥락 밀착 짧은 선택·목록 | modal bottom sheet | 초기 높이 ≤50% · scrim 탭/스와이프 dismiss · 초과분은 풀확장+내부 스크롤 |\r\n| 메인 UI 와 병행 사용 | standard bottom sheet (nonmodal) | scrim 없음 · 동시 상호작용 (예: 미니 플레이어) |\r\n| 키보드 폼·비즉시 저장·다단 작성 | full-screen dialog (compact 전용) | 확인 = 구체 동사(Send/Create) · 비활성화 금지 · 미저장 닫기 확인 · 큰 화면은 basic dialog 로 스왑 |\r\n| 리사이즈 가능한 시트 | detent + grabber | medium detent = 점진 공개 · compose 류는 full-height 만 · grabber 가 스크린리더 리사이즈 경로 |\r\n\r\n## 4. 모달 깊이 규칙\r\n\r\n- **모달 깊이 = 1.** 시트 위에 시트를 쌓지 않는다 — 첫 시트를 닫고 다음을 연다.\r\n- 예외 2개뿐: ① alert 는 모든 것 위에 뜰 수 있다(동시 1개만) ② full-screen dialog 위에는 basic dialog(미저장 확인·실패 통보)만 허용.\r\n- 해제 경로는 항상 2개 이상(버튼 + 스와이프/scrim) + 데이터 유실 위험 시 확인 단계.\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. 모바일 화면을 만들기 전에 목적지 수·브레이크포인트로 §1 표에서 컨테이너를 정한다 — \"모바일이니까 햄버거 메뉴\"는 금지 수(§1 마지막 행).\r\n2. 오버레이가 필요하면 §3 표에서 태스크 무게로 표면을 정하고 §4 깊이 규칙을 검사한다.\r\n3. 규칙과 다른 선택을 하면 그 이유를 산출물에 한 줄 남긴다(플랫폼 규범 이탈은 의도여야 한다).\r\n4. 스타일(Liquid Glass·M3 색 역할·인디케이터 모양)은 흡수 대상이 아니다 — look 은 프로젝트 토큰 소유.\r\n\r\n## 집행 경로\r\n\r\n이 문서의 판정 규칙은 자산에 대해 **기계로 집행된다** — `node scripts/validate-mobile-assets.mjs`(= `npm run gate:mobile`)가 `surface_refs: mobile-apps` 자산을 390×844 에서 렌더해 §4 모달 깊이를 포함한 다섯 축을 잰다. 자산이 매이는 조건과 예외는 `docs/design-system/pattern-taxonomy.md` §2 「모바일 표면 자산이 충족해야 하는 것」. (M72, 2026-08-19)\r\n\r\n## Changelog\r\n\r\n- 2026-08-01: 초판 (M7) — HIG(tab-bars 2026-06-08판·sheets·modality) × M3(navigation-bar·bottom-sheets·dialogs·navigation-drawer) 실브라우저 캡처 기반. M3 Expressive drawer 비권장 반영."},{"id":"motion-principles","title":"Motion Principles","description":"Motion quality judgment rules — read BEFORE picking a tier or tool: still-appeal gate (motion cannot rescue a weak still frame), transition purpose taxonomy (continue/create/replace/compare/focus/confirm), one focal element per entrance, motion typography readability, and naming rules (user-facing behavior first, CSS technique second)","tags":["motion","quality","judgment"],"source_path":"knowledge/motion-principles.md","body":"# Motion Principles — 모션 품질 판정 규칙\n\nDate: 2026-07-28\nMilestone: VI6 (plan: `plans/2026-07-28-vi6-knowledge-consolidation.md`)\n지위: 살아있는 정본. \"이 모션이 좋은가\"를 판정한다 — \"무엇으로 만드나\"(티어·도구)는 [[expressive-stack]] 소관이며, 두 문서는 순서가 있다: **원칙 먼저, 도구 나중.**\n근거: knowledge-graph 노드 4+1건 흡수 (`research/2026-07-28-vi6-kg-crosswalk.md` 판정 #2~#5·#10). 각 절에 KG 원본 경로 병기 — 원 출처(remotion-data ANALYSIS 등)는 KG 노드 frontmatter 가 보유.\n\n## 1. Still appeal 게이트 — 모션은 약한 정지 화면을 못 구한다\n\n애니메이션 전에 **정지 프레임이 먼저 통해야 한다**: 타이포·구성·실루엣·대비가 정지 상태로 읽히지 않으면 모션을 얹어도 실패다. 중요한 프레임마다 답한다 — 눈이 먼저 읽는 것은 무엇인가 · 두 번째는 · 배경으로 물러나도 되는 것은 · focal anchor 는 어디인가 · 대비가 한 곳에 집중되는가 · beat 이 안착할 여백이 있는가.\n\n- 씬당 승리 요소는 하나. 고채도·고휘도 강조도 하나. 비활성·배경 요소는 대비·채도·선명도를 낮춘다.\n- 등장 순서는 위계와 일치시킨다 — 먼저 등장하는 것이 primary 로 읽힌다.\n- 실무 진단: **그레이스케일 + 블러 리뷰**에서 의도한 primary 가 사라지면 위계가 약한 것이다 — 애니메이션 전에 고친다.\n\n(KG `nodes/디자인/motion-styleframe-visual-hierarchy.md` · `nodes/디자인/motion-animation-principles-product-motion.md`, 접근일 2026-07-28)\n\n## 2. 목적 먼저, 효과 나중 — 전환의 의미 6분류\n\n전환 효과(fade/slide/match/morph/crop/stagger/shared element)를 고르기 전에 **전환의 목적**을 먼저 고른다:\n\n| 목적 | 뜻 |\n|---|---|\n| continue | 같은 흐름의 지속 |\n| create | 새 것의 진입 |\n| replace | 상태 교체 |\n| compare | 두 상태 비교 |\n| focus | 주목 이동 |\n| confirm | 결과 확인 |\n\n- 복잡한 전환에서는 **shared/focal 요소 하나**를 안정적으로 유지하고 나머지가 그 주변에서 출입하게 한다 — shared element 가 많거나 경로가 교차하면 관계가 안 읽힌다.\n- 지속시간은 프레임 수가 아니라 목적·이동 거리·표면적 변화로 정한다 (짧게/표준/확장). [[expressive-stack]] 규칙 6(전환 규모 비례)의 상위 판단이다.\n\n(KG `nodes/디자인/motion-semantic-transition-design.md`, 접근일 2026-07-28)\n\n## 3. 등장 안무 — 한 번에 하나\n\n- 한 번에 **하나의 focal element** 만 등장시킨다. 동시 등장은 의도된 예외로만.\n- 관계를 명확히 할 때만 보조 요소가 지연·정착(follow-through)하게 한다.\n- 이징 기본값은 명시적 easing 또는 스프링 — **linear motion 은 이유 있는 예외**다.\n- 타이밍은 톤에 맞춘다: 차분한 제품·코퍼레이트는 정제된 페이싱, 프로모·액션은 빠르게.\n\n(KG `nodes/디자인/motion-animation-principles-product-motion.md`, 접근일 2026-07-28)\n\n## 4. 움직이는 텍스트의 가독성\n\n텍스트 비중이 큰 모션(슬라이드·모션 그래픽·히어로 타이포)은 시간 압박 아래의 커뮤니케이션이다:\n\n- role map(headline/support/label/caption/CTA)과 read order(first/second/last)를 정하고, 모든 텍스트를 동시에 애니메이션하지 말고 **의미 우선순위로 스태거**한다.\n- 텍스트가 빠르게 움직이면: 카피를 줄이거나 · 크기를 키우거나 · 자간을 넓히거나 · 블러를 줄인다.\n- 보이스오버 주도가 아니면 **음소거 리뷰**로 텍스트만으로 전달되는지 확인한다.\n\n(KG `nodes/디자인/motion-typography-readability.md`, 접근일 2026-07-28)\n\n## 5. 호명 규칙 — 동작을 먼저, 기법을 나중에\n\n스펙·코드 리뷰·지식 문서에서 모션을 서술할 때 **사용자가 보는 동작을 먼저 이름 짓고, CSS 기법을 그다음에** 짓는다.\n\n- 구현 언어(스펙·리뷰용): disclosure, collapsible region, expand/collapse, fade, clipping, layout interpolation, pointer guard, focus guard, ARIA state.\n- 제품 언어(사용자 대면 카피용): \"open details\", \"show filters\", \"hide completed items\" — 두 언어를 섞지 않는다.\n\n(KG `nodes/개발/ui-state-motion-vocabulary.md`, 접근일 2026-07-28)\n\n## 6. 미리보기는 실시간이 아니어도 된다\n\n목록·탭·작업 표시줄에서 **내용을 열지 않고 미리 보여 주는** 썸네일은 실시간 프레임률로 그릴 이유가 없다. 사용자가 그 순간 판단하는 것은 \"저 안에서 무엇이 움직이는가\"이지 정확한 프레임이 아니다.\n\n- **15fps 로 충분하다** (관측: daedalOS 작업 표시줄 피크 — `research/m60-round6/p2-daedalos.json` 행 `r6-18`). rAF 루프를 그대로 돌리지 말고 프레임을 솎는다.\n- **최대 폭을 고정하고 높이를 비례로** 잡는다. 원본 크기를 그대로 캡처하면 미리보기 하나가 본 화면보다 비싸진다.\n- 캡처 대상에서 의미 없는 노드(소스 요소 등)는 필터로 뺀다.\n- 미리보기가 사라질 때 루프를 반드시 멈춘다 — 보이지 않는 썸네일이 계속 그려지는 것이 이 패턴의 기본 실패 모드다.\n\n정지 화면으로 충분한지 먼저 묻고, 움직임이 판단에 필요할 때만 라이브로 간다. 흡수 판정은 **B(축소이식)** 였다 — 원본은 `html-to-image` 의존이라 그대로 옮기면 무겁고, 옮길 것은 구현이 아니라 위 판정이다.\n\n## 7. 사이트 하나의 이징은 두셋이다\n\n커브를 매번 새로 고르면 리듬이 생기지 않는다. 사이트의 모션이 하나의 감속감으로 읽히는 것은 **커브를 등록해 두고 부르기 때문**이다.\n\nPartizan 실측(2026-08-17, `research/m67-batch2-css/p2-ledger.json` 행 `pz2-08`) — 브랜드 이름으로 CustomEase **3종**을 등록하고 전 모션이 그것만 참조한다. 사용 빈도가 하나로 쏠린다: `alpha` 68회 · `inOut` 22회 · `inOut2` 2회. 즉 **지배 커브 하나 + 보조 하나 + 특수 하나**다.\n\n같은 배치의 다른 두 곳도 습관은 같고 값만 다르다 — Made With Gsap 은 `cubic-bezier(.9,0,.1,1)` 과 CSS `linear()` 스프링을 반복하고, NORMAL IS BORING 은 `power3.inOut`·`power3.out`·`power2.out` 셋이 사실상 전부다.\n\n- **「이징을 몇 개 둘 것인가」가 「어느 커브인가」보다 상위 결정이다.** 후자는 취향이고 전자는 시스템이다.\n- 새 연출에 새 커브가 필요하다고 느끼면, 커브가 부족한 것이 아니라 **duration 이나 stagger 로 풀 문제**일 때가 많다.\n- 토큰으로 등록하면 이름이 곧 용도가 된다 — recipe 마다 커브를 따로 적는 지금 방식은 이 규칙이 없는 상태다.\n\n## 8. 전환은 걷어내는 쪽이 절반이다\n\n들이는 연출만 설계하고 걷어내는 쪽을 두지 않으면, 라우터 전환이 반복될수록 이전 화면의 애니메이션이 살아남아 쌓인다. 화면에는 안 보이고 프레임만 먹는다.\n\nMade With Gsap 실측(행 `mwg-12`) — 이탈마다 ① `ScrollTrigger` 전건 kill ② `<main>` 하위를 타깃으로 가진 트윈만 **선별** kill ③ DOM 교체 ④ 커튼 타임라인만 재사용. 전역 `killAll()` 이 아니라 **범위를 좁힌 kill** 인 것이 핵심이다 — 전역으로 죽이면 같은 페이지의 다른 연출까지 죽는다.\n\n- **정리 코드는 값어치가 화면에 나타나지 않는다.** 그래서 빠뜨리기 쉽고, 빠뜨린 것도 한동안 안 보인다. [[page-transition-modes]] 는 그 값어치를 「살아 있는 애니메이션 수」 계기로 드러낸다.\n- 적용처: [[curtain-page-transition]]·[[view-transition-language-crossfade]]·[[shared-element-flip-transition]] — 전환을 소유한 recipe 는 언마운트 규율을 함께 갖는다.\n\n## 9. 겹치는 동작은 구간이 아니라 이징이 잇는다\n\n\"A 가 끝나기 전에 B 가 가로채듯 들어온다\"를 **구간을 겹치는 것**으로만 지시하면 여전히 \"끝나고 나서 시작\"으로 보인다. 앞으로 몰린 이징(`cubic-bezier(0.16, 1, 0.3, 1)` 같은 강한 ease-out)은 **구간의 30% 지점에서 사실상 끝나서**, 70% 를 겹쳐 놓아도 실제로 겹친 것은 눈에 안 보이는 잔여뿐이기 때문이다.\n\n§7 의 \"두셋\"을 용도로 가른다:\n\n| 용도 | 성질 | 실측값 |\n|---|---|---|\n| 단발 동작 | 앞으로 몰린다 | `bezier(0.16, 1, 0.3, 1)` |\n| 겹치는 동작 | 중간에 무게가 실린다 — 옆 동작과 손이 맞는다 | `bezier(0.38, 0.02, 0.28, 1)` |\n| 훑고 지나가는 경계선 | 거의 균등 | `bezier(0.35, 0.15, 0.45, 0.95)` |\n\n- **경계선에 S자 이징을 쓰지 않는다.** wipe·`clip-path` 로 여는 연출에 S자를 걸면 드러남이 창의 가운데 몇 프레임에 몰려 \"켜졌다 꺼진 것\"으로 읽힌다. 지나가는 경계선은 일정한 속도로 가야 경계선으로 읽힌다.\n- **훑기의 이동 폭은 상자에 딱 맞춘다.** 범위가 상자(0~100%, 기울기가 있으면 그만큼 더)보다 넓으면 구간 앞부분만 쓰이고 나머지는 이미 다 드러난 채로 흘러간다 — 훑기가 순식간에 끝난 것처럼 보이는 원인이다.\n\n(KG `nodes/디자인/motion-overlap-needs-easing-not-just-range.md`, 실측 2026-09-13)\n\n## 10. 함께 보일 것은 함께 움직인다 — 부품을 따로 안무하지 않는다\n\n로고 락업·카드 묶음·라벨 붙은 도형처럼 **최종적으로 한 덩어리로 읽혀야 하는 것**은, 부품마다 좌표를 계산해 각자 움직이게 짜지 않는다. 최종 배치대로 부품을 담은 **상자 하나**를 움직이고, 부품은 상자 안에서 프레임과 무관한 상수 좌표를 갖고 **불투명도만**으로 등장한다.\n\n- 마지막 프레임이 원본 배치와 기하적으로 같아진다 — 배치를 두 군데에서 계산하지 않으니까.\n- 부품끼리 겹치는 일이 **구조적으로 불가능**해진다. 원본 배치의 간격이 곧 둘 사이의 거리다.\n\n**따로 움직이고 있다는 신호**: 등장하는 부품이 다른 부품 뒤에서 배어 나온다 → z-order 를 DOM 순서로 잡게 된다 → 겹침을 막으려고 `clip-path` 로 한쪽을 잘라 연다. 셋째는 훑기 연출처럼 보이지만 땜질이다. 실측 사례에서 상자 하나로 합치자 셋이 전부 코드에서 사라졌다.\n\n**함정** — 상자의 배율과 이동을 서로 다른 램프로 몰 때(겹치는 연출에서는 그래야 한다), 부품의 화면 좌표는 `x_box + x_local · s` 다. `x_box` 의 출발점을 상수로 두면 축소가 진행되는 동안 부품이 한쪽으로 **흘러간다**. 출발점을 현재 배율에 묶는다 — `x_box = lerp(p, -x_anchor * s, 0)`. 코드를 읽어서는 잘 안 보이고 **중앙선을 그려 넣은 스틸 대조**로 잡힌다.\n\n(KG `nodes/디자인/motion-composite-moves-as-one-unit.md`, 실측 2026-09-13)\n\n## 11. 크기는 변환으로 바꾼다 — 레이아웃 크기를 애니메이션하면 래스터가 깨진다\n\n브라우저가 그리는 모션에서 요소를 크게 보였다 작게 보이게 하는 길은 둘이다 — 레이아웃 크기(`width`/`height`)를 바꾸거나 `scale` 변환을 걸거나. 좌표 결과는 같지만 **래스터 품질은 같지 않다.** 레이아웃 크기를 프레임마다 바꾸면 브라우저가 매 프레임 새 크기로 다시 래스터화하는데, 그 경로는 정지 이미지 한 장을 리샘플링하는 경로가 아니라 빠른 쪽이다. 가장자리가 계단으로 보인다.\n\n- 상자는 그 요소가 애니메이션 동안 갖는 **최대 크기**로 고정하고 `scale` 로만 줄인다 — 모든 구간이 축소 경로가 되고 확대 경로는 아예 생기지 않는다.\n- 래스터 원본은 최대 표시 크기보다 크게 둔다(LANCZOS 초해상 등). 없던 디테일이 생기지는 않지만 확대 경로를 막는다.\n- 변환은 문자열 조립 대신 개별 속성(`translate`·`scale`·`rotate`)으로 — `docs/design-system/video-contract.md` §Studio 편집 가능 작성 규칙과 같은 처방이다.\n\n**진단 신호**: 같은 화면의 두 요소 중 **크기가 변하는 쪽만** 깨진다. 자산 품질이 원인이면 둘 다 깨져야 한다. 한쪽만이면 원인은 자산이 아니라 그 요소에만 걸린 애니메이션 방식이다 — 실측 사례에서는 이 비대칭을 읽지 못해 윤곽 추적·평활화·벡터 재작도·초해상으로 오진이 넷 쌓였다. 자산을 의심하기 전에 **깨지는 것과 멀쩡한 것의 차이**를 먼저 적는다. 확인은 스틸을 NEAREST 로 4배 확대해서 한다(스튜디오 프리뷰는 한 번 더 스케일을 먹으므로 판정에 쓰지 않는다).\n\n관련: 자산 자체가 손상된 사본일 가능성은 그 전에 거른다 — KG `nodes/디자인/damaged-asset-diagnose-source-before-restoring.md`.\n\n(KG `nodes/디자인/motion-animate-transform-not-layout-size.md`, 실측 2026-09-13)\n\n## 12. 정체가 바뀌는 전환은 단색 구간을 다리로 놓는다\n\n전환은 둘로 갈린다 — **같은 것이 움직이는** 전환과 **무엇인가가 다른 것으로 바뀌는** 전환. 뒤쪽(여러 개가 하나가 되거나, 비율이 바뀌거나, 내용이 통째로 갈리는 것)은 중간 프레임이 정직하게 그려질수록 어색해진다. 어느 프레임에도 \"지금 무엇인가\"라는 답이 없기 때문이다.\n\n전환하는 대상들을 흰 면으로 덮어 개성을 지우고, 모두가 똑같은 흰 판이 된 프레임에서 교체한 뒤, 새 대상에서 흰 면을 걷는다. 관객은 \"하얘졌다가 다시 나타났다\"로 읽고 그 사이에 몇 장이 몇 장이 되었는지 묻지 않는다.\n\n- **다리는 동작의 앞 절반 안에 완성한다.** 단색화가 느리면 대상들이 겹치기 시작할 때 아직 반투명이라 서로의 윤곽·텍스트가 비쳐 — 가리려던 것이 오히려 강조된다. 실측에서 50프레임을 22프레임으로 줄이자 사라졌다.\n- **상자 밖에 그려지는 것은 단색 면이 못 덮는다.** `box-shadow` 가 대표다. 하얗게 만든 카드 뒤에 검은 후광이 원래 개수만큼 남는다. 그림자·외곽 글로우는 단색화와 **같은 램프로 따로** 걷고, 새 대상의 그림자는 내용이 돌아올 때 같이 돌려준다.\n- **교체는 양쪽이 같은 자리·같은 크기·같은 색인 프레임 안에서** 한다. 그 조건이 갖춰지면 교체에 이징이 필요 없다 — 오히려 이징을 걸면 단색 구간의 길이가 흔들려 교체가 구간 밖으로 삐져나온다. 다리 위의 값은 **선형**이다.\n- 부수 효과: 다리 구간의 중간 프레임이 설계 대상에서 빠진다. 전환을 프레임 단위로 다듬는 대신 **경계 두 개**를 지키는 문제가 된다.\n\n(KG `nodes/디자인/motion-blank-bridge-for-identity-change.md`, 실측 2026-09-13)\n\n## 13. 군더더기는 길이가 아니라 관객이 이름 붙일 수 있는 단계 수다\n\n전환이 늘어져 보일 때 길이를 줄여도 답답함이 남는다면, 원인은 **\"이제 ○○합니다\"라고 이름 붙일 수 있는 단계가 하나 더 있다**는 것이다. 전환을 보며 그 문장을 몇 번 말할 수 있는지 센다.\n\n실측(세 조각 → 한 카드)에서는 넷이 나왔고 가운데 둘이 군더더기였다:\n\n| | 단계 | |\n| --- | --- | --- |\n| 1 | 이제 모입니다 | |\n| 2 | 이제 포개진 것이 정렬됩니다 | ← 군더더기 |\n| 3 | 이제 커집니다 | ← 군더더기 |\n| 4 | 이제 내용이 나타납니다 | |\n\n둘 다 **구현하기 편해서 생긴 단계**였다 — 크기가 다른 조각을 작은 자리에 모으는 쪽이 계산이 쉬웠고, 포갠 티를 내려고 엇갈리게 두었다가 다시 맞춰야 했다. 관객에게는 목적이 없는 동작이다.\n\n- **고치는 법은 중간 목표를 없애는 것**이다. 「작은 자리에 모았다가 키운다」와 「엇갈려 놓았다가 맞춘다」는 각각 목표를 둘로 쪼갠 것이다. 처음부터 최종 자리·최종 크기로 보내면 단계가 하나로 합쳐진다. 같은 프레임 수를 써도 군더더기가 사라진다.\n- ⚠ **겹침은 단계가 아니다.** 동작 둘을 겹치는 것(§9 가로채기)은 단계를 늘리지 않고 오히려 하나의 흐름으로 읽히게 만든다. 세는 대상은 「끝나고 나서 시작하는 것으로 읽히는 단계 수」다. 이어지는 두 동작을 관객이 둘로 센다면 단계가 많은 게 아니라 **겹침이 모자란 것**이다.\n\n(KG `nodes/디자인/motion-count-nameable-beats.md`, 실측 2026-09-13)\n\n## 관련\n\n- 미시 상호작용 프레임(trigger/rules/feedback/loops/modes)으로 작은 순간의 실패를 진단하려면: KG `nodes/책/book-microinteractions.md` (규칙 중복 없음 — 링크만).\n- 티어·도구 판정과 접근성 짝규칙: [[expressive-stack]] §판정 절차.\n- 외부 데모·사례 북마크: [[motion-references]].\n\n## Changelog\n\n- 2026-09-13(2): §12(정체가 바뀌는 전환은 단색 구간을 다리로 놓는다)·§13(군더더기는 관객이 이름 붙일 수 있는 단계 수다) 추가. 같은 영상의 S3 합체 연출을 여섯 라운드 고치며 나온 규칙 — KG 노드 2건 흡수.\n- 2026-09-13: §9(겹치는 동작은 구간이 아니라 이징이 잇는다)·§10(함께 보일 것은 함께 움직인다)·§11(크기는 변환으로 바꾼다) 추가. MABC 데모 영상 인트로 로고 락업 실측(Remotion 4.0.520) — KG 노드 3건 흡수. 매체 공통 규칙만 옮기고 Remotion API 는 `docs/design-system/video-contract.md` 에 둔다.\n- 2026-08-18: M67 배치 2 회차 — §7(사이트 하나의 이징은 두셋이다)·§8(전환은 걷어내는 쪽이 절반이다) 추가. 세 사이트 이징 운영 실측과 라우터 teardown 관측. 흡수 판정 A — 값이 아니라 운영 규칙만 옮긴다.\n- 2026-08-17: §6 「미리보기는 실시간이 아니어도 된다」 추가 (M60 회차 6 daedalOS 처분 K2 — 작업 표시줄 라이브 썸네일 15fps 관측). 흡수 판정 B: 구현이 아니라 프레임률·크기 판정만 옮긴다.\n- 2026-07-28: 초판 (VI6 — KG 모션 원칙 노드 4건 + 어휘 노드 1건 흡수)."},{"id":"motion-references","title":"Motion References","description":"External motion/interaction reference bookmarks: GSAP ScrollTrigger demo hubs, spatial 3D landing pattern with its decorative-skin failure gate, observation galleries (60fps.design, landing.love), and the media boundary note for pre-rendered video (Remotion)","tags":["motion","references","bookmarks"],"source_path":"knowledge/motion-references.md","body":"# Motion & Interaction References\r\n\r\n> 인터랙티브 UI를 만들 때 참고하는 외부 자료 북마크. 코드 라이브러리 클론은 toolshelf(`~/projects/toolshelf`)에 있고, 여기엔 레포가 아닌 기법·데모 레퍼런스를 남긴다.\r\n\r\n## 스크롤 내러티브 (scroll-driven storytelling)\r\n\r\n- **GSAP + ScrollTrigger** — 스크롤 핀·스크럽·패럴랙스·단계적 리빌 기법의 가장 깊은 데모 라이브러리.\r\n - 데모 허브: `https://gsap.com/scroll/` · `https://demos.gsap.com/plugin/scrolltrigger/` · CodePen 컬렉션 `https://codepen.io/collection/DkvGzg`\r\n - 라이선스: GSAP 코어 + 전 플러그인(ScrollTrigger 포함)이 2024년 Webflow 인수 이후 **모든 용도 무료**(상업 포함). 의존성으로 추가하기 전 `gsap.com/pricing`에서 현재 약관 재확인.\r\n - 용도: showcase 카드 4 (Scroll-Driven Narrative) 기법 참고. 레포가 아니라 데모 갤러리라 toolshelf에 클론하지 않고 여기 북마크. 우리 모션 primitive로 재구현하거나, 무료화됐으니 `gsap` 직접 추가 중 택.\r\n - 접근일: 2026-07-07\r\n\r\n## 공간형 랜딩 (Three.js walkable/spatial hero)\r\n\r\n포트폴리오·랜딩을 평면 목록이 아니라 **장소**(방·도시·캠퍼스)로 모델링하는 임팩트 패턴. 실사례: Bruno Simon Folio 2025 · Sooah's Room Folio · My Room in 3D · Repolis(레포→3D 도시) 등 — 사례 장부와 실측 리포트는 KG `nodes/디자인/spatial-repo-portfolio-agentic-wayfinding-pattern.md` + toolshelf `3d-spatial-landing-reference-pack`(8사이트) (접근일 2026-07-28).\r\n\r\n**채택 게이트 (decorative-skin failure)**: 3D 가 데이터 위의 장식 스킨이면 실패다 — ① 정보 구조(구역·경로) ② 시각 위계(랜드마크·스케일·조명) ③ 증거 접근(각 목적지에서 실제 콘텐츠 열람)의 3층이 전부 의도적으로 매핑될 때만 공간 메타포가 정당하다. 첫 제품 형태는 도시보다 **룸 스케일**이 적합할 수 있다. LLM 없는 non-agent 폴백(로컬 검색·결정적 라우팅)을 유지한다.\r\n\r\n## 영상 매체 (Remotion) — 매체가 다르다, 계약은 `video-contract.md`\r\n\r\n사전 렌더 영상은 화면 UI 와 게이트가 다르다(프레임 결정론·스틸·프레임 시퀀스 검증). **2026-09-02 M95 부터 이 레포의 4번째 매체다** — 게이트·무대·Element 재흥 규칙·라이선스·관측 경로는 `docs/design-system/video-contract.md` 가 소유하고, 자산은 `examples/ui-vocabulary-site/src/video/elements/` 에 산다(구 「범위 밖 — 링크만」 문구는 이 날 개정). KG 원본은 원리 참조용으로 유지한다 (접근일 2026-07-28):\r\n\r\n- `nodes/디자인/motion-remotion-animation-pattern-library.md` — 프리미티브 패턴 조직법\r\n- `nodes/디자인/motion-remotion-scene-spec-workflow.md` — 씬 스펙·컴포넌트 레지스트리·렌더 루프\r\n- `nodes/디자인/motion-remotion-official-resource-selection.md` — 공식 리소스 역설계 루프\r\n- `nodes/디자인/motion-remotion-frame-render-guardrails.md` — 프레임 기반·CSS 애니메이션 배제 가드레일\r\n\r\n단, 모션 **원칙**(still appeal·목적 6분류·타이포 가독성)은 매체 공통 — [[motion-principles]] 가 정본.\r\n\r\n## 타격감·붕괴 (역설계 실측 3건 — M58 2026-08-16)\r\n\r\n수치 원문과 처분은 `research/2026-08-16-m58-carryover.md` 가 소유한다. 여기엔 재사용되는 판단만 남긴다.\r\n\r\n- **타격감은 시간 정지가 아니어도 성립한다** — js13kGames 2025 1위작 CLAWSTRIKE 는 전역 타임스케일 정지 없이, x 10Hz·y 15Hz 의 **결정론적 사인 카메라 셰이크**(일반 타격 0.05s·진폭 5, 강타격 0.1s·진폭 10)와 공격 쿨다운(0.1s, 6연타 후 0.5s)만으로 타격을 만든다. 난수 지터보다 저용량이고 재현 가능하다. 화면 UI 의 대응물 = 확정 순간의 짧은 이중 주파수 진동 + 재입력 쿨다운. (`https://play.js13kgames.com/clawstrike/`, 접근 2026-08-16)\r\n- **행 단위 붕괴형 리빌** — Dragonfly 의 GL_SMEAR 는 스크롤 진행률을 `1 - progress` 로 뒤집어 1초 `expo.out` 로 트윈한 뒤, 셰이더에서 행 붕괴(위 행이 마지막 행으로 빨려듦) + 가로 늘임(`0.1 * uSmear^2`) + 깊이 페이드 3채널을 합성한다. 난수는 전부 행 인덱스 해시라 프레임 간 안정적이다. 우리 저장고에 없는 계열이라 다음 회차 구현 후보. (`https://www.dragonfly.xyz/`, 접근 2026-08-16)\r\n- **지도 메타포 내비게이션은 zoom 스칼라 하나가 지배한다** — Chartogne-Taillet 은 카메라 고도·패닝 속도·모드 분기(지도/다이브)를 전부 하나의 `zoom.value`(switch 임계 .8)에서 파생시키고, 위치·회전은 목표값을 향해 `easing * delta` 로 감쇠 추종한다. 파라미터를 3벌 따로 두면 이 일관성이 깨진다. (`https://chartogne-taillet.com`, 접근 2026-08-16)\r\n\r\n## 구조 판단 (역설계 실측 6건 — M58 회차 4, 2026-08-16)\r\n\r\n수치 원문과 처분은 `research/m58-round4/` 가 소유한다(`p2-nk-studio.json` 행 ID 로 대조). 여기엔 다른 작업에서 재사용되는 판단만 남긴다. 출처는 전부 20 Years Inspired by People (`https://inspiring.nk.studio/es`, 접근 2026-08-16).\r\n\r\n- **사운드는 효과음 목록이 아니라 단계 번호로 배열한다** — 파일명이 `0-ambient` `1-intro-overview-transition` `2-overview-cards-intro` `3-1/3-2-cards-hover` `4-history-open` `5-overview-drag` 로 연출 순서를 달고 있다. 볼륨도 임의값이 아니라 사다리다: 방금 사용자가 한 일일수록 크고(상세 열기 0.7) 배경에 가까울수록 작다(호버 0.1). 호버는 2종을 교대로 써서 연발을 막고, 드래그는 제스처가 지속되는 동안만 도는 루프다. 이 배열법은 매체와 무관하게 옮겨진다. (r4-03)\r\n- **자산 해석은 로컬 우선 + 짧은 프로브 + 폴백** — `/sounds/<name>.mp3` 를 300ms AbortController 로 HEAD 프로브해 `content-type` 이 audio 계열일 때만 채택하고, 아니면 CDN 으로 간다. 인스턴스는 이름별 참조 카운트로 캐시하고 마지막 구독이 풀리면 언로드 + `revokeObjectURL`. 자체 호스팅으로 옮겨도 호출부를 안 고치는 구조다. (r4-04)\r\n- **관성이 멎기 전에 스냅을 시작한다** — 스냅 판정을 \"속도가 0에 가까운가\"가 아니라 \"감속 중인가\"로 바꾼다: `|v| < |v_prev| - 1.05` 이면서 `|v|` 가 2.5~76 구간이면 최근접 슬롯 정렬을 건다. 멈춘 뒤 끌려가는 느낌 대신 미끄러지는 도중에 빨려드는 느낌이 나온다. (r4-24)\r\n- **라우트 없는 상세 오버레이의 대가를 명시한다** — 33인의 스토리를 개별 URL 없이 클라이언트 상태 패널로만 여는 선택은 전환 연출을 자유롭게 하지만, 정적 HTML 에 본문이 없어 공유·색인이 사라진다. 이 사이트는 쿼리 파라미터 진입로만 남겨 딥링크를 반쯤 회수했다. 같은 선택을 할 때 반드시 함께 결정할 항목. (r4-35)\r\n- **본문 저작 문법은 마크다운 전체가 아니라 필요한 한 줄이면 된다** — `[라벨][href]` 하나만 정규식으로 파싱해 조각 배열을 만들고, 링크를 걷어낸 평문 버전을 함께 반환한다(요약·메타태그용). 편집자에게 문법 하나만 가르치고 렌더러는 20줄로 끝난다. (r4-36)\r\n- **의사요소 + radial-gradient 로 이음매를 그린다** — 탭·말풍선 밑동의 오목한 곡선을 SVG 없이 `radial-gradient(circle at top left, transparent 15px, #fff 16px)` 13×13px 의사요소 2개로 만든다. 1px 차이로 안티에일리어싱 경계를 잡는 것이 요령. (r4-39)\r\n\r\n## 관찰 갤러리 (기법이 아니라 사례를 본다)\r\n\r\n- **60fps.design** — iOS·웹 앱의 마이크로인터랙션·전환·제스처 디테일 큐레이션. `https://60fps.design/` (접근일 2026-07-28)\r\n- **landing.love** — 애니메이션 랜딩 갤러리, 풀페이지 영상 기록 — 스크롤 페이싱·히어로 전환 관찰용. `https://www.landing.love/` (접근일 2026-07-28)\r\n- **Brainwave Interactive Landing** — 애니메이션 헤비 AI 제품 랜딩 완성 사례(히어로 레이어링·스크롤 트리거 섹션). `https://github.com/arnobt78/Brainwave-Interactive-Landing--React-Frontend` (접근일 2026-07-28)\r\n\r\n판정 이력: 위 3건 + 공간형 랜딩 팩은 absorption-criteria **B(링크 참조)** — 재서술·벤더링 금지, 관찰 소스로만 (`research/2026-07-28-vi7-toolshelf-placement.md`).\r\n\r\n## 관련 toolshelf 항목 (코드 클론)\r\n\r\n- `magicui` — 애니메이션 컴포넌트 패턴 (카드 5/6/9)\r\n- `shaders` (paper-design) — WebGL 메시 그라디언트 (카드 6)\r\n- `cmdk` — 커맨드 팔레트 primitive (카드 10)\r\n- `cult-ui` — 편집 캔버스·머티리얼 컴포넌트 (카드 1/7)\r\n- `react-bits` · `motion` — 애니메이션 컴포넌트/엔진 (기존)\r\n\r\n상세 후보 분석: `docs/research/showcase-card-reference-shortlist-2026-07.md`"},{"id":"onboarding","title":"Onboarding","description":"Onboarding judgment rules: design toward the first real success (activation) rather than signup completion, cut every step that is not required to reach it, teach in context at the moment of use instead of front-loading slides, and treat the first empty screen as the primary onboarding surface","tags":["onboarding","activation","first-run","empty-state"],"source_path":"knowledge/onboarding.md","body":"# Onboarding, 온보딩 판정 규칙\r\n\r\nDate: 2026-08-17\r\nMilestone: M57 (plan: `plans/2026-08-17-m57-ux-dictionary-absorb.md`)\r\n지위: 처음 온 사용자를 첫 성공까지 데려가는 흐름의 판정 정본. 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #7·#8 에 동결(원본 `ux-dictionary@60cbab3` `docs/ux-dictionary/methods.yml` 의 `onboarding-ux`·`progressive-onboarding`). 온보딩 중 권한을 묻는 지점은 [[permission-and-notification]] §1 소관, 첫 화면의 빈 상태는 [[empty-and-error-states]] §2 소관, 가입 폼 자체는 [[form-fields]] 소관이다.\r\n\r\nAudience: 에이전트(첫 실행·첫 설정 화면 생성 시 필수 참조) + 사람.\r\n\r\n## 0. 대원칙, 목표는 가입 완료가 아니라 첫 성공이다\r\n\r\n온보딩의 성공 기준을 가입 완료로 잡으면 가입만 잘 되고 사용은 일어나지 않는 화면이 나온다. 기준은 **사용자가 이 제품으로 실제 가치를 처음 얻은 순간**이어야 한다. 협업 도구라면 첫 문서를 남과 공유한 순간이지 계정을 만든 순간이 아니다.\r\n\r\n이 지점을 먼저 정하고 나면 나머지 판정이 기계적으로 따라온다. **그 순간에 도달하는 데 필요 없는 단계는 전부 뒤로 미룰 후보다.**\r\n\r\n## 1. 첫 성공 앞의 단계를 센다\r\n\r\n- 첫 성공에 **반드시 필요한 것**만 앞에 둔다.\r\n- **나중에 물어도 되는 것**은 나중에 묻는다. 프로필 사진, 팀 이름, 알림 취향은 대개 여기에 속한다.\r\n- **우리가 궁금한 것**과 사용자가 필요한 것을 구분한다. 유입 경로 설문은 사용자에게 아무 값도 주지 않는다. 넣으려면 건너뛰기를 준다.\r\n- 값을 보기 전에 결제 정보를 요구하지 않는다.\r\n\r\n## 2. 설명 슬라이드를 온보딩이라고 부르지 않는다\r\n\r\n기능 소개 화면을 3장 넘기게 하는 것은 학습이 아니라 지연이다. 사용자는 아직 맥락이 없어서 그 설명을 기억할 자리가 없다.\r\n\r\n대신 **쓰는 순간에 가르친다.**\r\n\r\n- 해당 기능을 처음 만나는 화면에서 한 문장으로 안내한다.\r\n- 코치마크는 그 화면에서 **하나만** 띄운다. 여러 개를 동시에 띄우면 사용자는 전부 닫는다.\r\n- 한 번 본 안내는 다시 띄우지 않는다. 다시 띄우려면 사용자가 그 사이에 실패했다는 근거가 있어야 한다.\r\n\r\n## 3. 첫 화면의 빈 상태가 곧 온보딩이다\r\n\r\n신규 사용자가 처음 보는 것은 대개 아무것도 없는 목록이다. 이 화면이 온보딩의 본체인데 흔히 방치된다.\r\n\r\n- 이 화면이 무엇으로 채워지는지 보여주고, 첫 항목을 만드는 행동 하나를 눈에 띄게 둔다([[empty-and-error-states]] §1 최초 사용 행).\r\n- 예시 데이터를 넣을 거라면 **그것이 예시임을 알리고 지우는 법을 준다.** 사용자가 자기 데이터와 헷갈리면 신뢰가 깨진다.\r\n- 체크리스트를 쓴다면 항목이 실제 가치와 이어져야 한다. `terms.yml` 의 `onboarding-checklist` 가 이 자리의 물건이다.\r\n\r\n## 4. 건너뛰기를 막지 않는다\r\n\r\n이미 아는 사용자, 두 번째 계정을 만드는 사용자, 남의 초대로 온 사용자가 있다. 건너뛰기가 없으면 이들에게 온보딩은 순수한 손해다. `onboarding-carousel-skip-pattern` 이 이 계약의 물건이다.\r\n\r\n건너뛴 사용자가 나중에 그 안내를 다시 찾을 수 있어야 한다.\r\n\r\n## 5. 판정 절차\r\n\r\n1. 이 제품의 첫 성공 순간을 한 문장으로 말할 수 있는가. 그것이 가입 완료가 아닌가.\r\n2. 첫 성공까지의 단계를 셌는가. 각 단계가 그 순간에 필요한 이유를 댈 수 있는가.\r\n3. 설명 슬라이드로 시작하고 있지 않은가.\r\n4. 첫 빈 화면이 설계돼 있는가.\r\n5. 건너뛸 수 있고, 건너뛴 뒤 되찾을 수 있는가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 (M57). `ux-dictionary@60cbab3` 의 `onboarding-ux`·`progressive-onboarding` 두 항목을 합본으로 흡수. `knowledge/` 에 온보딩 축이 grep 0건이던 공백을 메운다. 원문이 지표 측정(activation metric 정의·이탈 계측)까지 다루지만 그 부분은 이 레포 무게중심 밖이라 담지 않았다(판정 장부 §3-2 참조). 원문의 2차 출처(`material-navigation`)는 1차 확인 전이라 인용하지 않는다."},{"id":"permission-and-notification","title":"Permission & Notification","description":"System-permission and notification judgment rules: never fire an OS permission prompt without an in-app pre-explanation tied to the action that needs it, a denied permission must lead somewhere (never a dead end), one prompt per moment of need, and notifications are chosen by channel and urgency with a visible opt-out on every recurring type","tags":["permission","notification","mobile","consent","interruption"],"source_path":"knowledge/permission-and-notification.md","body":"# Permission & Notification, 권한 요청과 알림 판정 규칙\r\n\r\nDate: 2026-08-17\r\nMilestone: M57 (plan: `plans/2026-08-17-m57-ux-dictionary-absorb.md`)\r\n지위: 시스템 권한 요청과 알림 전달의 판정 정본. 근거는 `research/2026-08-17-m57-ux-dictionary-verdicts.md` §3-1 #3·#4 에 동결(원본 `ux-dictionary@60cbab3` `docs/ux-dictionary/methods.yml` 의 `permission-request-ux`·`notification-ux`). 알림을 **무엇으로 띄우는가**(snackbar·dialog·sheet 등 컨테이너 선택)는 [[mobile-navigation]] §3 소관이고, 이 문서는 **언제 물을 것인가와 어느 채널로 보낼 것인가**를 담는다. 권한 거절 후의 빈 화면 설계는 [[empty-and-error-states]] §1 과 맞물린다.\r\n\r\nAudience: 에이전트(권한 요청·알림 설계 시 필수 참조) + 사람.\r\n\r\n## 0. 대원칙, 요청은 필요해진 순간에 붙는다\r\n\r\n권한과 알림은 둘 다 사용자의 주의를 강제로 가져오는 장치다. 판정 기준은 하나로 모인다. **그 순간 사용자가 원하던 일과 이 요청이 이어져 있는가.**\r\n\r\n앱 첫 실행에 위치·알림·카메라를 몰아서 묻는 화면이 전형적인 실패다. 사용자는 아직 아무것도 하지 않았으므로 허용할 이유가 없고, 시스템 권한은 대개 한 번 거절되면 앱 안에서 되물을 수 없다.\r\n\r\n## 1. 시스템 프롬프트 앞에 앱 안 설명을 둔다\r\n\r\n시스템 프롬프트는 문구를 우리가 못 정하고 재요청도 막힌다. 그래서 순서가 규칙이 된다.\r\n\r\n1. 사용자가 그 권한이 필요한 행동을 시작한다.\r\n2. **앱 안 사전 설명**을 보여준다. 무엇에 쓰는지, 허용하지 않으면 무엇이 안 되는지. 이 화면은 우리가 문구를 정할 수 있고 거절해도 비용이 없다.\r\n3. 사용자가 여기서 동의하면 그때 시스템 프롬프트를 띄운다.\r\n\r\n`terms.yml` 의 `permission-education-screen` 이 2단계의 물건이고 `permission-prompt` 가 3단계다. 2단계를 건너뛰고 3단계로 직행하는 구현이 결함이다.\r\n\r\n**한 순간에 하나만 묻는다.** 두 권한이 동시에 필요해 보이면 대개 흐름을 쪼갤 수 있다.\r\n\r\n## 2. 거절은 막다른 길이 아니다\r\n\r\n거절률은 0 이 될 수 없으므로 거절 경로가 제품의 정상 경로다.\r\n\r\n- 거절한 사용자에게 **무엇이 제한되는지와 대안**을 보여준다. 위치를 거절했으면 주소 직접 입력을 준다.\r\n- 기능 전체를 잠그지 않는다. 권한 없이 되는 부분은 되게 둔다.\r\n- **설정으로 가는 경로를 제공한다.** 시스템 권한은 앱 안에서 되물을 수 없으므로 OS 설정 화면으로 보내는 것이 유일한 복구다. `location-permission-empty` 같은 빈 상태가 이 자리를 맡는다([[empty-and-error-states]] §1 표의 권한 행).\r\n- **재요청은 맥락이 바뀐 뒤에만 한다.** 같은 화면에서 반복해 조르지 않는다.\r\n\r\n## 3. 알림은 채널과 긴급도로 고른다\r\n\r\n전부 푸시로 보내면 사용자는 전부 끈다. 하나를 끄면 다른 것도 같이 꺼지는 것이 문제다.\r\n\r\n| 성격 | 채널 | 판정 기준 |\r\n|---|---|---|\r\n| 놓치면 손해가 나고 시간에 묶임(예약 변경, 결제 실패) | 푸시 | 지금 화면 밖에 있어도 알아야 하는가 |\r\n| 기록이 남아야 함(영수증, 정책 변경) | 이메일 | 나중에 찾아볼 일이 있는가 |\r\n| 지금 이 화면 안의 결과(저장됨, 실패함) | 인앱 토스트·배너 | 화면을 보고 있는 사용자에게만 의미 있는가 |\r\n| 몰아서 확인해도 되는 것(누가 댓글을 달았다) | 알림 센터 | 개별로 깨울 만한가 |\r\n| 마케팅·재방문 유도 | 별도 채널, 별도 동의 | 사용자가 요청한 적 있는가 |\r\n\r\n마지막 행이 규칙의 핵심이다. **마케팅을 핵심 알림 채널로 보내지 않는다.** 한 번 그렇게 하면 사용자가 핵심 알림을 끄고, 그 손해는 되돌리기 어렵다.\r\n\r\n## 4. 반복되는 알림에는 끄는 길이 보인다\r\n\r\n- 반복 유형마다 해제 경로가 있어야 하고, 그 경로가 알림 자체나 설정 화면에서 한두 단계 안에 닿아야 한다.\r\n- **전체 끄기만 있는 설정은 설정이 아니다.** 유형별로 끌 수 없으면 사용자는 전체를 끈다.\r\n- 빈도 상한을 유형별로 정한다. 같은 사건으로 여러 번 깨우지 않는다.\r\n\r\n## 5. 판정 절차\r\n\r\n1. 이 권한 요청이 사용자가 시작한 행동에 붙어 있는가. 첫 실행에 몰려 있지 않은가.\r\n2. 시스템 프롬프트 앞에 앱 안 사전 설명이 있는가.\r\n3. 거절 상태에 대안과 설정 이동 경로가 있는가.\r\n4. 이 알림이 §3 표의 어느 행인가. 푸시여야 할 이유를 댈 수 있는가.\r\n5. 이 알림 유형만 따로 끌 수 있는가.\r\n\r\n## Changelog\r\n\r\n- 2026-08-17: 초판 (M57). `ux-dictionary@60cbab3` 의 `permission-request-ux`·`notification-ux` 두 항목을 합본으로 흡수. `knowledge/` 에 권한 축은 grep 0건, 알림 축은 [[mobile-navigation]] 1줄뿐이던 공백을 메운다. 원문의 2차 출처(`apple-hig`)는 1차 확인 전이라 인용하지 않고, 플랫폼별 수치·API 제약은 담지 않았다."},{"id":"recreated-sites","title":"102개 재현 사이트에서 요소 꺼내 쓰기","description":"메뉴 스크롤 영상 캐러셀 가격 호버 등장 글자 셰이더 — 102개 사이트 원문 기반 모듈 검색과 이식","tags":["recreation","motion","menu","scroll","video","carousel","pricing","shader","Apple","Framer","Rivian","Cloudflare"],"source_path":"knowledge/recreated-sites.md","body":"# 102개 재현 사이트에서 요소 꺼내 쓰기\r\n\r\n이 색인은 사용자 승인을 받은 재현 사이트 전용이다(회차별 승인 날짜는 recreated-sites.json `approval`). 이전 페이지·장면·예제를 포함한 전체 목록은 `/site-library/index.json`과 `docs/design-system/site-reference.md`를 본다.\r\n\r\n승인: 2026-10-16 첫 40곳 · 2026-10-23 M142 26곳(사용자 시각 검토). 원시 움직임 잔차는 각 changeset/evidence에 그대로 남아 있다.\r\n이 문서는 검색 색인이다. 본문 코드는 로컬 미배포 자산이며 npm/원격 등재를 뜻하지 않는다.\r\n\r\n로컬 검색: `python scripts/recreate/catalog.py \"메뉴\" --limit 5` (ui-dictionary 루트).\r\n사이트 이름 또는 원본 부품 이름도 검색한다. 전체 목록은 `docs/design-system/recreated-sites.json`.\r\n색인 갱신: `python scripts/recreate/catalog.py --refresh`.\r\n\r\n결과의 모듈 → 사이트 motion.js 마운트 옵션 → 원본 DOM/CSS → decoded 원문 → changeset 근거 순으로 읽는다.\r\n선택자·CSS 변수·상위 컨텍스트·자산·상대 import·정지 함수를 확인하고, 필요한 의존만 함께 옮긴다.\r\n내용은 대상 프로젝트의 문구·로고·사진·영상으로 바꾼다. 해당 프로젝트 토큰으로 restyle하고\r\n진입/이탈/재진입·hover·메뉴·reduced-motion·모바일을 실제 렌더로 검증한다.\r\n파일 하나가 범용 독립 컴포넌트 하나라는 뜻은 아니다. 내부 도우미와 사이트 전용 결합도 포함한다.\r\n정식 recipe/registry 승격은 `methodology/site-recreation/reuse.md` 「범용 자산으로 승격」을 따른다.\r\nHAR/tmp 캐시는 검색 결과나 대상 프로젝트에 복사하지 않는다.\r\n\r\n| 사이트 | 모듈 파일(부품과 내부 도우미 포함) |\r\n|---|---|\r\n| claude-startups | AnimatedHeading, AnimatedReveal, CoverVideo, Dropdown, ExistingRuntime, HeadingContent, Illustration, LogoWall, ResponsiveStory, StoryPointer, useInView |\r\n| claude-overview | AnimatedHeading, AnimatedReveal, ConstellationMap, Dropdown, ExistingRuntime, HeadingContent, HeroClip, HeroVideoCover, NewsToastOverview, OverviewHero, OverviewOrbit, RolesBand, SectionReveal, StepPanel, TestimonialPreload, UseCaseArtifact, useInView |\r\n| claude-code | AnimatedHeading, AnimatedReveal, Dropdown, ExistingRuntime, HeadingContent, Illustration, LogoWall, Tabs, useInView |\r\n| apple-iphone-air | AirGalleryState, AirInlineState, AirKeyframes, AirLocalNavTabs, AnimLazyImage, ExistingRuntime, ExpressionParser, GlobalNav, GlobalSearchAnnouncement, InlineMedia, IntroTurntable, LocaleSwitcher, MediaCardGalleryControl, PictureFrameToggle, PictureLazyLoading, ProductViewerIntro, ScrollTimeline, SpringLayer, StaggeredFadeIn, TextIconControl, TimeTimeline, UpgradeLoadAssets, VideoScrub |\r\n| mistral | Headline, Navigation, PreservedInteractions |\r\n| github-home | Dropdown, HeaderScroll, HeroCarousel, HeroCarouselIcons, IntroScroll, PreservedInteractions, SectionReveals |\r\n| vercel-ship | CanvasShell, EventCountdown, FeaturedSpeakers, PreservedInteractions, ScrambleText |\r\n| shopify-editions-w26 | BackgroundImages, CanvasShell, CloudCards, DavinciLines, MediaVisibility, NavigationScroll, NavigationTheme, OriginalPostprocessing, PreservedInteractions, RiveShell, RolloutParticles, SidekickCard, SkillTags |\r\n| apple-airpods-pro | AudioPerformance, ExpressionParser, GlobalNav, GlobalSearchAnnouncement, ImageLoadBootstrap, InlineMedia, LocaleSwitcher, LocalnavTrigger, MediaCardGalleryControl, ParallaxImage, PictureLazyLoading, PictureToggleSource, ProductViewerIntro, RetainedInteractions, ScrollTimeline, SpringLayer, StaggeredFadeIn, TextIconControl, TimeTimeline, VideoScrub |\r\n| apple-macbook-pro | AirKeyframes, BatteryAnim, CaptionTileMedia, ChipFamily, ChipHero, ClassKeyframes, ExpressionParser, GlobalNav, GlobalSearchAnnouncement, HardwareSlide, HardwareZoom, ImageLoadBootstrap, InlineMedia, InlineMediaLoad, LocaleSwitcher, LocalnavTrigger, MediaCardGalleryControl, PictureFrameToggle, PictureLazyLoading, ProductViewerIntro, RetainedInteractions, ScrollTimeline, SpringLayer, StaggeredFadeIn, TextIconControl, TextOverMedia, TimeTimeline |\r\n| apple-ipad-pro | AllAccessPass, ClassKeyframes, ExpressionParser, GlobalNav, GlobalSearchAnnouncement, HighlightsGallery, ImageLoadBootstrap, InlineMedia, InlineMediaLoad, LocaleSwitcher, LocalnavTrigger, MediaSource, PictureLazyLoading, ProductGalleryColor, RetainedInteractions, ScrollTimeline, TextAnim, TimeTimeline |\r\n| claude-cowork | Dropdown, EmbedFrame, HeadingContent, RetainedInteractions, useInView |\r\n| dia-browser | ExistingRuntime, NavigationCta, SunriseEntry, ViewReveal |\r\n| cloudflare-home | AgentRows, CornerLines, CustomerBrowser, HeaderScrollState, InfiniteSlider, LogosCompact, NavDropdown, PixelGrid, PlatformTabs, Popups, PricingCategories, ProductNodes, RetainedInteractions, RotatingText, RotatingTextHover, Shipping, TabIndicator, WorkflowCard |\r\n| raycast-home | ApiFig3, ApiFig4, CmdSpaceKeyboard, ExtensionHighlight, FeatureWall, HotkeysShowcase, RetainedInteractions, ShowcaseReel, SnippetsShowcase, Stars, WorkflowDemo, YoutubeCarousel |\r\n| spotify-premium | BackgroundColorManager, ContentCardGroup, FeatureCardGroup, FeatureGrid, HorizontalLoop, RetainedInteractions, ScrollingHero, ScrollingImageGallery, SpotlightSection, TextReveal |\r\n| apple-vision-pro | AnimLazyImage, DisplayLayers, ExpressionParser, FocusManager, Foundations, GLGradient, GlobalNav, GlobalSearchAnnouncement, InlineMedia, InlineMediaLoad, LocaleSwitcher, LocalNavTheme, PictureLazyLoading, Portal, ScrollTimeline, Sensors |\r\n| apple-iphone | DeepLink, ExpressionParser, GlobalNav, GlobalSearchAnnouncement, ImageAccordion, LocaleSwitcher, MediaCardGalleryControl, PictureLazyLoading, ScrollTimeline, SpringLayer, StaggeredFadeIn |\r\n| apple-mac-studio | ChipBentoContent, ChipBentosContainer, ConnectivityReveal, ConnectivitySequence, ExpressionParser, GlobalNav, ImageLoadBootstrap, ImageSequencePlayer, InternalsXRay, LocaleSwitcher, LocalNav, PictureLazyLoading, RetainedInteractions, ScrollTimeline, StatsGallery |\r\n| claude-platform-api | AnimatedHeading, AnimatedReveal, Dropdown, ExistingRuntime, HeadingContent, Illustration, LogoWall, Testimonials, useInView |\r\n| microsoft-ai | HeaderScroll, Locations, MetaNav, SiteReady, SplitText |\r\n| elevenlabs-home | ChladniContours, ChladniHover, ContextualNavigation, CustomerStoryRotator, FloatingAgentLayout, NavigationMenu |\r\n| stripe-home | AgenticCommerce, BackgroundFade, BentoCard, BillingPlan, ConnectPlatform, DevelopersWaveEngine, DeveloperSystems, ExistingLogoStrip, Globe, GlobeCore, GlobeDependencies, GlobeWorker, HeroValue, LazyAnimation, MotionEngine, NavigationMenu, Payments, WaveFirstDraw |\r\n| rivian-r2 | ChargerLayers, ChargingMap, HapticTabs, HeroLoad, HeroScroll, InteriorStopIndicator, LottiePlayer, NavbarTrigger, NavPanelEntry, RetainedInteractions, RivianAssistant, SafetyReveal, StickyNav, TrimReveal |\r\n| claude-financial-services | AnimatedHeading, AnimatedReveal, Dropdown, EmbedFrame, HeadingContent, Illustration, LogoWall, NewsToast, SnapshotInteractions, Testimonials, useInView |\r\n| anthropic-opus-5-5 | AnthropicHeader, ArticleInteractions, BenchmarkGrid, ChartFrame, ChartLayout, FluidStage, LaunchHero, LogoWordmark, Lottie, ModelComparison, OverflowTabs, Prefooter, SideToc |\r\n| meta-quest-3 | PreservedMedia, TopNav, TopNavButtons |\r\n| surface-laptop | FloatingButton, InView, PageInteractions, SplitText, SplitTextCore |\r\n| framer-ai | AgentDotAnimation, AnimatedCounter, CookieBanner, FramerScene, GenBorder, InstanceTextProps, LayoutProjection, MotionMixer, MotionSpring, Progress, PromptPlaceholder, ProTypewriter, ResponsiveVisibility, ScrollTransform, ServiceStatus, SignupHover, SnapshotInteractions, StrokeLoop, StyleAppearEffect, VariantController |\r\n| google-flow | FlowNavigation, MenuOverlay, SnapshotInteractions |\r\n| github-universe | HeaderCountdown, LogoEntry, NavScroll, NavUnscramble, SectionReveals, SnapshotInteractions, SpeakerReveal, TestimonialTypewriter |\r\n| linear-next | AsciiLoader, MediaIdle, Navigation, PreservedInteractions, Tooltip |\r\n| deepmind-veo | DeepAIDemo, ExistingRuntime, Jumplinks, MediaVideo, Navigation |\r\n| deepmind-genie | DeepAIDemo, ExistingRuntime, Jumplinks, MediaVideo, Navigation |\r\n| deepmind-alphafold | Jumplinks, Navigation, NewsGroups, PreservedEffects, QuoteAmbient |\r\n| gemini-overview | ArticleMobileNav, Drawer, Header, PreservedInteractions |\r\n| anthropic-claude-opus | LogoWordmark, Lottie, PreservedInteractions |\r\n| xai-grok-4 | AgentOrb, ArrowIcon, BentoImagine, BentoPreview, BentoRender, BotConvo, BotEntries, BotMark, BotRender, DOM, DotLoader, HeaderNav, HeaderScroll, HoverHighlight, ImagineImages, MotionEngine, OrbRenderer, PageInteractions, ResponsiveCharts, TerminalPrimitives |\r\n| perplexity-comet | MediaLifecycle, MotionEngine, NavHover, NavScrollVariant, PageInteractions, planet-effects, PlanetAppear, ScrollTransform |\r\n| figma-config | EmbeddedMedia |\r\n| palantir-ontology | AnimatedLine, AutonomousLayers, DiagramArrows, GsapRuntime, Navigation, ReadWriteLayers, ResponsiveOntologyHeading, search-template, SearchRuntime, SiteSearch, ToolchainOverlay, VideoPoster |\r\n| igloo-inc | AssetLoader, Camera, Engine, Input, LoaderOverlay, Renderer, SceneContent, SceneEntry, SceneTransition, Shaders |\r\n| lusion | Engine, HeaderMotion, Input, listener-owner, Lusion, PreloaderMotion, RendererScene, SceneContent, ScrollMotion, Viewport |\r\n| active-theory | AssetLoader, Camera, ContentSlots, Input, native-definitions, Renderer, SceneTransition, Shaders |\r\n| kinfolk | AdBanner, BasketCard, Burger, Carousel, CategoryList, EditorsPickCard, FeaturedRow, FlexibleTitle, Footer, JournalCard, MiniCart, MiniCartIcon, native-common, native-ownership, native-runtime, native-vendor, PaceLoader, PrefetchLinks, ProductCard, ProductHero, SearchBar, SiteHeader, SiteNav, SmoothScroll, StoryModule, SubscribeBar, SubscribePanel, SubscriberBar, Video |\r\n| oura | CyclingText, Footer, Header, HeroIntro, lifecycle, ScrollParallax, Slider, ViewReveal |\r\n| locomotive | CookieConsent, CopyToClipboard, Environment, FeaturedLinks, Header, HomeHero, Hovers, Load, ModelViewer, ModuleHost, NativeModule, NewsletterModal, NewsletterToggler, Preloader, Ring, Scroll, SharedRuntime, TeamCanvas, VideoInview, VideoModal |\r\n| pentagram | FilterCarousel, HomeFilters, LazyImage, NavToggle, ProjectCard, SerializedCarousel, StickyTop, ThematicCarousel, VimeoPlayers |\r\n| pangram-pangram | AppearBinding, Carousel, CustomFont, HeaderNav, Parallax, SnapshotState |\r\n| bruno-simon | Inputs, lifecycle, Noises, Rendering, ResourcesLoader, Reveal, View |\r\n| resn | GemCamera, GemDefaults, GemInput, GemLoop, GemScene, GemShaders, GemTransition, lifecycle, SceneContent |\r\n| rive | AnimaScriptRepl, AnimaScriptReplSource, ClipboardActions, ContentRuntime, HeroRotator, HeroRotatorSource, HeroScenes, MarkupPreview, Navigation, Prism, ResponsivePage, SectionVisuals, SectionVisualsSource, VideoNew |\r\n| dont-board-me | Care, Consultation, ContentRuntime, Core, Dog, Environment, Header, Hero, Instagram, LottieRuntime, Page, Preloader, Pricing, ProcessCards, Reveal, ScopedMount, Services, State, Swiper, Testimonials, TestimonialsData |\r\n| stripe-press | AssetURLs, BookSettings, CanvasData, ContentRuntime, Controller, Debounce, EngineQAMXPDSP, Environment, Globalization, GLTFLoader, Helpers, MaterialsData, PageController, PodcastScene, PressHomepage, PressHomepageCanvas, PressHomepageFilmDetails, PressHomepageFilmDetailsHero, PressHomepageFilmOverlay, PressHomepageFooter, PressHomepageFooterNewsletter, PressHomepageMenu, PressHomepagePodcastDetails, PressHomepagePodcastDetailsPreview, PressHomepagePodcastTranscriptOverlay, Three, Video, Viewport |\r\n| anduril | ArsenalItem, BalanceText, CompanyDomainWrapper, ContentRuntime, FeaturedAnnouncementSlice, FeaturedProductsCard, Footer, FooterListLinks, HeaderDesktop, HeaderDomainWrapper, HeaderMobile, HeaderSubmenus, HomeHero, LenisCore, LinkButton, LoaderView, Media, MediaLinkCard, NativeScope, ResponsiveImage, ResponsiveMedia, Scroll, SearchDomainWrapper, SnapshotDOM, SpacerSlice |\r\n| basement-studio | ContentRuntime, OpacityReveal, TooltipPosition, VisibleTimeout |\r\n| immersive-garden | ContentRuntime, DefaultCamera, HomeRelief, HomeScroll, lifecycle, PointerInput, Renderer, ResourcePipeline, ShaderSources, WebGLApp |\r\n| lando-norris | Environment, HelmetWorld, HomeScroll, LandoRuntime, Marquee, Navigation, PageTransition, RiveCanvases, SceneContent, SmoothScroll, TextSplit |\r\n| spline | AssetLoader, AssetURLs, Camera, CodeBlock, ContentRuntime, Header, HeroPrompt, Input, Media, Renderer, RuntimeBridge, RuntimeCore, RuntimeFactories, Scenes, Shaders, Tooltips |\r\n| modal | CustomerCarousel, CustomerFieldCore, EnterpriseHover, EnterpriseHoverCore, EnterpriseReveal, EnterpriseRevealCore, MarketingWebGL, Navigation, NavigationCore, RegionMap, Workload, WorkloadEngine, WorkloadNavigation, WorkloadSettings |\r\n| krea | Agent, AgentRenderer, Bootstrap.source, Carousel, EmblaCore, Gallery, GalleryEngine, HomePage, Intersection, Media, ModelImages, SearchPlaceholder, StickySearch, Templates, Theme, ThemeBootstrap.source, Tooltip, VideoVisibility, WeatherCategory, Welcome, WheelGestures |\r\n| motion-dev | Agent, Collection, Coverflow, GalleryEngine, GalleryHost, Gestures, HelixGallery, IdentityField, MadeCarousel, Media, NativeLayout, Navigation, ScrollPanel, SpinnerEngine, SpringDemo, SpringEngine, Stats, TickerMath, TypewriterEngine, WallTicker |\r\n| gsap | Header, HeaderBootstrap, HeaderButtons, HomeAnimate, HomeHero, HomeIntro, HomeTools, Listeners, SharedBlocks, SharedBootstrap |\r\n| cuberto | Card.entry, Card, Cursor.entry, Cursor, Divider.entry, Divider, FaqSection.entry, FaqSection, FeatureSection.entry, FeatureSection, Layout.entry, Layout, Lifecycle, Loader.entry, Loader, LogoreelSection.entry, LogoreelSection, Navbar.entry, Navbar, OutroSection.entry, OutroSection, OverviewSection.entry, OverviewSection, Page, PreviewSection.entry, PreviewSection, RoundButton.entry, RoundButton, ShowreelModal.entry, ShowreelModal, SummarySection.entry, SummarySection, TopheadSection.entry, TopheadSection, View |\r\n| posthog | AskAnythingDemo, BackdropMirror, ContentStyles, CookieToast, HeroCarousel, InboxDemo, ProductContextDemo, RoughAnnotation, SiteTheme |\r\n| playdate | DeviceModels, GalleryLightbox, GameCards, LazyCatalog, ResourceContent, SiteState |\r\n| toss | ContentRuntime, MotionControls, OriginalRuntime |\r\n| kurly | ContentRuntime, MotionControls, OriginalRuntime |\r\n| kakao-corp | ContentRuntime, MotionControls, OriginalRuntime |\r\n| grafana-play | ContentRuntime, MotionControls, OriginalRuntime |\r\n| neal-deep-sea | ContentRuntime, Depth, MotionControls, OriginalRuntime |\r\n| morpho-water | CanScene, Journey, JourneySettings |\r\n| pudding-happy-map | MapAssets, MapCamera, MapInput, MapRenderer, MapScene, MapText |\r\n| windy | LeafletGL, SearchInput, WeatherApp, WeatherSettings |\r\n| pegassi-ascension | SceneSettings, Stage |\r\n| miumiu-immersive-bags | App, RoomSettings, RoomStage, Runtime |\r\n| the-tiebreak | App, GameSettings, GameStage, Runtime |\r\n| cerebrium | BackgroundCanvas.Bg_NUauP, FeatureCardTerminal.DNPFJKpV, HeroHome.BVyja4RL, preload-helper.CVfkMyKi, SceneSettings, Scroll.cO9MU0Pv |\r\n| ewha-140 | 10-cms-common, 11-footer.content, 12-main-visual.content, 13-main-content03.content, 14-main-content04.content, 15-main-content02.content, 16-header.content, 18-main |\r\n| obama-oral-history | HeroStackOptions, TemplateContent |\r\n| explora-journal | ArticleData, GridOptions, NativeMedia, NativeScripts |\r\n| pudding-kpop-generations | NativeStory |\r\n| the7eye-both-sides | NativeScripts |\r\n| ai-zeitgeist | NativeApp |\r\n| trigger-dev | CodeTabs, EntryReveals, HeartWaves, LogoWall, Navigation, RivePlayback, SetupPrompt, Testimonials, VideoPlayback |\r\n| butter-video | NativeAssets, NativeContent, NativeScene |\r\n| duolingo | AccountModals, CharacterMotion, FontFaces, index, LanguageMenu, Layout, LottieRuntime, Spring |\r\n| wispr-flow | 00-inline, 05-inline, 06-inline, 13-inline, 15-inline, 19-global, 20-global, 21-global, 22-global, 23-global, 24-global, 25-global, 26-global, 28-inline, 29-inline, 30-inline, 31-flow-stack, 32-wave-slider, 33-languages, 35-inline, 36-inline, 37-jquery-3.5.1.min.dc5e7f18c8, 38-webflow.schunk.f2efb3c5440a81cf, 39-webflow.schunk.b7b72544d8957638, 40-webflow.schunk.8c96c5d5554accc4, 41-webflow.3eca6ab1.bb3201bfb649b737, 42-gsap.min, 43-MotionPathPlugin.min, 44-SplitText.min, 45-DrawSVGPlugin.min, 46-ScrollTrigger.min, 47-MotionPathHelper.min, 48-ScrollToPlugin.min, 49-inline, 50-inline, 51-inline, 52-inline, 53-inline, 54-inline, 55-inline, 56-inline, 57-inline, 58-embed, 59-inline, 60-inline, 61-inline, 62-inline, 63-inline, 64-inline, 65-inline, 66-inline, 67-inline, 68-inline, 69-inline, 70-inline, 71-inline, 72-inline, Controls, ResourceMap |\r\n| risk-film | 00-webfont, 01-inline, 02-inline, 03-inline, 04-jquery-3.5.1.min.dc5e7f18c8, 05-webflow.751e0867.c70914493f903d19, 08-main, 09-inline, 10-inline, Controls, ResourceMap |\r\n| balatro | Confetti, ConfettiSettings |\r\n| temporal | BackgroundAssets, CssMotion, HeroGlow, MobileNavigation, StatusBadge, WorkflowDemo |\r\n| boulder-climate | _commonjsHelpers.Cpj98o6Y, gsap.original, Intro, Menu, ScrollTrigger.original, setColors.original |\r\n| plausible-demo | InitialVariant, Popovers, ReportTabs, StickyFilters |\r\n| stripe-docs | CommandDemo, CookieBanner, Dialogs, SailState |\r\n| tailwind-docs | CopyButton, HeadlessState, Panels |\r\n| scroll-driven-animations | CSSTokens, Shared, VideoPreview |\r\n| liveblocks | AnimatedAiIcon, CollaborationCard, EdgeNetworkGlobe, HeaderNavigation, HeroBodyDefinitions, HeroCamera, HeroContentCanvas, HeroHeading, HeroModels, HeroRenderer, HeroScene, PersistentDataIllustration, PhysicsSimulation, ThreeDependencies, UseCaseCarousel |\r\n| observable | CodeMirrorDependencies, NotebookCarousel, NotebookEditors, NotebookEditorTheme, NotebookFrames |\r\n| poolsuite | DesktopLayout, DockPager, HeaderClock, PoolsuiteBooter, RadioPlayer, RadioWaveform, ResponsiveRadio, ScrollWrapper, VideoPlayer |\r\n| pudding-street-view | ImageGridStory, PanoramaStory, RankingStory, StoryActions, StreetVideo |\r\n| swish-dental | MotionControls, OriginalRuntime |\r\n| r2d3-visual-ml | Engine, helpers, rAF-polyfill, Views |"},{"id":"scroll-collapse-header","title":"Scroll-Collapse Header","description":"Weather-app style header collapse — one scroll progress value drives per-layer parallax coefficients, threshold-staggered fade-outs, and a final absorb-into-background step; the mechanics are asset-agnostic so illustration layers can be swapped without touching the wiring","tags":["motion","scroll","header","pattern"],"source_path":"knowledge/scroll-collapse-header.md","body":"# Scroll-Collapse Header — 스크롤 붕괴 헤더 패턴\n\nDate: 2026-09-02\n지위: 살아있는 정본. 일러스트/씬 헤더가 스크롤에 반응해 접히는 방식을 판정한다. \"이 모션이 좋은가\"의 상위 판단은 [[motion-principles]], 티어·도구 선택은 [[expressive-stack]] 소관.\n근거: askewly-command M132 실구현(오너 실기기 판정 \"스크롤 느낌 괜찮아\", 2026-09-01) + 상용 날씨 앱 관찰·리서치(`askewly-command/research/2026-09-01-m132-header-methodology.md` — KG `nodes/모바일/layered-parallax-fade-color-absorb-scroll-pattern` 병행 인입). 참조 실물: 갤럭시 One UI 날씨 앱의 헤더 붕괴.\n\n## 패턴 — 진행도 하나, 세 가지 차등\n\n씬 헤더는 스크롤을 따라 **한 번에 사라지지 않는다.** 레이어마다 다르게 접혀야 깊이가 생긴다. 구동은 전부 **스크롤 진행도(progress) 값 하나**에서 파생한다 — 레이어별로 타이머·상태를 따로 두지 않는다.\n\n1. **레이어별 패럴랙스 계수** — 각 레이어는 `translateY = scroll × k` 로 밀린다. k 가 다르면(예: 태양 0.45 · 구름 0.18/0.3/0.5) 스크롤 자체가 깊이를 연출한다. 원경일수록 작게, 근경·소품일수록 크게.\n2. **threshold 차등 fade** — fade 곡선은 같고 **시작·끝 스크롤 범위만 레이어마다 다르다**(예: 태양 [0,140] · 구름 [0~25, 130~190] · 씬 바탕 [90,230]). 소품이 먼저, 바탕이 마지막에 사라지는 순서가 \"장면이 걷힌다\"로 읽힌다.\n3. **배경 흡수** — 마지막 단계는 씬이 앱 배경색으로 녹는 것이다. 씬 바탕의 최후 fade(또는 배경색 보간)로 만든다. 헤더가 사라진 뒤 남는 것은 앱의 평상시 표면이어야 한다 — 경계선·잔상을 남기지 않는다.\n\n## 판정 규칙\n\n- **헤더는 고정 크롬이 아니라 스크롤 콘텐츠다.** 리스트를 밀면 같이 밀려 올라간다. 고정해 두고 투명도만 바꾸는 것은 이 패턴이 아니다.\n- **역학과 에셋을 분리한다.** 패럴랙스 계수·fade 범위는 레이어 슬롯에 붙고, 그림(SVG/비트맵)은 슬롯에 꽂힌다 — 에셋을 갈아끼워도 배선이 그대로 살아야 유지비가 낮다.\n- **주변광 모션과 붕괴 모션은 합성한다.** 상시 드리프트(느린 루프, ≥20s)와 스크롤 구동 transform 은 같은 레이어에서 곱으로 결합한다 — 서로를 끄지 않는다.\n- 상용 구현의 완전 재현은 목표가 아니다 — Apple 날씨는 데이터 기반 절차 렌더링(다른 계열)이고, 재현 시도 1차 기록도 sticky fade-out 완전 재현 실패를 인정한다(리서치 §2). 위 3요소가 충족되면 사용자 눈에는 같은 문법으로 읽힌다.\n\n## 구현 스케치 (React Native / Reanimated 기준)\n\n```\nscrollY (SharedValue) ← 스크롤 핸들러\nlayer.style = {\n opacity: base × interpolate(scrollY, fadeRange_layer, [1,0], CLAMP),\n transform: [{ translateY: scrollY × k_layer }, ...주변광 transform],\n}\nsceneBase.opacity = interpolate(scrollY, [진입 늦게, 헤더높이 근처], [1,0], CLAMP)\n```\n\n실구현 정본: `askewly-command/mobile-v2/src/components/HeaderArt.tsx` + `Screen.tsx` (M132 step-9)."},{"id":"session-persistence-boundary","title":"Session Persistence Boundary","description":"Deciding what a UI remembers across visits and what it deliberately forgets: user-authored arrangement (position, size, sort order, view mode) persists, while momentary traces (stack order, foreground focus, load flags) are dropped on purpose; includes the observed schema split and how to apply the rule","tags":["session","persistence","state","windows","workspace"],"source_path":"knowledge/session-persistence-boundary.md","body":"# 재방문 지속의 경계 — 무엇을 기억하고 무엇을 잊을 것인가\r\n\r\nDate: 2026-08-17\r\nMilestone: M60 (plan: `plans/2026-08-17-m60-round6-prep-and-maintenance.md`, 처분 K1)\r\n지위: 살아있는 정본. 관측 근거는 `research/m60-round6/p2-daedalos.json` 행 `r6-19`(동결 기록).\r\n출처: daedalOS (`https://dustinbrett.com` · `github.com/DustinBrett/daedalOS`, MIT, HEAD `0df82d7`) — 회차 6 소진형 심층에서 0.5층 정적 직독으로 스키마 전 구간 확보.\r\n\r\nAudience: 사용자가 상태를 만드는 표면(작업 공간, 편집기, 대시보드, 파일 목록)을 만드는 에이전트. \"이걸 저장해야 하나\"를 판정하는 데 쓴다.\r\n\r\n## 왜 데모가 아니라 지식으로 착지했나\r\n\r\n이 규칙은 **값어치가 화면에 나타나지 않는다.** 다시 방문했을 때에만 드러나므로 한 화면짜리 데모로 보여 줄 수 없다. 계약 §1 의 「시각이 아니라 규칙이 값어치인 기법」 경로를 따랐다(선례 [[adjacency-level-lod]]).\r\n\r\n## 규칙\r\n\r\n**사용자가 만든 배치는 남기고, 방금 무엇을 했는지의 흔적은 버린다.**\r\n\r\n가르는 질문은 하나다: **이것을 잃으면 사용자가 다시 해야 하는 일이 생기는가.** 창을 다시 배치하고, 정렬을 다시 고르고, 뷰 모드를 다시 바꾸는 것은 노동이다. 반면 어느 창이 맨 위였는지는 다시 만들 필요가 없다 — 다음에 클릭하는 것이 맨 위가 되면 그만이다.\r\n\r\n## 관측된 경계 (daedalOS `SessionData`)\r\n\r\n| 남긴다 | 버린다 |\r\n|---|---|\r\n| 창의 위치·크기 (`windowStates`) | 창 겹침 순서 (`stackOrder`) |\r\n| 아이콘 위치 (`iconPositions` — 격자 좌표, [[adjacency-level-lod]] 참조) | 지금 활성인 창 (`foregroundId`) |\r\n| 디렉터리별 정렬 (`sortOrders`) | 세션 로드 완료 여부 (`sessionLoaded`) |\r\n| 디렉터리별 뷰 모드 (`views`) | |\r\n| 최근 항목·실행 이력 (`recentFiles`·`runHistory`) | |\r\n| 테마·배경 (`themeName`·`wallpaperImage`·`wallpaperFit`) | |\r\n\r\n버리는 셋은 런타임 컨텍스트(`SessionContextState`)에만 있고 저장 스키마(`SessionData`)에는 아예 없다 — **저장을 잊은 것이 아니라 타입 수준에서 분리해 둔 것**이다. 이 분리 자체가 규칙의 표현이다.\r\n\r\n## 적용\r\n\r\n- **저장 목록을 타입으로 분리하라.** \"저장되는 것\"과 \"런타임에만 있는 것\"을 한 객체에 섞으면 무엇이 지속되는지 읽을 수 없고, 실수로 순간 상태가 디스크에 눌어붙는다.\r\n- **위치는 가능하면 상대 좌표로.** 픽셀로 저장한 배치는 다음 방문의 화면 크기가 다르면 무너진다. 격자·비율·앵커 기준이 있으면 그쪽을 쓴다([[adjacency-level-lod]]).\r\n- **되살릴 때도 경계 검사를 다시 한다.** 저장 시점에 유효했던 위치가 지금도 유효하다는 보장이 없다(화면이 작아졌을 수 있다). 복원 값을 그대로 믿지 말고 현재 뷰포트로 클램프한다.\r\n- **쓰기 가능 여부를 전역 플래그로 잠글 수 있게 하라.** daedalOS 는 `window.sessionIsWriteable` 로 세션 저장을 통째로 막는다 — 초기화 중이거나 사용자가 리셋을 원할 때 저장 경로 하나만 끄면 된다.\r\n\r\n## 언제 쓰지 않나\r\n\r\n- 사용자가 배치를 만들지 않는 표면(읽기 전용 문서, 랜딩)에는 지속시킬 것이 없다.\r\n- 공유 링크로 도달하는 화면에서는 **URL 이 상태의 정본**이어야 한다 — 세션 저장이 URL 을 이기면 링크를 열 때마다 남의 배치가 덮인다.\r\n- 민감한 내용(최근 항목·검색 이력)은 지속의 이득보다 노출 위험이 클 수 있다. 남길 값의 성격을 따로 판정한다.\r\n\r\n## 방문 사실도 지속의 대상이다 (M67 2026-08-18)\r\n\r\n위 표는 **사용자가 만든 배치**를 다룬다. 그런데 지속시킬 값이 하나 더 있다 — **이 사람이 여기 와 본 적이 있는가.** 배치가 아니라 방문 사실이고, 그것을 남기면 대기 연출을 두 번 시키지 않을 수 있다.\r\n\r\nNORMAL IS BORING 은 `localStorage` 플래그 하나(`first_charge`)로 초회와 재방문에 **완전히 다른 타이밍**을 태운다(관측 2026-08-17, `research/m67-batch2-css/p2-ledger.json` 행 `nib-02`):\r\n\r\n| | 초회 | 재방문 |\r\n|---|---|---|\r\n| 진행률 | 실제 이미지 로드율을 5ms 카운터가 뒤쫓는다(앞지르지 못하게 클램프) | 진행 바·숫자를 즉시 감춘다 |\r\n| 본문 | 로드 완료 후 노출 | **즉시** 노출 |\r\n| 브랜드 | 카운터와 함께 재생 | 0.5초 뒤 로고만 다시 조립 |\r\n| 상호작용 개방 | 완료 + 1.5초 | 3초(고정) |\r\n\r\n**규칙: 대기 연출의 값어치는 첫 만남에 있다.** 두 번째 방문자는 그 연출을 이미 봤으므로 같은 시간을 다시 낼 이유가 없다 — 브랜드는 짧게 되풀이하고 본문은 먼저 준다.\r\n\r\n- 가르는 질문은 위와 같다: **이것을 잃으면 사용자가 다시 해야 하는 일이 생기는가.** 방문 사실을 잃으면 사용자는 **기다리는 일**을 다시 한다.\r\n- 다만 이것은 배치와 달리 **잃어도 치명적이지 않다** — 플래그가 없으면 초회 경로로 떨어질 뿐이다. 그래서 저장 실패를 방어할 필요가 없다.\r\n- 적용처: [[fake-progress-preloader]]·[[multi-stage-entry-loader]]·[[reward-reveal-loader]] 세 recipe 가 전부 「매번 같은 대기」를 전제한다. 이 규칙이 그 셋의 적용 조건을 정한다.\r\n\r\n## 관련\r\n\r\n[[adjacency-level-lod]](격자 기준 저장 — 이 문서의 \"상대 좌표로\" 항목의 구체 사례) · `docs/design-system/reverse-engineering-contract.md` §1(규칙형 기법의 착지 판정).\r\n\r\n## Changelog\r\n\r\n- 2026-08-18: M67 배치 2 회차 — 「방문 사실도 지속의 대상이다」 절 추가(NORMAL IS BORING 초회/재방문 분기 관측). 흡수 판정 A, 데모가 아니라 지식으로 착지한 이유는 초판과 같다 — 값어치가 재방문에만 드러난다.\r\n- 2026-08-17: 초판 — M60 회차 6(daedalOS) 처분 K1 의 지식 착지. 데모 없이 규칙만 남긴다."},{"id":"slide-principles","title":"Slide Principles","description":"What makes a good presentation slide: 5 convergent principles from 8 master lineages (one message per slide, title-as-assertion with Penn State evidence, visual evidence over bullets, audience-first editing, structure before design) with evidence grades, plus HTML-native expressive techniques (SVG filters, chart/3D embeds) and their caveats","tags":["slides","deck","presentation"],"source_path":"knowledge/slide-principles.md","body":"# Slide Principles — 무엇이 좋은 발표 슬라이드인가\n\n> 발표 슬라이드의 콘텐츠·구조 원칙. 거장 방법론 8계보가 수렴하는 5원칙과 표현 기법을 담는다.\n> 제작 절차(\"어떻게 만드는가\")는 [slide-production](../methodology/slide-production.md), 캔버스·대비·통설 규칙의 **게이트 정본**은 [slide-spec](../docs/design-system/slide-spec.md) — 이 문서는 게이트를 재서술하지 않는다.\n> 일반 지식 원본(KG): `nodes/디자인/slide-deck-convergent-principles.md` — 여기는 Askewly Design 적용 관점만 적는다.\n\n## 수렴 원칙 5\n\nMinto(피라미드 원칙)·Michael Alley(assertion-evidence)·맥킨지(action title)·Nancy Duarte(slide:ology/Resonate)·Garr Reynolds(Presentation Zen)·Guy Kawasaki(10/20/30)·스티브 잡스 키노트 분석(카민 갤로)·speaking.io(Zach Holman) — 표현만 다를 뿐 같은 곳으로 수렴한다. (전체 출처 장부: `research/2026-07-28-sl1-slide-methodology-research.md`, 접근일 전부 2026-07-28)\n\n| # | 원칙 | 계보 | 에이전트 규칙화 |\n|---|---|---|---|\n| 1 | **슬라이드당 메시지 하나** — 메시지가 둘이면 슬라이드도 둘 | 맥킨지 · Alley · Jobs | 자동 검사 가능 |\n| 2 | **제목이 곧 주장** — 명사구 제목(\"결과\", \"개요\") 금지, 완결 문장. 제목만 읽어도 스토리가 통해야 함(title-only read test) | Alley(assertion) · 맥킨지(action title) · Minto(결론 우선) | 자동 검사 가능 |\n| 3 | **텍스트 대신 시각 증거** — 불릿 나열 지양, 그래프·다이어그램·이미지가 주장을 증명 | Alley(evidence) · Reynolds · Duarte · Jobs · Kawasaki(30pt 하한) | 부분 검사 가능 (비율 휴리스틱) |\n| 4 | **청중 관점 편집** — 발표자가 하고 싶은 말이 아니라 청중이 받아야 할 말. MECE + 핵심 메시지 의도적 반복 | Duarte · Minto(MECE) · speaking.io | 프로세스 규칙 |\n| 5 | **구조 확정이 디자인보다 먼저** — 청중 분석 → 논리 구조(SCQA) → 스토리보드 → 그다음 슬라이드 도구 | Duarte · Minto · Reynolds · Jobs(과잉 리허설) | 프로세스 규칙 (순서 강제) |\n\n근거 무게는 같지 않다 — [slide-spec](../docs/design-system/slide-spec.md)의 등급 철학을 따른다:\n\n- **원칙 2는 실증이 있다**: Penn State assertion-evidence 연구가 전통 불릿 슬라이드 대비 이해도·기억 회상 우위를 보고 (Michael Alley 계열, ASEE: `https://peer.asee.org/assertion-evidence-slides-appear-to-lead-to-better-comprehension-and-recall-of-more-complex-concepts.pdf`, 접근일 2026-07-28).\n- **수치 규칙은 대부분 통설(folklore)이다**: Kawasaki 10장/20분/30pt(원저자 `https://guykawasaki.com/the_102030_rule/`, 접근일 2026-07-28)는 저자는 특정되나 숫자의 실증이 없다 — slide-spec §3이 24pt를 `folklore` 등급 옵트인 경고로 두는 것과 같은 이유로, 이 수치들을 차단 규칙으로 승격하지 않는다.\n- speaking.io는 반대 방향의 수치 조언을 준다 — 장수를 줄이지 말고 **장당 정보 밀도**를 줄여라(`https://speaking.io/plan/number-of-slides/`, 접근일 2026-07-28). \"10장 이내\"와 충돌하는 게 아니라, 제약 대상이 장수가 아니라 밀도임을 보여준다.\n\n## 표현 기법 — HTML이 제작 표면일 때 열리는 것\n\n출처: 코딩애플 영상 2건 요약(사용자 제공 2026-07-28) — ① \"관종이 될 수 있는 PPT\" `https://www.youtube.com/watch?v=2kdo2ZLTG_E` ② \"진정한 남자는 포토샵 대신 html 쓴다\" `https://www.youtube.com/watch?v=RrYPBkmnUwc`.\n\n- **PPT 대비 이점**: 애니메이션 자유도, Chart.js 차트, Three.js 3D, 실시간 데이터 반응, 브라우저·모바일 대응, 깃허브 페이지 배포. 이 레포의 [expressive-stack](expressive-stack.md) 티어와 recipe 층([motion-principles](motion-principles.md) 원칙 포함)이 슬라이드에도 그대로 표현 재료가 된다.\n- **SVG 필터 기법**: feTurbulence + displacement map(물결·자글거림 뒤틀기), feGaussianBlur + feColorMatrix(슬라임/liquid 병합), `<animate>`(프레임 작업 없는 필터 속성 애니메이션). 임팩트 장면용 — 본문 가독 영역에는 쓰지 않는다.\n- **보안 주의**: SVG 필터는 픽셀 단위 색상 조작이 가능해 클릭재킹(가짜 UI 겹치기)에 악용된 실사례가 구글 보안 리포트에 있다 — 외부 SVG/필터 코드를 슬라이드에 반입할 때 출처 검증.\n- **실전 주의 3가지** (HTML 발표에서 매번 명시해야 하는 것): 한글 폰트 지정, 글자 크기, 다크모드 대비 — 기본값에 맡기면 export에서 깨진다. 레이아웃이 많아지면 파일 분리(단일 파일 비대화 방지).\n\n## 관련\n\n- 제작 절차·export 경로·엔진 선택: [slide-production](../methodology/slide-production.md)\n- 게이트(캔버스 프리셋·WCAG 대비·통설 규칙 옵트인): [slide-spec](../docs/design-system/slide-spec.md) · 매체 판정: [medium-taxonomy](../docs/design-system/medium-taxonomy.md)\n- KG 일반 지식: `slide-deck-convergent-principles` · `html-first-slide-export-pipeline` (`~/projects/knowledge-graph`)\n\n## Changelog\n\n- 2026-07-28: 초판 (SL1 step-1 — 리서치 `research/2026-07-28-sl1-slide-methodology-research.md` 흡수)."},{"id":"spacing-and-alignment","title":"Spacing & Alignment","description":"Why an interface reads as tidy: pick one base unit and use only its multiples (the number itself is not the rule — measured grids are 4, 5, and 8px across benchmarks), derive the rest instead of defining it, align labels/icons/controls on shared axes, and treat whitespace as a grouping and importance signal rather than decoration","tags":["spacing","layout","alignment","tidiness"],"source_path":"knowledge/spacing-and-alignment.md","body":"# Spacing & Alignment — 정돈돼 보이게 만드는 규율\r\n\r\nDate: 2026-08-12\r\nMilestone: M48 (plan: `plans/2026-08-12-m48-visual-benchmark-absorption.md`)\r\n지위: 살아있는 정본. \"왜 이 화면은 정돈돼 보이고 저 화면은 어수선해 보이는가\"를 여백·정렬 축에서 판정한다. 이것은 **기반층**(판정 축)이지 시그니처가 아니다 — 특색은 [[identity-layers]] 의 시그니처층 소관이고 이 문서는 값을 정하지 않는다.\r\n근거: `research/2026-08-12-visual-benchmark-references.md` (Linear·Stripe·Apple 1차 + Notion·Figma·Attio·Vercel·Framer 보강, 출처 URL+접근일 전건 · 실브라우저 계측 포함).\r\n소비처: M44 자산 전수 점검의 판정 기준 — **미실증**(M48 시점에 M44 가 아직 실행되지 않았다).\r\n\r\n인접 문서 경계: 밀집 화면의 소음 판정은 [[dashboard-density]], 랜딩 섹션의 배경 밴드 리듬은 [[landing-section-rhythm]], 슬라이드 지면 격자는 [[deck-layout-vocabulary]] 가 소유한다. 이 문서는 매체를 가리지 않는 **여백값의 결정 방식과 정렬**만 다룬다.\r\n\r\n## 0. 대원칙 — 정돈은 값이 아니라 구조에서 온다\r\n\r\n벤치마크 5종의 여백 격자를 실측하면 **8px 이 보편이 아니다**: Stripe 8 · Notion 8 · Figma 8 · **Attio 4** · **Framer 5**. 그런데 다섯 다 정돈돼 보인다. 공유되는 것은 숫자가 아니라 **\"기본 단위를 하나 정하고 그 배수만 쓴다\"** 는 규율이다.\r\n\r\nStripe 는 이것을 제3자에게 노출하는 계약으로까지 만들었다: \"The base spacing unit that all other spacing is derived from. Increase or decrease this value to make your layout more or less spacious.\" (Stripe Elements Appearance API, 접근일 2026-08-12)\r\n\r\n> **왜 정돈돼 보이는가**: 값이 배수로 갇히면 눈이 값의 종류를 세지 않아도 되고, 인접한 두 간격의 관계가 자동으로 정수비가 된다. 값이 자유로우면 뇌가 매번 \"이건 저것과 같은가 다른가\"를 판정해야 한다.\r\n\r\n## 1. 결정표 — 여백\r\n\r\n| 상황 | 규칙 | 왜 |\r\n|---|---|---|\r\n| 프로젝트 시작 | **기본 단위 1개를 먼저 정한다.** 4·5·8 어느 것도 정답이며, **정한 뒤 배수만 쓰는 것**이 규칙이다 | 값의 종류가 줄면 어긋남이 눈에 안 띈다 |\r\n| 새 간격이 필요 | 배수 안에 없으면 **값을 만들지 말고 가장 가까운 배수를 쓴다.** 배수로 표현이 안 되면 그건 간격 문제가 아니라 구조 문제다 | 예외값 하나가 다음 예외값을 정당화한다 |\r\n| 파생 가능한 값 | **정의하지 말고 파생시킨다** — 컴포넌트 패딩·gap 은 기본 단위의 함수로 쓴다 | 정의가 늘면 테마·표면이 늘 때마다 곱해진다 (Stripe `spacingUnit`·Linear 테마 변수 98→3, [[dark-mode]] §3 참조) |\r\n| 마케팅 ↔ 제품 표면 | **격자는 공유하고 진폭만 바꾼다.** 같은 단위의 큰 배수(마케팅)와 작은 배수(제품) | Stripe 실측: 마케팅 16/24/32/64/96, 문서 4/8/16 — 같은 8의 배수 골격 |\r\n| 중요한 정보 | **공간을 주는 것이 곧 중요도 선언이다** — 중요한 것 주위를 비운다 | \"Make essential information easy to find by giving it sufficient space.\" (Apple HIG Layout) |\r\n| 그룹 표현 | 관련 항목은 **여백으로 먼저 묶는다** — 선·배경은 그다음 | \"you might use negative space, background shapes, colors, materials, or separator lines\" — Apple 의 열거 순서 자체가 규율이다 (HIG Layout). [[dashboard-density]] §2-1 과 같은 순서 |\r\n\r\n## 2. 결정표 — 정렬\r\n\r\n정렬은 이 문서에서 가장 강한 근거를 가진 항목이다. 서로 다른 두 조직이 서로 다른 문맥에서 **\"정돈돼 보인다\"의 실체로 정렬을 지목**했다.\r\n\r\n> \"Align components with one another to make them easier to scan and to communicate organization and hierarchy. **Alignment makes an app look neat and organized** and can help people track content while scrolling or moving their eyes.\" (Apple HIG Layout, 접근일 2026-08-12)\r\n\r\n> \"I also spent time aligning labels, icons, and buttons, both vertically and horizontally in the sidebar and tabs.\" (Linear, How we redesigned the Linear UI, 접근일 2026-08-12 — 좁은 표면의 밀도를 관리한 실작업이 여백값 조정이 아니라 정렬이었다)\r\n\r\n| 상황 | 규칙 | 왜 |\r\n|---|---|---|\r\n| 요소가 많은 좁은 표면(사이드바·툴바·탭) | **정렬을 먼저 손본다.** 여백을 늘리기 전에 라벨·아이콘·컨트롤이 공통 축에 걸리는지 본다 | 좁은 표면에서는 여백을 늘릴 자리가 없다 — 정돈은 정렬로만 얻는다 |\r\n| 축의 개수 | 화면당 **수직·수평 정렬 축을 세지 못할 만큼 늘리지 않는다.** 셀 수 없으면 축이 없는 것이다 | 축이 많으면 정렬이 아니라 우연이 된다 |\r\n| 아이콘과 텍스트 | 광학 중심으로 맞춘다 — 바운딩 박스가 아니라 **보이는 무게**를 기준으로 | 기하학적 중앙 정렬이 눈에는 어긋나 보이는 경우가 흔하다 |\r\n| 들여쓰기 | 위계를 나타낼 때만 쓰고, 위계 단계와 들여쓰기 단계를 1:1 로 묶는다 | \"Along with indentation, alignment can also help people understand an information hierarchy.\" (Apple HIG Layout) |\r\n| 값이 표로 늘어설 때 | 숫자는 오른쪽·소수점 정렬, 텍스트는 왼쪽 | 스캔 시 눈이 한 축만 따라가면 된다 |\r\n\r\n## 2-1. 하한만 지키면 정돈이 아니라 밋밋함이 된다\r\n\r\n§1·§2 는 전부 **줄이는 규칙**이다 — 값의 종류를 줄이고 축을 맞춘다. 이것만 지키면 어긋남은 사라지지만 위계도 함께 사라진다.\r\n\r\n실측 근거: BK3 에서 취향 기각된 recipe 7종을 재보니 간격 값 종류가 1~5개, 반경 1종, 글자 크기 2~3단이었다 — **줄이는 규칙을 전건 통과한 상태에서 기각됐다**(`research/2026-08-12-visual-benchmark-references.md` §9-2). 규칙이 기각을 하나도 설명하지 못했다.\r\n\r\n| 확인 | 질문 |\r\n|---|---|\r\n| 간격 진폭 | 가장 촘촘한 간격과 가장 넓은 간격의 비가 화면의 위계 단계 수만큼 벌어져 있나 — 전부 같은 배수면 그룹이 없는 것이다 |\r\n| 여백의 역할 | 여백이 **무엇이 중요한지 말하고 있나.** 균일한 여백은 \"다 똑같이 중요하다\"는 선언이고, 그것은 대개 거짓이다 |\r\n| 진폭의 값 | 얼마나 벌릴지는 **취향이라 기반층이 정하지 않는다** — 이 문서는 \"벌려야 한다\"까지만 말한다([[identity-layers]] 시그니처층) |\r\n\r\n## 2-2. 그리드 공식은 컴포넌트 폭까지 지배한다 (M67 2026-08-18)\r\n\r\n격자를 레이아웃에만 쓰면 그 밖의 표면(모달·팝오버·시트)은 결국 임의 폭을 갖게 된다. 그러면 정렬 축이 화면 한가운데서 끊긴다.\r\n\r\nNORMAL IS BORING 실측(2026-08-17, `research/m67-batch2-css/p2-ledger.json` 행 `nib-05`·`nib-10`):\r\n\r\n```\r\n.col-N { width: calc((99.9vw - var(--wrapper-padd)*2 - var(--grid-gap)*11) / 12 * N + var(--grid-gap)*(N-1)) }\r\n.modal--contact .modal__content { width: calc(... / 12 * 5 + var(--grid-gap)*4 + var(--grid-gap)*2.5) }\r\n```\r\n\r\n- **컬럼 수는 브레이크포인트에서 바뀌지 않는다.** 항상 12분할이고, 스텝하는 것은 여백·거터 토큰뿐(`--grid-gap 1.5rem → 0.75rem`, `--wrapper-padd 1.65rem → 0.75rem`). 「반응형 = 컬럼을 접는 것」이 유일한 답은 아니다.\r\n- **모달 폭이 같은 공식의 5칸이다.** 임의 `max-width` 가 아니라 격자에 물려 있어, 어떤 화면 폭에서도 본문과 모달의 좌우가 같은 축을 공유한다.\r\n- **radius 도 같은 토큰(`--border-radius`)을 공유해 함께 스텝한다.** 화면이 좁아지면 격자와 모서리가 같이 작아진다.\r\n\r\n**규칙: 격자 공식을 하나 정했으면 컴포넌트 폭도 그 공식의 항으로 쓴다.** 적용처 — [[responsive-content-grid]]·[[popover-anatomy-variants]]·[[mobile-buy-bar-sheet]] 가 현재 폭을 각자 정하고 있다.\r\n\r\n## 3. 하지 않는 것\r\n\r\n- **특정 제품의 여백 값을 이식하지 않는다.** 이 문서는 \"8px 을 써라\"고 말하지 않는다 — 단위를 하나 정하라고 말한다. look 은 프로젝트 토큰 소유(component-restyle 대원칙).\r\n- **밀도 자체를 규칙으로 삼지 않는다.** \"조밀하되 구조적으로\"는 표면 종류의 함수라 보편 규칙이 못 된다 — Apple 은 부차 정보를 다른 곳으로 옮기라 하고([[dashboard-density]] 가 인용한) Linear 는 정보를 지키고 소음을 줄이라 한다. 두 처방이 반대다. 이 문서가 채택한 보편항은 밀도가 아니라 **정렬**이다.\r\n\r\n## 판정 절차 (에이전트 의무)\r\n\r\n1. 화면을 만들기 전에 **기본 단위를 선언한다.** 프로젝트 토큰에 이미 있으면 그것을 쓰고, 없으면 하나 정한 뒤 문서에 적는다.\r\n2. 완성된 화면에서 **쓰인 간격값의 종류를 센다.** 기본 단위의 배수가 아닌 값이 있으면 근거를 대거나 배수로 되돌린다.\r\n3. **간격 진폭을 잰다** — §2-1. 모든 간격이 같은 배수면 그룹 위계가 없는 것이다.\r\n4. **정렬 축을 센다.** 수직·수평 각각 몇 개인지 말할 수 없으면 정렬이 없는 것이다 — §2 첫 행부터 다시 본다.\r\n5. \"어수선하다\"는 진단을 받으면 **여백을 늘리기 전에 정렬을 먼저 본다.** 좁은 표면에서는 이것이 유일한 수단이다.\r\n6. 마케팅·제품 표면을 함께 만들 때 **격자를 갈라 놓지 않는다** — 진폭만 다르게 한다.\r\n\r\n## Changelog\r\n\r\n- 2026-08-18: M67 배치 2 회차 — §2-2 신설(그리드 공식이 컴포넌트 폭까지 지배한다). 흡수 판정 A, 값이 아니라 공식의 적용 범위를 옮긴다.\r\n- 2026-08-12: M48 step-9 — §2-1 신설(하한만 지키면 밋밋해진다 — BK3 7종 소급 반증에서 도출), 판정 절차 3 추가·번호 재배치.\r\n- 2026-08-12: 초판 (M48) — Linear·Stripe·Apple 1차 근거 + Notion·Figma·Attio·Framer 보강 실측. 「8px 격자」 가설을 「단일 기본 단위」로 수정하고 정렬을 정돈의 실체로 채택."},{"id":"type-hierarchy","title":"Type Hierarchy","description":"How to build a readable type hierarchy without making the interface look generated: keep one typeface family by default (mixing families obscures hierarchy), pick ONE channel to carry the hierarchy — size amplitude, weight, or step count — rather than pushing all three at once, cap the number of sizes on a screen, and treat a second typeface as a signature-layer decision, not a tidiness one","tags":["typography","hierarchy","tidiness"],"source_path":"knowledge/type-hierarchy.md","body":"# Type Hierarchy — 글자 위계 판정 규칙\n\nDate: 2026-08-12\nMilestone: M48 (plan: `plans/2026-08-12-m48-visual-benchmark-absorption.md`)\n지위: 살아있는 정본. \"이 화면의 글자 위계가 제대로 서 있는가\"를 판정한다. **기반층**(판정 축)이며 특정 서체·크기 값을 정하지 않는다 — 서체 선택 자체는 [[identity-layers]] 시그니처층 소관이다.\n근거: `research/2026-08-12-visual-benchmark-references.md` §5-1~§7 (Apple HIG Typography 1차 + Linear·Stripe·Figma·Notion 실측, 출처 URL+접근일 전건).\n소비처: M44 자산 전수 점검의 판정 기준 — **미실증**.\n\n인접 문서 경계: 커스터마이저가 타이포를 **어느 축까지 노출하는가**는 [[customizer-and-packaging]] §1 이 소유한다(그 문서의 \"서체 한 가족\" 규칙이 이 문서의 §1 과 같은 결론이며, 여기서는 근거와 예외를 붙인다). 모션 중 글자 가독성은 [[motion-principles]], 밀집 화면의 2단 텍스트 위계는 [[dashboard-density]] §3-2 소관이다.\n\n## 1. 서체 가족 — 기본값은 하나\n\n> \"Minimize the number of typefaces you use, even in a highly customized interface. **Mixing too many different typefaces can obscure your information hierarchy and hinder readability**, in addition to making an interface feel internally inconsistent or poorly designed.\" (Apple HIG Typography, 접근일 2026-08-12)\n\n이 문장이 주는 것은 금지가 아니라 **인과**다. 서체를 섞으면 위계가 흐려지는 이유는 서체마다 같은 크기에서 보이는 무게가 다르기 때문이다 — 크기로 세운 순서를 서체 차이가 무너뜨린다.\n\n**예외는 있고, 그것은 정돈이 아니라 표현의 선택이다.** Linear 는 헤딩에만 표현형 서체를 들였다(\"to add more expression to our headings while maintaining their readability\", 접근일 2026-08-12). 판정 기준이 \"가독성을 유지하면서\"였다는 점이 중요하다 — 표현을 위해 위계를 깎지는 않았다.\n\n| 상황 | 규칙 |\n|---|---|\n| 기본 | **한 가족.** 위계는 크기·굵기로 만든다 |\n| 두 번째 가족을 들이고 싶을 때 | ① 역할을 **하나로** 한정한다(예: 헤딩 전용) ② 본문에는 손대지 않는다 ③ 이것은 **시그니처층 결정**이므로 기반층 규칙으로 정당화하지 않는다 |\n| 공용 플랫폼 안에 사는 앱 | 시스템 서체를 쓴다 — 주변 앱과의 일관성이 비용이다 (Apple HIG) |\n\n## 2. 위계는 채널 하나로 만든다\n\n실측된 경로가 셋이고, 셋 다 성공한다. **중요한 것은 어느 채널을 골랐느냐가 아니라 하나를 골랐다는 것이다.**\n\n| 경로 | 방식 | 실측 |\n|---|---|---|\n| **크기 진폭** | 굵기를 거의 안 쓰고(가는 값 하나로 고정) 크기를 크게 벌린다 | Stripe 홈: 본문 굵기가 사실상 한 값, 크기 16→48(3배). Figma: 굵기 400 미만 값을 본문에 쓰고 크기 16→48 |\n| **굵기 단** | 크기 범위를 좁히고 굵기를 3~4단으로 쓴다 | Stripe 문서: 크기 14~24 로 좁고 굵기 4단. Attio: 한 굵기를 기본값으로 놓고 위아래로 조금 |\n| **단계 축소 + 여백** | 크기 종류를 2단 수준으로 줄이고 나머지를 여백이 가른다 | Notion: 사실상 2단. Apple HIG 도 이쪽 |\n\n**⚠ 고른 채널은 실제로 벌려야 한다 — 종류를 줄이는 것만으로는 위계가 안 생긴다.** 이 조항은 소급 반증에서 나왔다. BK3 에서 취향 기각된 recipe 7종을 재보니 **전건이 크기 2단·굵기 1종**이었다(`research/2026-08-12-visual-benchmark-references.md` §9-2). 종류를 줄이라는 규칙은 완벽히 지켰는데 채널을 고르지 않아 진폭이 0이었고, 결과는 정돈이 아니라 밋밋함이었다. 위 세 경로의 실측 진폭을 하한 감각으로 삼는다 — 크기 진폭 경로는 본문에서 최상위 헤딩까지 **3배 안팎**, 굵기 단 경로는 **3~4단**, 단계 축소 경로는 단계를 줄이는 대신 **여백이 그 몫을 대신하는지**를 확인한다.\n\n**세 채널을 동시에 밀면 위계가 아니라 소란이 된다.** 크기도 벌리고 굵기도 4단이고 색도 3단이면, 각 채널이 서로를 상쇄해 어느 것이 primary 인지 읽히지 않는다.\n\n## 3. 결정표\n\n| 판정 | 규칙 | 왜 |\n|---|---|---|\n| 한 화면의 크기 종류 | **셀 수 있어야 한다.** 못 세면 스케일이 없는 것이다 | 값이 자유로우면 눈이 매번 \"이건 저것과 같은 급인가\"를 판정한다 |\n| 세 번째 회색이 필요할 때 | 위계 설계 실패 신호 — 구조를 다시 본다 ([[dashboard-density]] §3-2) | 색으로 못 만든 위계를 색을 더해 만들 수는 없다 |\n| 가는 굵기를 쓸 때 | **권장보다 크게 잡는다** — \"If you use a custom font with a thin weight, aim for larger than the recommended sizes to increase legibility.\" (Apple HIG Typography) | 굵기를 내리면 가독성 예산이 줄고, 그 예산은 크기로만 되산다 |\n| 최소 크기 | 플랫폼 최소값을 하한으로 삼는다. Apple 공표값: iOS·iPadOS 17pt 기본 / 11pt 최소, macOS 13pt / 10pt, visionOS 17pt / 12pt (HIG Typography) | 값 이식이 아니라 **하한 참조**다 — 웹은 해당 플랫폼 규범을 따로 확인한다 |\n| 크기 파생 | 단계값을 손으로 나열하지 말고 기준 크기에서 파생시킨다 | Stripe 계약: \"other font size variables such as `fontSizeXs` or `fontSizeSm` are scaled from this value using `rem` units\" (Appearance API) |\n| 크기 변경 대응 | 사용자가 글자를 키우면 **전부 키우지 않는다** — 중요한 것만 | \"Prioritize important content when responding to text-size changes.\" (Apple HIG Typography) |\n\n## 4. 하지 않는 것\n\n- **특정 제품의 서체 이름을 규칙에 적지 않는다.** 이 문서는 \"헤딩용 표현형 서체를 하나 둘 수 있다\"고 말하고 그것이 무엇인지는 프로젝트가 정한다.\n- **\"강한 타이포 위계\"를 단일 규칙으로 쓰지 않는다.** 실측 경로가 셋이고 서로 반대 방향(굵기를 내리는 쪽 / 굵기 단을 늘리는 쪽)이라 하나로 뭉치면 틀린다.\n\n## 판정 절차 (에이전트 의무)\n\n1. 화면을 만들기 전에 **어느 채널로 위계를 만들지 하나 선언한다**(크기 진폭 / 굵기 단 / 단계 축소).\n2. 완성 후 **크기 종류와 굵기 종류를 센다.** 선언한 채널 밖에서 종류가 늘었으면 되돌린다.\n2-1. **선언한 채널의 진폭을 잰다.** 크기 2단·굵기 1종으로 끝났으면 채널을 고르지 않은 것이다 — §2 의 경고 조항으로 되돌아간다.\n3. 서체 가족이 둘 이상이면 §1 표의 3조건을 통과하는지 확인하고, **시그니처층 결정임을 보고에 적는다.**\n4. 가는 굵기를 썼으면 크기를 올렸는지 확인한다.\n5. 크기 단계가 손으로 나열돼 있으면 기준값 파생으로 바꾼다.\n\n## Changelog\n\n- 2026-08-12: M48 step-9 — §2 에 진폭 조항 추가(BK3 7종 소급 반증에서 도출: 종류 축소만으로는 위계가 안 생긴다), 판정 절차 2-1 추가.\n- 2026-08-12: 초판 (M48) — Apple HIG Typography 인과 + Linear·Stripe·Figma·Notion 실측 3경로. `customizer-and-packaging.md` §1 의 \"서체 한 가족\" 결론에 근거와 예외를 붙였다."}]