@askewly/design 0.4.2 → 0.4.4
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.
- package/data/DESIGN.md +2 -0
- package/data/askewly.css +1 -0
- package/data/knowledge.json +1 -0
- package/data/tokens.css +1 -0
- package/data/tokens.json +1 -1
- package/dist/index.js +55 -1
- package/dist/kickstart.js +21 -2
- package/dist/load.js +25 -0
- package/package.json +1 -1
package/data/DESIGN.md
CHANGED
|
@@ -7,6 +7,7 @@ tokens:
|
|
|
7
7
|
color:
|
|
8
8
|
primitive:
|
|
9
9
|
white: { value: {"colorSpace":"oklch","components":[1,0,0]}, type: color }
|
|
10
|
+
black: { value: {"colorSpace":"oklch","components":[0,0,0]}, type: color }
|
|
10
11
|
gray:
|
|
11
12
|
"1": { value: {"colorSpace":"oklch","components":[0.985,0,0]}, type: color }
|
|
12
13
|
"2": { value: {"colorSpace":"oklch","components":[0.97,0.005,270]}, type: color }
|
|
@@ -61,6 +62,7 @@ tokens:
|
|
|
61
62
|
base: { value: "{color.primitive.gray.1}", type: color }
|
|
62
63
|
raised: { value: "{color.primitive.white}", type: color }
|
|
63
64
|
overlay: { value: "{color.primitive.white}", type: color }
|
|
65
|
+
scrim: { value: "{color.primitive.black}", type: color }
|
|
64
66
|
muted: { value: "{color.primitive.gray.3}", type: color }
|
|
65
67
|
secondary: { value: "{color.primitive.gray.4}", type: color }
|
|
66
68
|
tint: { value: "{color.primitive.askewly.mint}", type: color }
|
package/data/askewly.css
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
--color-border: var(--border);
|
|
35
35
|
--color-input: var(--input);
|
|
36
36
|
--color-ring: var(--ring);
|
|
37
|
+
--color-scrim: var(--scrim);
|
|
37
38
|
--color-primary-hover: var(--primary-hover);
|
|
38
39
|
--color-emphasis-surface: var(--emphasis-surface);
|
|
39
40
|
--color-emphasis-foreground: var(--emphasis-foreground);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"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":"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## 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## 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)"},{"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. **구분의 기본값 = 여백.** 보더·구분선은 이유 없이 증식한다 — 여백 → 배경 미차 → 라운딩 순으로 먼저 시도하고 선은 최후 수단.\n2. 선을 쓸 때는 저대비 + 그룹 경계에만(항목마다 긋지 않는다).\n3. 페이지 배경은 **2단 상한** — 2단째는 미묘한 구분이 필요할 때만 아껴서 (Geist Background 1/2).\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 은 프로젝트 토큰 소유.\n\n## Changelog\n\n- 2026-08-01: 초판 (M8) — Linear 리프레시 원문(2026-03-12)·Geist Colors 실브라우저 캡처 기반."},{"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":"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 — 표현 기술 티어 지도\n\nDate: 2026-07-17\nMilestone: VI1 (plan: `archive/plans/2026-07-17-vi1-expressive-stack-map.md`)\n지위: 살아있는 정본. 근거·출처 전체는 `research/2026-07-17-expressive-stack-genealogy.md`(동결 기록, 전 항목 출처 URL+접근일) — 이 문서는 판정 규칙만 담고 근거를 중복 서술하지 않는다.\n\nAudience: 코드를 생성하는 에이전트(Codex, Claude Code). \"이 시각 효과는 무엇으로 만드는가\"를 판정하는 데 쓴다.\n\n## 4개 렌더링 티어\n\n| 티어 | 정의 | 판별 신호 |\n|---|---|---|\n| **① CSS·SVG** (선언) | 브라우저가 선언만으로 렌더·애니메이션. JS 개입 없음 | 상태 A→B 전환, 정적/반복 시각, 스크롤 진행률 매핑 |\n| **② 모션 오케스트레이션** (JS 지휘 + CSS/DOM 렌더) | JS가 입력·측정·시퀀스를 계산하고 최종 렌더는 CSS transform/opacity | 커서 좌표·요소 측정·동적 시퀀스가 필요, 그러나 그리는 대상은 여전히 DOM |\n| **③ Canvas 2D·물리** | JS가 픽셀 버퍼에 직접 그림. 물리 엔진 포함 | 수백 개 이상 개체, 경로 이력, 상호 충돌 — DOM 개체로는 성능 불가 |\n| **④ WebGL·three.js** (GPU) | 셰이더 파이프라인. 3D·프레임 단위 절차적 픽셀 계산 | 조명·깊이·유기적 노이즈 애니메이션·유체 — CPU 픽셀 루프로 불가 |\n\n## 기법→티어 결정 표\n\n| 원하는 효과 | 티어 | 도구·기법 (근거: research doc 해당 절) |\n|---|---|---|\n| hover 시 색·그림자·크기 전환 | ① | CSS `transition` — JS 불필요 |\n| 유리 질감(글래스모피즘)·배경 블러 | ① | `backdrop-filter` + `mix-blend-mode` |\n| 메시/원뿔형 그라디언트 배경 | ① | 다중 radial-gradient 레이어링, `conic-gradient`, `@property`로 색 애니메이션 |\n| 형태 잘라내기·리빌 | ① | `clip-path: path()`, `mask-image` |\n| 스크롤 진행률에 매핑된 애니메이션 | ① | CSS scroll-driven animations (`animation-timeline`) — 단순 매핑이면 JS 불필요 |\n| 그레인/필름 노이즈 텍스처 | ① | SVG `feTurbulence` 오버레이 (CSS filter 단독으론 노이즈 생성 불가) |\n| 텍스트·이미지 왜곡 | ① | SVG `feTurbulence`+`feDisplacementMap` |\n| 커서 추종·마그네틱 hover | ② | 포인터 좌표→거리 계산→CSS transform 갱신 (CSS `:hover`는 커서 위치를 모름) |\n| 스프링·관성 있는 UI 모션 | ② | Framer Motion / react-spring (물리 파라미터 보간은 JS 런타임) |\n| 핀 고정·스크럽·스크롤 스토리텔링 | ② | GSAP ScrollTrigger (CSS scroll-driven보다 세밀한 제어) |\n| 리스트 재정렬·카드 확장 전환 | ② | FLIP 기법 (측정은 JS, 재생은 CSS transform) |\n| 페이지·상태 전환 모핑 | ①+② | View Transitions API (트리거 JS 1줄 + `::view-transition-*` CSS) |\n| 벡터 일러스트 애니메이션 | ② | Lottie (AE 제작 → lottie-web 재생) |\n| 다요소 등장 안무(stagger) | ①/② | 정적이면 CSS `animation-delay`, 동적 개수·인터럽트면 JS stagger |\n| 수백 개 파티클·커서 트레일 | ③ | Canvas 2D + rAF (DOM 개체로는 레이아웃 비용 폭발) |\n| 개체 간 충돌·낙하·물리 | ③ | matter-js (트윈으론 상호작용 불가) |\n| 제너러티브 아트·노이즈 드로잉 | ③ | p5.js / Canvas API |\n| 3D 씬·조명·제품 뷰어 | ④ | three.js / R3F + drei (CSS `perspective`는 조명·깊이버퍼 없음 — 유사 흉내만) |\n| 애니메이팅되는 유기적 셰이더 그라디언트 | ④ | GLSL / Paper Shaders (CSS 그라디언트는 프레임 단위 노이즈 불가) |\n| 유체 시뮬레이션 | ④ | WebGL-Fluid-Simulation (Navier-Stokes는 GPU 병렬 필수) |\n| 화면 전역 글로우·색수차 | ④ | @react-three/postprocessing |\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) |\n\n## 판정 절차 (에이전트 의무)\n\n1. **하위 티어 우선.** 같은 효과가 두 티어로 가능하면 항상 낮은 티어를 쓴다 — 예: 단순 hover에 JS 금지, 단순 스크롤 리빌에 GSAP 금지, 유사 3D(coverflow류)에 three.js 도입 금지(CSS `perspective`+`rotateY`로 충분한지 먼저 판정).\n2. **티어 상승에는 근거를 적는다.** ②→③→④로 올릴 때는 \"하위 티어로 안 되는 이유\"(커서 이력 필요, 상호 충돌 필요, 조명/노이즈 필요 등)를 코드 리뷰어가 볼 위치(PR/변경 기록)에 남긴다.\n3. **③·④ 티어는 reduced-motion 분기가 의무.** canvas/WebGL은 CSS와 달리 자동 억제되지 않는다 — `matchMedia('(prefers-reduced-motion: reduce)')`로 렌더 루프·자동 회전을 명시적으로 끄고, 스크린리더용 병렬 DOM 레이어를 제공한다.\n4. **④ 티어는 lazy-load가 의무.** three.js는 gzip ~155KB 고정 비용 — dynamic import + Suspense로 격리하고 초기 번들에 넣지 않는다.\n5. **시그니처 접점**: 실험적 표현(③·④ 티어 대부분, ②의 커서/마그네틱)은 운용 원칙 5 \"실험적 터치는 수동\" — 사용자가 명시로 요청했을 때만 도입하고, 기본 제품 UI에는 ① 티어와 ②의 실무 패턴(스프링·FLIP·stagger)까지만 기본값으로 쓴다.\n6. **모션은 전환 규모에 비례한다.** 작은 상태 변화(토글·선택)는 짧고 담백하게, 크고 주목할 전환(시트 확장·전면 이동)은 길고 표현적으로 — 작은 것에 과장 모션, 큰 것에 인색한 모션이 둘 다 실패다 (M3 easing/duration 판단 diff 채택, taste ledger T-16).\n\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)\n8. **모션의 \"무엇을·왜\"는 [[motion-principles]] 가 정본이다.** 이 문서는 \"무엇으로 만드나\"(티어·도구)만 판정한다 — 전환의 목적 선택·still appeal 게이트·타이포 가독성 등 품질 원칙은 `knowledge/motion-principles.md` 를 먼저 통과한다.\n\n## 인접 게이트 — ④ 티어가 외부 3D 에셋을 실을 때\n\n3D **씬 구현**(three.js/R3F)은 이 문서 소관이지만, 씬에 싣는 **에셋**(GLB/glTF·Gaussian splat)은 별도 게이트를 통과해야 한다 — 요지와 정본 위치만 적는다 (VI8 three.js recipe 확장 시 필수 참조):\n\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)\n- **AI 3D 생성 도구 선택**: 생성기는 완결 에셋이 아니라 upstream 후보 소스 — 입력 유형·품질·하드웨어·텍스처·배포 요건으로 레인을 고른 뒤 위 게이트에 반드시 짝지운다. → KG `nodes/AI/ai-3d-asset-generation-model-selection-gate.md` (접근일 2026-07-28)\n\n## 자체 쇼케이스 역산 (참조 구현)\n\naskewly.com 랜딩 Showcase Atlas 12종의 티어 분포 — 실코드 근거는 research doc 역산 표:\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.\n\n## Changelog\n\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`.\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 — 포커스·키보드 규율 판정 규칙\n\nDate: 2026-08-01\nMilestone: M10 (plan: `plans/2026-08-01-m10-radix-focus-keyboard-absorption.md`)\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` 참조.\n\nAudience: 에이전트(커스텀 위젯·오버레이 생성 시 필수 참조) + 사람.\n\n## 0. 대원칙 — 정답은 WAI-ARIA APG, 발명하지 않는다\n\n네이티브 요소를 div 로 대체하는 순간 시맨틱(role·aria)과 **행동**(키보드·포커스) 재구현이 전부 우리 책임이 된다. 각 패턴의 행동 정답은 WAI-ARIA authoring practices 에 이미 있다 — 키보드 동작을 창작하지 말고 APG 패턴을 찾는다.\n\n## 1. 모달 오버레이 계약 (트랩 + 반환)\n\n1. modal 이면 **포커스 트랩** — Tab 순환이 내부에 갇힌다. non-modal(popover 등)엔 트랩을 걸지 않는다.\n2. **닫힐 때 포커스는 연 트리거로 반환** — Esc·버튼·scrim 어느 해제 경로든 동일. 반환 없는 트랩은 미완성.\n3. **열림 직후 첫 논리 항목에 포커스** — 메뉴 = 첫 항목, 파괴적 확인 = 안전한 버튼(Cancel), 폼 모달 = 첫 필드. 포커스 이동이 곧 스크린리더 공지 채널이다.\n4. Title/Description 을 배선해 열림이 \"무엇인지\" 공지되게 한다.\n\n## 2. 복합 위젯 계약 (roving tabindex)\n\n탭 목록·메뉴·툴바·라디오 그룹 같은 복합 위젯은 **Tab stop 1개**다:\n1. Tab 은 위젯에 들어왔다(활성 항목) 다음 Tab 에 밖으로 나간다 — 항목마다 Tab 을 먹으면 FAIL.\n2. 내부 이동은 방향키(orientation 기준) + Home/End.\n3. 항목 목록엔 **typeahead** — 타이핑으로 매칭 항목 점프.\n4. 이동-활성화 결합(automatic activation)이 기본, 비용 큰 패널 전환만 Enter 분리(manual).\n\n## 3. 해제(dismiss) 계층 계약\n\n1. 오버레이 해제 경로는 **계약으로 명시**: Esc · 외부 클릭/scrim · 트리거 재활성. 셋의 동작이 예측 가능해야 한다.\n2. 겹친 레이어의 Esc 는 **최상위 하나만** 닫는다 — 스택 순서 명시.\n3. 모달 깊이·중첩 허용 예외는 [[mobile-navigation]] §4 를 따른다.\n\n## 4. 라벨 계약\n\n1. **이름 없는 커스텀 컨트롤 금지** — 시각 라벨이 있으면 연결(label/aria-labelledby), 없으면 aria-label. 프리미티브가 배선을 추상화해도 라벨 내용은 제품 책임이다.\n2. 상태 변화로 화면이 바뀌면 포커스 이동 + 라벨로 새 맥락을 공지한다(라이브 리전 남발 대신 포커스 채널 우선).\n\n## 판정 절차 (에이전트 의무)\n\n1. 커스텀 위젯을 만들기 전에 APG 에 해당 패턴이 있는지 먼저 확인한다(§0) — 있으면 그 키보드 표가 스펙이다.\n2. 모달을 만들면 §1 4항(트랩·반환·초기 포커스·공지) 전부 체크 — 하나라도 빠지면 미완성.\n3. 항목형 위젯이면 §2 로 Tab stop 수를 센다 — 위젯당 1개.\n4. 포커스 가시성 검증은 실키 Tab 으로 한다 — 프로그램적 focus() 는 :focus-visible 을 안 태워 오탐을 만든다(kg 실측).\n5. Radix 등 프리미티브의 기본 비주얼은 흡수 대상이 아니다 — 행동 계약만 가져오고 look 은 토큰 소유.\n\n## Changelog\n\n- 2026-08-01: 초판 (M10) — Radix Primitives(Introduction·Accessibility·Dialog·DropdownMenu·Tabs) 실브라우저 캡처 기반."},{"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":"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 — 컨테이너 선택 판정 규칙\n\nDate: 2026-08-01\nMilestone: M7 (plan: `plans/2026-08-01-m7-platform-guideline-absorption.md`)\n지위: 모바일 화면에서 내비게이션 컨테이너와 모달 표면을 고르는 판정 규칙의 정본. 근거 원문은 `research/2026-08-01-m7-mobile-nav-sheets-capture.md` 에 동결(출처 URL·접근일 포함) — 이 문서는 판정 규칙만 담고 원문을 재서술하지 않는다. Apple HIG × Material 3 를 대조해 플랫폼 공통 규칙과 분기 지점을 가른다.\n\nAudience: 에이전트(모바일 화면 생성 시 필수 참조) + 사람.\n\n## 1. 주 내비게이션 컨테이너 선택\n\n**화면당 주 내비게이션 컴포넌트는 1개다.** drawer + bottom bar 동시 사용은 안티패턴.\n\n| 조건 | 컨테이너 | 근거 |\n|---|---|---|\n| 최상위 목적지 3~5개 · compact(폰) | bottom navigation / tab bar | M3 3~5 규칙 · HIG 탭바 |\n| 목적지 3개 미만 | 페이지 내 tabs (주 내비 아님) | M3 — nav bar 금지 |\n| 목적지 5개 초과 · medium+ | navigation rail (확장형) 또는 사이드바 전환 | M3 Expressive · HIG sidebarAdaptable |\n| 목적지 5개 초과 · compact | 탭 커스터마이즈(기본 ≤5) 또는 rail 로 승격 | HIG iPadOS 커스터마이즈 |\n| 데스크톱/expanded | rail·사이드바 — bottom bar 금지 | M3 |\n| **navigation drawer 신규 채택** | **비권장** — 확장형 rail 이 대체 (M3 Expressive, 2026-08-01 확인) | 기존 제품 유지만 허용 |\n\n## 2. 주 내비게이션 행동 계약\n\n1. **탭 전환은 탭/클릭만** — 좌우 스와이프로 목적지를 전환하지 않는다(스와이프는 카드 캐러셀·행 액션 몫).\n2. **상태 계약을 명시적으로 정한다** — preserve(스크롤·하위 탭·검색 유지) vs reset. 섹션을 자주 오가는 앱은 preserve.\n3. **활성 목적지 재선택 = 최상단 스크롤.**\n4. **탭 숨김·비활성 금지** — 내용이 없으면 빈 섹션에 이유를 보여준다.\n5. **라벨 필수** — 1~2 단어, 줄바꿈·축소·제거 금지. 활성 표시는 1개만.\n6. **스크롤 숨김은 스크린리더 활성 시 끈다.** FAB 는 바 위에, 바를 가리지 않는다.\n7. **배지는 critical 정보만** — 남용하면 의미가 죽는다.\n\n## 3. 모달 표면 선택 (태스크 무게로 가른다)\n\n| 태스크 | 표면 | 핵심 계약 |\n|---|---|---|\n| 저중요 알림 (액션 선택적) | snackbar/toast | 비차단·자동 소멸 — dialog 금지 |\n| 고중요 결정·확인 | dialog | 액션 ≤2(확인 트레일링) · 제목은 구체 질문 · 제3의 이동 버튼 금지 |\n| 맥락 밀착 짧은 선택·목록 | modal bottom sheet | 초기 높이 ≤50% · scrim 탭/스와이프 dismiss · 초과분은 풀확장+내부 스크롤 |\n| 메인 UI 와 병행 사용 | standard bottom sheet (nonmodal) | scrim 없음 · 동시 상호작용 (예: 미니 플레이어) |\n| 키보드 폼·비즉시 저장·다단 작성 | full-screen dialog (compact 전용) | 확인 = 구체 동사(Send/Create) · 비활성화 금지 · 미저장 닫기 확인 · 큰 화면은 basic dialog 로 스왑 |\n| 리사이즈 가능한 시트 | detent + grabber | medium detent = 점진 공개 · compose 류는 full-height 만 · grabber 가 스크린리더 리사이즈 경로 |\n\n## 4. 모달 깊이 규칙\n\n- **모달 깊이 = 1.** 시트 위에 시트를 쌓지 않는다 — 첫 시트를 닫고 다음을 연다.\n- 예외 2개뿐: ① alert 는 모든 것 위에 뜰 수 있다(동시 1개만) ② full-screen dialog 위에는 basic dialog(미저장 확인·실패 통보)만 허용.\n- 해제 경로는 항상 2개 이상(버튼 + 스와이프/scrim) + 데이터 유실 위험 시 확인 단계.\n\n## 판정 절차 (에이전트 의무)\n\n1. 모바일 화면을 만들기 전에 목적지 수·브레이크포인트로 §1 표에서 컨테이너를 정한다 — \"모바일이니까 햄버거 메뉴\"는 금지 수(§1 마지막 행).\n2. 오버레이가 필요하면 §3 표에서 태스크 무게로 표면을 정하고 §4 깊이 규칙을 검사한다.\n3. 규칙과 다른 선택을 하면 그 이유를 산출물에 한 줄 남긴다(플랫폼 규범 이탈은 의도여야 한다).\n4. 스타일(Liquid Glass·M3 색 역할·인디케이터 모양)은 흡수 대상이 아니다 — look 은 프로젝트 토큰 소유.\n\n## Changelog\n\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## 관련\n\n- 미시 상호작용 프레임(trigger/rules/feedback/loops/modes)으로 작은 순간의 실패를 진단하려면: KG `nodes/책/book-microinteractions.md` (규칙 중복 없음 — 링크만).\n- 티어·도구 판정과 접근성 짝규칙: [[expressive-stack]] §판정 절차.\n- 외부 데모·사례 북마크: [[motion-references]].\n\n## Changelog\n\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\n\n> 인터랙티브 UI를 만들 때 참고하는 외부 자료 북마크. 코드 라이브러리 클론은 toolshelf(`~/projects/toolshelf`)에 있고, 여기엔 레포가 아닌 기법·데모 레퍼런스를 남긴다.\n\n## 스크롤 내러티브 (scroll-driven storytelling)\n\n- **GSAP + ScrollTrigger** — 스크롤 핀·스크럽·패럴랙스·단계적 리빌 기법의 가장 깊은 데모 라이브러리.\n - 데모 허브: `https://gsap.com/scroll/` · `https://demos.gsap.com/plugin/scrolltrigger/` · CodePen 컬렉션 `https://codepen.io/collection/DkvGzg`\n - 라이선스: GSAP 코어 + 전 플러그인(ScrollTrigger 포함)이 2024년 Webflow 인수 이후 **모든 용도 무료**(상업 포함). 의존성으로 추가하기 전 `gsap.com/pricing`에서 현재 약관 재확인.\n - 용도: showcase 카드 4 (Scroll-Driven Narrative) 기법 참고. 레포가 아니라 데모 갤러리라 toolshelf에 클론하지 않고 여기 북마크. 우리 모션 primitive로 재구현하거나, 무료화됐으니 `gsap` 직접 추가 중 택.\n - 접근일: 2026-07-07\n\n## 공간형 랜딩 (Three.js walkable/spatial hero)\n\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).\n\n**채택 게이트 (decorative-skin failure)**: 3D 가 데이터 위의 장식 스킨이면 실패다 — ① 정보 구조(구역·경로) ② 시각 위계(랜드마크·스케일·조명) ③ 증거 접근(각 목적지에서 실제 콘텐츠 열람)의 3층이 전부 의도적으로 매핑될 때만 공간 메타포가 정당하다. 첫 제품 형태는 도시보다 **룸 스케일**이 적합할 수 있다. LLM 없는 non-agent 폴백(로컬 검색·결정적 라우팅)을 유지한다.\n\n## 영상 매체 (Remotion) — 링크만, 매체가 다르다\n\n사전 렌더 영상은 화면 UI 와 게이트가 다르다(프레임 결정론·정지프레임 검증). 이 레포 범위 밖 — 필요 시 KG 원본을 직접 읽는다 (접근일 2026-07-28):\n\n- `nodes/디자인/motion-remotion-animation-pattern-library.md` — 프리미티브 패턴 조직법\n- `nodes/디자인/motion-remotion-scene-spec-workflow.md` — 씬 스펙·컴포넌트 레지스트리·렌더 루프\n- `nodes/디자인/motion-remotion-official-resource-selection.md` — 공식 리소스 역설계 루프\n- `nodes/디자인/motion-remotion-frame-render-guardrails.md` — 프레임 기반·CSS 애니메이션 배제 가드레일\n\n단, 모션 **원칙**(still appeal·목적 6분류·타이포 가독성)은 매체 공통 — [[motion-principles]] 가 정본.\n\n## 관찰 갤러리 (기법이 아니라 사례를 본다)\n\n- **60fps.design** — iOS·웹 앱의 마이크로인터랙션·전환·제스처 디테일 큐레이션. `https://60fps.design/` (접근일 2026-07-28)\n- **landing.love** — 애니메이션 랜딩 갤러리, 풀페이지 영상 기록 — 스크롤 페이싱·히어로 전환 관찰용. `https://www.landing.love/` (접근일 2026-07-28)\n- **Brainwave Interactive Landing** — 애니메이션 헤비 AI 제품 랜딩 완성 사례(히어로 레이어링·스크롤 트리거 섹션). `https://github.com/arnobt78/Brainwave-Interactive-Landing--React-Frontend` (접근일 2026-07-28)\n\n판정 이력: 위 3건 + 공간형 랜딩 팩은 absorption-criteria **B(링크 참조)** — 재서술·벤더링 금지, 관찰 소스로만 (`research/2026-07-28-vi7-toolshelf-placement.md`).\n\n## 관련 toolshelf 항목 (코드 클론)\n\n- `magicui` — 애니메이션 컴포넌트 패턴 (카드 5/6/9)\n- `shaders` (paper-design) — WebGL 메시 그라디언트 (카드 6)\n- `cmdk` — 커맨드 팔레트 primitive (카드 10)\n- `cult-ui` — 편집 캔버스·머티리얼 컴포넌트 (카드 1/7)\n- `react-bits` · `motion` — 애니메이션 컴포넌트/엔진 (기존)\n\n상세 후보 분석: `docs/research/showcase-card-reference-shortlist-2026-07.md`"},{"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` 흡수)."}]
|
package/data/tokens.css
CHANGED
package/data/tokens.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$description":"Askewly Design token SSOT (DTCG-compatible, stable 2025.10 subset). Tier rule: component -> semantic -> primitive, one direction only. Dark mode lives in $extensions['com.askewly.modes'].dark on semantic tokens. Generated artifacts: DESIGN.md frontmatter, examples/ui-vocabulary-site/src/tokens.css. Do not edit generated files by hand.","color":{"primitive":{"$description":"Raw values only. gray is the site's violet-tinted neutral (hue 270) formalized as a 12-step ramp; usage convention: 1-2 app background, 3-5 interactive component states, 6-8 borders, 9-10 solid/low-contrast foreground, 11-12 text.","white":{"$type":"color","$value":{"colorSpace":"oklch","components":[1,0,0]}},"gray":{"1":{"$value":{"colorSpace":"oklch","components":[0.985,0,0]}},"2":{"$value":{"colorSpace":"oklch","components":[0.97,0.005,270]}},"3":{"$value":{"colorSpace":"oklch","components":[0.95,0.015,270]}},"4":{"$value":{"colorSpace":"oklch","components":[0.94,0.02,270]}},"5":{"$value":{"colorSpace":"oklch","components":[0.91,0.015,270]}},"6":{"$value":{"colorSpace":"oklch","components":[0.88,0.015,270]}},"7":{"$value":{"colorSpace":"oklch","components":[0.72,0.02,270]}},"8":{"$value":{"colorSpace":"oklch","components":[0.6,0.025,270]}},"9":{"$value":{"colorSpace":"oklch","components":[0.46,0.03,270]}},"10":{"$value":{"colorSpace":"oklch","components":[0.35,0.03,270]}},"11":{"$value":{"colorSpace":"oklch","components":[0.25,0.03,270]}},"12":{"$value":{"colorSpace":"oklch","components":[0.16,0.015,270]}},"$type":"color"},"askewly":{"$type":"color","$description":"Brand palette extracted from the landing planning reference image (2026-07-05). Canonical purple: violet.","violet":{"$value":{"colorSpace":"srgb","components":[0.435294,0.176471,0.741176],"hex":"#6F2DBD"}},"orchid":{"$value":{"colorSpace":"srgb","components":[0.65098,0.388235,0.8],"hex":"#A663CC"}},"lavender":{"$value":{"colorSpace":"srgb","components":[0.698039,0.596078,0.862745],"hex":"#B298DC"}},"sky":{"$value":{"colorSpace":"srgb","components":[0.721569,0.815686,0.921569],"hex":"#B8D0EB"}},"mint":{"$value":{"colorSpace":"srgb","components":[0.72549,0.980392,0.972549],"hex":"#B9FAF8"}},"violet-deep":{"$description":"Hover shade of the canonical brand violet (M2 2026-08-01, promoted from hardcoded #5f22a8).","$value":{"colorSpace":"srgb","components":[0.372549,0.133333,0.658824],"hex":"#5F22A8"}}},"red":{"9":{"$value":{"colorSpace":"oklch","components":[0.58,0.22,27]}},"$type":"color"},"indigo":{"50":{"$value":{"colorSpace":"oklch","components":[0.962,0.018,272.314]}},"300":{"$value":{"colorSpace":"oklch","components":[0.785,0.115,274.713]}},"400":{"$value":{"colorSpace":"oklch","components":[0.673,0.182,276.935]}},"500":{"$value":{"colorSpace":"oklch","components":[0.585,0.233,277.117]}},"600":{"$value":{"colorSpace":"oklch","components":[0.511,0.262,276.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.457,0.24,277.023]}},"950":{"$value":{"colorSpace":"oklch","components":[0.257,0.09,281.288]}},"$type":"color","$description":"Emphasis/selection accent ramp (M2 2026-08-01). Values = Tailwind v4 defaults the site already rendered, promoted verbatim for light-mode visual losslessness."},"skyx":{"100":{"$value":{"colorSpace":"oklch","components":[0.951,0.026,236.824]}},"300":{"$value":{"colorSpace":"oklch","components":[0.828,0.111,230.318]}},"400":{"$value":{"colorSpace":"oklch","components":[0.746,0.16,232.661]}},"500":{"$value":{"colorSpace":"oklch","components":[0.685,0.169,237.323]}},"600":{"$value":{"colorSpace":"oklch","components":[0.588,0.158,241.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.5,0.134,242.749]}},"950":{"$value":{"colorSpace":"oklch","components":[0.293,0.066,243.157]}},"$type":"color","$description":"Informational accent ramp (M2 2026-08-01, Tailwind v4 sky — named skyx to avoid clashing with brand askewly.sky)."},"emerald":{"50":{"$value":{"colorSpace":"oklch","components":[0.979,0.021,166.113]}},"300":{"$value":{"colorSpace":"oklch","components":[0.845,0.143,164.978]}},"700":{"$value":{"colorSpace":"oklch","components":[0.508,0.118,165.612]}},"950":{"$value":{"colorSpace":"oklch","components":[0.262,0.051,172.552]}},"$type":"color","$description":"Success accent ramp (M2 2026-08-01, Tailwind v4 defaults)."},"rose":{"100":{"$value":{"colorSpace":"oklch","components":[0.941,0.03,12.58]}},"400":{"$value":{"colorSpace":"oklch","components":[0.712,0.194,13.428]}},"500":{"$value":{"colorSpace":"oklch","components":[0.645,0.246,16.439]}},"600":{"$value":{"colorSpace":"oklch","components":[0.586,0.253,17.585]}},"950":{"$value":{"colorSpace":"oklch","components":[0.271,0.105,12.094]}},"$type":"color","$description":"Danger accent ramp (M2 2026-08-01, Tailwind v4 defaults). Distinct from red.9 (--destructive): the site's danger demos render rose, and the shades intentionally differ."}},"semantic":{"$description":"Role tokens. Components and site CSS consume this layer only. Dark values in $extensions['com.askewly.modes'].dark.","surface":{"$type":"color","base":{"$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"raised":{"$description":"Cards, panels sitting on base.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"overlay":{"$description":"Popovers, dropdowns, floating layers.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"muted":{"$value":"{color.primitive.gray.3}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"secondary":{"$description":"Secondary button / subtle filled surface.","$value":"{color.primitive.gray.4}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"tint":{"$description":"Informational wash, agent-ready signal surface.","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}}},"text":{"$type":"color","default":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"muted":{"$value":"{color.primitive.gray.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.7}"}}},"secondary":{"$description":"Foreground on surface.secondary.","$value":"{color.primitive.gray.11}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.3}"}}},"on-accent":{"$description":"Text/icon on action.primary solid fills.","$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"on-destructive":{"$description":"Text/icon on action.destructive solid fills. Theme-invariant white (M2: promotes the settled 'text-white is correct' marker verdict into a token).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"action":{"$type":"color","primary":{"$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"primary-hover":{"$description":"Hover state for brand-violet fills/text (M2). Dark uses the brighter orchid — darkening on hover loses contrast on dark surfaces.","$value":"{color.primitive.askewly.violet-deep}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}},"secondary":{"$value":"{color.primitive.askewly.orchid}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.mint}"}}},"destructive":{"$value":"{color.primitive.red.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.red.9}"}}}},"emphasis":{"$type":"color","$description":"Selection/emphasis accent (indigo family, M2 2026-08-01). Distinct from 'accent' (shadcn interaction-state wash, mint family) — do not merge (group-semantics collision, fresh-verifier finding).","surface":{"$description":"Chip/selected-row wash.","$value":"{color.primitive.indigo.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.950}"}}},"foreground":{"$description":"Text on emphasis.surface.","$value":"{color.primitive.indigo.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.300}"}}},"solid":{"$description":"Solid emphasis fill (buttons).","$value":"{color.primitive.indigo.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.500}"}}},"solid-hover":{"$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"ring":{"$description":"Focus border/ring on emphasis-accented inputs.","$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"on-solid":{"$description":"Text/icon on emphasis.solid fills. White holds contrast on indigo-600 (light) and indigo-500 (dark).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"status":{"$type":"color","$description":"Status colors (M2 2026-08-01). info=sky, success=emerald, danger=rose. Kept out of 'accent' (interaction-state group).","info-surface":{"$value":"{color.primitive.skyx.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.950}"}}},"info-foreground":{"$value":"{color.primitive.skyx.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"info-link":{"$description":"Standalone info-accented link/action text.","$value":"{color.primitive.skyx.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.400}"}}},"info-link-hover":{"$value":"{color.primitive.skyx.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"success-surface":{"$value":"{color.primitive.emerald.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.950}"}}},"success-foreground":{"$value":"{color.primitive.emerald.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.300}"}}},"danger-surface":{"$value":"{color.primitive.rose.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.950}"}}},"danger-foreground":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.400}"}}},"danger-solid":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.600}"}}},"danger-solid-hover":{"$value":"{color.primitive.rose.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.500}"}}}},"accent":{"$type":"color","base":{"$description":"Informational accent wash (mint family).","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"foreground":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}}},"border":{"$type":"color","default":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"input":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"focus":{"$description":"Focus ring.","$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.lavender}"}}},"accent":{"$description":"Selected/active edges only.","$value":"{color.primitive.askewly.lavender}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}}}},"component":{"$description":"Minimal by policy (survey criterion A-5): only overrides that semantic roles cannot express. Exemplar tier for the lint chain.","button":{"$type":"color","bg":{"$value":"{color.semantic.action.primary}"},"text":{"$value":"{color.semantic.text.on-accent}"}}}},"dimension":{"space":{"1":{"$value":{"value":4,"unit":"px"}},"2":{"$value":{"value":8,"unit":"px"}},"4":{"$value":{"value":16,"unit":"px"}},"8":{"$value":{"value":32,"unit":"px"}},"12":{"$value":{"value":48,"unit":"px"}},"16":{"$value":{"value":64,"unit":"px"}},"$type":"dimension","$description":"4/8 grid. Reference variables only - not bound into the Tailwind theme namespace."},"radius":{"$type":"dimension","$description":"Canonical = shipped site values (PSS1 visually approved), not the older 2/4px draft.","sm":{"$value":{"value":4,"unit":"px"}},"md":{"$value":{"value":6,"unit":"px"}},"lg":{"$value":{"value":8,"unit":"px"}},"xl":{"$value":{"value":12,"unit":"px"}},"2xl":{"$description":"SD gen2 (2026-07-12): large-surface corner radius for sheets/overlays that sit above the base scale — device-frame inner viewport, bottom-sheet-detents. Recurs identically in >=2 mobile components as Tailwind's built-in rounded-2xl (16px), currently outside the tokenized sm-xl range.","$value":{"value":16,"unit":"px"}}},"size":{"$type":"dimension","$description":"Fixed-purpose sizing constants that are not part of the 4/8 spacing grid. SD gen2 (2026-07-12): touch-target minimum recurs across mobile surfaces (large-title-collapsing-header back button, mobile-signup-field-stack inputs/submit, plus pre-existing search-autocomplete/showcase-card/term-visual usage) as Tailwind size-11/h-11/min-h-11 (44px) — the WCAG 2.5.5 / iOS HIG minimum hit target.","touch-target-min":{"$value":{"value":44,"unit":"px"}}},"z-index":{"$type":"number","$description":"SD gen2 (2026-07-12): layering scale distilled from recurring hardcoded z-index values across mobile overlays and dense-data sticky headers. 'sticky' = content pinned within its own scroll container (collapsing header bar, bottom-sheet/action-sheet root within DeviceFrame, table sticky header row). 'elevated' = a second layer above a sticky layer in the same local stacking context (permission-matrix-editor-grid's sticky corner cell, which must sit above both its own header row and column). 'overlay' = page-level modal/scrim/popover stacking context (ui/dialog, ui/sheet, ui/popover, ui/tooltip, full-screen confirm overlays).","sticky":{"$value":10},"elevated":{"$value":20},"overlay":{"$value":50}}},"typography":{"font":{"$type":"fontFamily","sans":{"$value":["Geist","Noto Sans KR","ui-sans-serif","system-ui","sans-serif"]},"mono":{"$value":["Geist Mono","ui-monospace","monospace"]}},"scale":{"$type":"dimension","$description":"M6 2026-08-01: expanded to the 9 steps screens actually render (M1 closeout measurement). New steps (2xs/xs/3xl/5xl/7xl) carry Tailwind v4 default values verbatim in rem — they are wired into @theme --text-* so existing utility classes route through the SSOT with zero render change. Legacy 5 names (sm/base/lg/xl/2xl, px) are kept for backward compat; lg(20) and 2xl(40) differ from Tailwind's same-named defaults and are NOT wired.","2xs":{"$description":"Micro labels (10px at default root). New utility text-2xs — Tailwind has no built-in 2xs.","$value":{"value":0.625,"unit":"rem"}},"xs":{"$value":{"value":0.75,"unit":"rem"}},"sm":{"$value":{"value":14,"unit":"px"}},"base":{"$value":{"value":16,"unit":"px"}},"lg":{"$value":{"value":20,"unit":"px"}},"xl":{"$description":"Unused by live screens as of M1 measurement (2026-08-01) — kept as record for llms consumers.","$value":{"value":28,"unit":"px"}},"2xl":{"$value":{"value":40,"unit":"px"}},"3xl":{"$value":{"value":1.875,"unit":"rem"}},"5xl":{"$value":{"value":3,"unit":"rem"}},"7xl":{"$value":{"value":4.5,"unit":"rem"}}},"weight":{"$type":"fontWeight","regular":{"$value":400},"medium":{"$value":500}}},"motion":{"$description":"SD gen2 (2026-07-12): motion timing constants. Kept minimal by the same policy as color.component — only the value that actually recurs, not a full scale invented ahead of need.","duration":{"$type":"duration","overlay":{"$description":"Open/close transition for modal-style surfaces. Recurs identically (200ms) in bottom-sheet-detents.tsx and ui/dialog.tsx.","$value":{"value":200,"unit":"ms"}}}}}
|
|
1
|
+
{"$description":"Askewly Design token SSOT (DTCG-compatible, stable 2025.10 subset). Tier rule: component -> semantic -> primitive, one direction only. Dark mode lives in $extensions['com.askewly.modes'].dark on semantic tokens. Generated artifacts: DESIGN.md frontmatter, examples/ui-vocabulary-site/src/tokens.css. Do not edit generated files by hand.","color":{"primitive":{"$description":"Raw values only. gray is the site's violet-tinted neutral (hue 270) formalized as a 12-step ramp; usage convention: 1-2 app background, 3-5 interactive component states, 6-8 borders, 9-10 solid/low-contrast foreground, 11-12 text.","white":{"$type":"color","$value":{"colorSpace":"oklch","components":[1,0,0]}},"black":{"$type":"color","$value":{"colorSpace":"oklch","components":[0,0,0]}},"gray":{"1":{"$value":{"colorSpace":"oklch","components":[0.985,0,0]}},"2":{"$value":{"colorSpace":"oklch","components":[0.97,0.005,270]}},"3":{"$value":{"colorSpace":"oklch","components":[0.95,0.015,270]}},"4":{"$value":{"colorSpace":"oklch","components":[0.94,0.02,270]}},"5":{"$value":{"colorSpace":"oklch","components":[0.91,0.015,270]}},"6":{"$value":{"colorSpace":"oklch","components":[0.88,0.015,270]}},"7":{"$value":{"colorSpace":"oklch","components":[0.72,0.02,270]}},"8":{"$value":{"colorSpace":"oklch","components":[0.6,0.025,270]}},"9":{"$value":{"colorSpace":"oklch","components":[0.46,0.03,270]}},"10":{"$value":{"colorSpace":"oklch","components":[0.35,0.03,270]}},"11":{"$value":{"colorSpace":"oklch","components":[0.25,0.03,270]}},"12":{"$value":{"colorSpace":"oklch","components":[0.16,0.015,270]}},"$type":"color"},"askewly":{"$type":"color","$description":"Brand palette extracted from the landing planning reference image (2026-07-05). Canonical purple: violet.","violet":{"$value":{"colorSpace":"srgb","components":[0.435294,0.176471,0.741176],"hex":"#6F2DBD"}},"orchid":{"$value":{"colorSpace":"srgb","components":[0.65098,0.388235,0.8],"hex":"#A663CC"}},"lavender":{"$value":{"colorSpace":"srgb","components":[0.698039,0.596078,0.862745],"hex":"#B298DC"}},"sky":{"$value":{"colorSpace":"srgb","components":[0.721569,0.815686,0.921569],"hex":"#B8D0EB"}},"mint":{"$value":{"colorSpace":"srgb","components":[0.72549,0.980392,0.972549],"hex":"#B9FAF8"}},"violet-deep":{"$description":"Hover shade of the canonical brand violet (M2 2026-08-01, promoted from hardcoded #5f22a8).","$value":{"colorSpace":"srgb","components":[0.372549,0.133333,0.658824],"hex":"#5F22A8"}}},"red":{"9":{"$value":{"colorSpace":"oklch","components":[0.58,0.22,27]}},"$type":"color"},"indigo":{"50":{"$value":{"colorSpace":"oklch","components":[0.962,0.018,272.314]}},"300":{"$value":{"colorSpace":"oklch","components":[0.785,0.115,274.713]}},"400":{"$value":{"colorSpace":"oklch","components":[0.673,0.182,276.935]}},"500":{"$value":{"colorSpace":"oklch","components":[0.585,0.233,277.117]}},"600":{"$value":{"colorSpace":"oklch","components":[0.511,0.262,276.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.457,0.24,277.023]}},"950":{"$value":{"colorSpace":"oklch","components":[0.257,0.09,281.288]}},"$type":"color","$description":"Emphasis/selection accent ramp (M2 2026-08-01). Values = Tailwind v4 defaults the site already rendered, promoted verbatim for light-mode visual losslessness."},"skyx":{"100":{"$value":{"colorSpace":"oklch","components":[0.951,0.026,236.824]}},"300":{"$value":{"colorSpace":"oklch","components":[0.828,0.111,230.318]}},"400":{"$value":{"colorSpace":"oklch","components":[0.746,0.16,232.661]}},"500":{"$value":{"colorSpace":"oklch","components":[0.685,0.169,237.323]}},"600":{"$value":{"colorSpace":"oklch","components":[0.588,0.158,241.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.5,0.134,242.749]}},"950":{"$value":{"colorSpace":"oklch","components":[0.293,0.066,243.157]}},"$type":"color","$description":"Informational accent ramp (M2 2026-08-01, Tailwind v4 sky — named skyx to avoid clashing with brand askewly.sky)."},"emerald":{"50":{"$value":{"colorSpace":"oklch","components":[0.979,0.021,166.113]}},"300":{"$value":{"colorSpace":"oklch","components":[0.845,0.143,164.978]}},"700":{"$value":{"colorSpace":"oklch","components":[0.508,0.118,165.612]}},"950":{"$value":{"colorSpace":"oklch","components":[0.262,0.051,172.552]}},"$type":"color","$description":"Success accent ramp (M2 2026-08-01, Tailwind v4 defaults)."},"rose":{"100":{"$value":{"colorSpace":"oklch","components":[0.941,0.03,12.58]}},"400":{"$value":{"colorSpace":"oklch","components":[0.712,0.194,13.428]}},"500":{"$value":{"colorSpace":"oklch","components":[0.645,0.246,16.439]}},"600":{"$value":{"colorSpace":"oklch","components":[0.586,0.253,17.585]}},"950":{"$value":{"colorSpace":"oklch","components":[0.271,0.105,12.094]}},"$type":"color","$description":"Danger accent ramp (M2 2026-08-01, Tailwind v4 defaults). Distinct from red.9 (--destructive): the site's danger demos render rose, and the shades intentionally differ."}},"semantic":{"$description":"Role tokens. Components and site CSS consume this layer only. Dark values in $extensions['com.askewly.modes'].dark.","surface":{"$type":"color","base":{"$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"raised":{"$description":"Cards, panels sitting on base.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"overlay":{"$description":"Popovers, dropdowns, floating layers.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"scrim":{"$description":"Dimming layer behind modals, sheets and lightboxes. Pure black, with no dark override on purpose: a scrim must be darker than the darkest surface it covers. Keying it off --foreground inverts it in dark mode (the backdrop gets brighter); keying it off gray.12 makes it identical to the dark canvas, so it dims nothing at all. Consumers own the opacity (bg-scrim/50, /72).","$value":"{color.primitive.black}"},"muted":{"$value":"{color.primitive.gray.3}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"secondary":{"$description":"Secondary button / subtle filled surface.","$value":"{color.primitive.gray.4}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"tint":{"$description":"Informational wash, agent-ready signal surface.","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}}},"text":{"$type":"color","default":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"muted":{"$value":"{color.primitive.gray.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.7}"}}},"secondary":{"$description":"Foreground on surface.secondary.","$value":"{color.primitive.gray.11}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.3}"}}},"on-accent":{"$description":"Text/icon on action.primary solid fills.","$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"on-destructive":{"$description":"Text/icon on action.destructive solid fills. Theme-invariant white (M2: promotes the settled 'text-white is correct' marker verdict into a token).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"action":{"$type":"color","primary":{"$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"primary-hover":{"$description":"Hover state for brand-violet fills/text (M2). Dark uses the brighter orchid — darkening on hover loses contrast on dark surfaces.","$value":"{color.primitive.askewly.violet-deep}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}},"secondary":{"$value":"{color.primitive.askewly.orchid}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.mint}"}}},"destructive":{"$value":"{color.primitive.red.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.red.9}"}}}},"emphasis":{"$type":"color","$description":"Selection/emphasis accent (indigo family, M2 2026-08-01). Distinct from 'accent' (shadcn interaction-state wash, mint family) — do not merge (group-semantics collision, fresh-verifier finding).","surface":{"$description":"Chip/selected-row wash.","$value":"{color.primitive.indigo.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.950}"}}},"foreground":{"$description":"Text on emphasis.surface.","$value":"{color.primitive.indigo.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.300}"}}},"solid":{"$description":"Solid emphasis fill (buttons).","$value":"{color.primitive.indigo.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.500}"}}},"solid-hover":{"$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"ring":{"$description":"Focus border/ring on emphasis-accented inputs.","$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"on-solid":{"$description":"Text/icon on emphasis.solid fills. White holds contrast on indigo-600 (light) and indigo-500 (dark).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"status":{"$type":"color","$description":"Status colors (M2 2026-08-01). info=sky, success=emerald, danger=rose. Kept out of 'accent' (interaction-state group).","info-surface":{"$value":"{color.primitive.skyx.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.950}"}}},"info-foreground":{"$value":"{color.primitive.skyx.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"info-link":{"$description":"Standalone info-accented link/action text.","$value":"{color.primitive.skyx.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.400}"}}},"info-link-hover":{"$value":"{color.primitive.skyx.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"success-surface":{"$value":"{color.primitive.emerald.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.950}"}}},"success-foreground":{"$value":"{color.primitive.emerald.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.300}"}}},"danger-surface":{"$value":"{color.primitive.rose.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.950}"}}},"danger-foreground":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.400}"}}},"danger-solid":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.600}"}}},"danger-solid-hover":{"$value":"{color.primitive.rose.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.500}"}}}},"accent":{"$type":"color","base":{"$description":"Informational accent wash (mint family).","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"foreground":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}}},"border":{"$type":"color","default":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"input":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"focus":{"$description":"Focus ring.","$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.lavender}"}}},"accent":{"$description":"Selected/active edges only.","$value":"{color.primitive.askewly.lavender}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}}}},"component":{"$description":"Minimal by policy (survey criterion A-5): only overrides that semantic roles cannot express. Exemplar tier for the lint chain.","button":{"$type":"color","bg":{"$value":"{color.semantic.action.primary}"},"text":{"$value":"{color.semantic.text.on-accent}"}}}},"dimension":{"space":{"1":{"$value":{"value":4,"unit":"px"}},"2":{"$value":{"value":8,"unit":"px"}},"4":{"$value":{"value":16,"unit":"px"}},"8":{"$value":{"value":32,"unit":"px"}},"12":{"$value":{"value":48,"unit":"px"}},"16":{"$value":{"value":64,"unit":"px"}},"$type":"dimension","$description":"4/8 grid. Reference variables only - not bound into the Tailwind theme namespace."},"radius":{"$type":"dimension","$description":"Canonical = shipped site values (PSS1 visually approved), not the older 2/4px draft.","sm":{"$value":{"value":4,"unit":"px"}},"md":{"$value":{"value":6,"unit":"px"}},"lg":{"$value":{"value":8,"unit":"px"}},"xl":{"$value":{"value":12,"unit":"px"}},"2xl":{"$description":"SD gen2 (2026-07-12): large-surface corner radius for sheets/overlays that sit above the base scale — device-frame inner viewport, bottom-sheet-detents. Recurs identically in >=2 mobile components as Tailwind's built-in rounded-2xl (16px), currently outside the tokenized sm-xl range.","$value":{"value":16,"unit":"px"}}},"size":{"$type":"dimension","$description":"Fixed-purpose sizing constants that are not part of the 4/8 spacing grid. SD gen2 (2026-07-12): touch-target minimum recurs across mobile surfaces (large-title-collapsing-header back button, mobile-signup-field-stack inputs/submit, plus pre-existing search-autocomplete/showcase-card/term-visual usage) as Tailwind size-11/h-11/min-h-11 (44px) — the WCAG 2.5.5 / iOS HIG minimum hit target.","touch-target-min":{"$value":{"value":44,"unit":"px"}}},"z-index":{"$type":"number","$description":"SD gen2 (2026-07-12): layering scale distilled from recurring hardcoded z-index values across mobile overlays and dense-data sticky headers. 'sticky' = content pinned within its own scroll container (collapsing header bar, bottom-sheet/action-sheet root within DeviceFrame, table sticky header row). 'elevated' = a second layer above a sticky layer in the same local stacking context (permission-matrix-editor-grid's sticky corner cell, which must sit above both its own header row and column). 'overlay' = page-level modal/scrim/popover stacking context (ui/dialog, ui/sheet, ui/popover, ui/tooltip, full-screen confirm overlays).","sticky":{"$value":10},"elevated":{"$value":20},"overlay":{"$value":50}}},"typography":{"font":{"$type":"fontFamily","sans":{"$value":["Geist","Noto Sans KR","ui-sans-serif","system-ui","sans-serif"]},"mono":{"$value":["Geist Mono","ui-monospace","monospace"]}},"scale":{"$type":"dimension","$description":"M6 2026-08-01: expanded to the 9 steps screens actually render (M1 closeout measurement). New steps (2xs/xs/3xl/5xl/7xl) carry Tailwind v4 default values verbatim in rem — they are wired into @theme --text-* so existing utility classes route through the SSOT with zero render change. Legacy 5 names (sm/base/lg/xl/2xl, px) are kept for backward compat; lg(20) and 2xl(40) differ from Tailwind's same-named defaults and are NOT wired.","2xs":{"$description":"Micro labels (10px at default root). New utility text-2xs — Tailwind has no built-in 2xs.","$value":{"value":0.625,"unit":"rem"}},"xs":{"$value":{"value":0.75,"unit":"rem"}},"sm":{"$value":{"value":14,"unit":"px"}},"base":{"$value":{"value":16,"unit":"px"}},"lg":{"$value":{"value":20,"unit":"px"}},"xl":{"$description":"Unused by live screens as of M1 measurement (2026-08-01) — kept as record for llms consumers.","$value":{"value":28,"unit":"px"}},"2xl":{"$value":{"value":40,"unit":"px"}},"3xl":{"$value":{"value":1.875,"unit":"rem"}},"5xl":{"$value":{"value":3,"unit":"rem"}},"7xl":{"$value":{"value":4.5,"unit":"rem"}}},"weight":{"$type":"fontWeight","regular":{"$value":400},"medium":{"$value":500}}},"motion":{"$description":"SD gen2 (2026-07-12): motion timing constants. Kept minimal by the same policy as color.component — only the value that actually recurs, not a full scale invented ahead of need.","duration":{"$type":"duration","overlay":{"$description":"Open/close transition for modal-style surfaces. Recurs identically (200ms) in bottom-sheet-detents.tsx and ui/dialog.tsx.","$value":{"value":200,"unit":"ms"}}}}}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { addRecipe, initProject, InjectError } from "./inject.js";
|
|
|
6
6
|
import { runKickstart, KickstartError } from "./kickstart.js";
|
|
7
7
|
import { DEFAULT_TYPOGRAPHY_THRESHOLD } from "./typography.js";
|
|
8
8
|
import { verifyDir } from "./verify.js";
|
|
9
|
-
import { loadRecipes, loadTerms, loadTokens, loadTokensCss, searchTerms, tokensForTier, } from "./load.js";
|
|
9
|
+
import { loadKnowledge, loadRecipes, loadTerms, loadTokens, loadTokensCss, searchKnowledge, searchTerms, tokensForTier, } from "./load.js";
|
|
10
10
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
11
11
|
const program = new Command();
|
|
12
12
|
program
|
|
@@ -212,6 +212,60 @@ program
|
|
|
212
212
|
console.error(" collapse extra type sizes onto the token scale, or raise --typography-threshold.");
|
|
213
213
|
process.exit(1);
|
|
214
214
|
});
|
|
215
|
+
function printKnowledgeLine(doc) {
|
|
216
|
+
console.log(`${doc.id} — ${doc.title} [${doc.tags.join(", ")}]`);
|
|
217
|
+
console.log(` ${doc.description}`);
|
|
218
|
+
}
|
|
219
|
+
const knowledge = program
|
|
220
|
+
.command("knowledge")
|
|
221
|
+
.description("design knowledge rules (what good design is — decision tables and judgment gates)");
|
|
222
|
+
knowledge
|
|
223
|
+
.command("list")
|
|
224
|
+
.option("--json", "machine-readable output")
|
|
225
|
+
.description("list all knowledge docs")
|
|
226
|
+
.action((options) => {
|
|
227
|
+
const all = loadKnowledge();
|
|
228
|
+
if (options.json) {
|
|
229
|
+
console.log(JSON.stringify(all.map((d) => ({ id: d.id, title: d.title, tags: d.tags, source_path: d.source_path })), null, 2));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
for (const doc of all)
|
|
233
|
+
printKnowledgeLine(doc);
|
|
234
|
+
});
|
|
235
|
+
knowledge
|
|
236
|
+
.command("search")
|
|
237
|
+
.argument("<query>", "text to match against id, title, tags, description")
|
|
238
|
+
.option("--json", "machine-readable output")
|
|
239
|
+
.option("--limit <n>", "max results", "10")
|
|
240
|
+
.description("search knowledge docs")
|
|
241
|
+
.action((query, options) => {
|
|
242
|
+
const results = searchKnowledge(loadKnowledge(), query).slice(0, Number(options.limit) || 10);
|
|
243
|
+
if (results.length === 0)
|
|
244
|
+
fail(`no knowledge docs match "${query}" — try: askewly-design knowledge list`);
|
|
245
|
+
if (options.json) {
|
|
246
|
+
console.log(JSON.stringify(results.map((d) => ({ id: d.id, title: d.title, tags: d.tags, description: d.description })), null, 2));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
for (const doc of results)
|
|
250
|
+
printKnowledgeLine(doc);
|
|
251
|
+
});
|
|
252
|
+
knowledge
|
|
253
|
+
.command("show")
|
|
254
|
+
.argument("<id>", "knowledge doc id, e.g. dark-mode")
|
|
255
|
+
.option("--json", "machine-readable output (metadata + body)")
|
|
256
|
+
.description("show one knowledge doc in full")
|
|
257
|
+
.action((id, options) => {
|
|
258
|
+
const doc = loadKnowledge().find((d) => d.id === id);
|
|
259
|
+
if (!doc)
|
|
260
|
+
fail(`unknown knowledge doc "${id}" — try: askewly-design knowledge search ${id}`);
|
|
261
|
+
if (options.json) {
|
|
262
|
+
console.log(JSON.stringify(doc, null, 2));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
printKnowledgeLine(doc);
|
|
266
|
+
console.log("");
|
|
267
|
+
console.log(doc.body);
|
|
268
|
+
});
|
|
215
269
|
const recipes = program.command("recipes").description("component recipes (agent-ready implementation contracts)");
|
|
216
270
|
recipes
|
|
217
271
|
.command("list")
|
package/dist/kickstart.js
CHANGED
|
@@ -245,6 +245,16 @@ export function renderBrandCss(answers) {
|
|
|
245
245
|
:root {
|
|
246
246
|
font-family: ${font};
|
|
247
247
|
${vars(c.light, a.light, a.onLight, a.ring, "light")}
|
|
248
|
+
/* Scrim — the dimming layer behind modals, sheets and lightboxes. Declared
|
|
249
|
+
* once, outside the light/dark pair, on purpose: a scrim means "press the
|
|
250
|
+
* background down", so it must read dark in BOTH modes. Keying it off
|
|
251
|
+
* \`--foreground\` (the obvious-looking choice) inverts it in dark mode and the
|
|
252
|
+
* backdrop gets *brighter* than the page behind it. Pure black rather than the
|
|
253
|
+
* dark canvas colour, because a scrim identical to the surface it covers dims
|
|
254
|
+
* nothing — measured on the dark bottom-sheet demo, where the two matched
|
|
255
|
+
* exactly and the sheet stopped separating from the screen behind it.
|
|
256
|
+
* Consumers own the opacity: bg-scrim/50, bg-scrim/72. */
|
|
257
|
+
--scrim: oklch(0 0 0);
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
.dark {
|
|
@@ -271,6 +281,7 @@ ${vars(c.dark, a.dark, a.onDark, a.darkRing, "dark")}
|
|
|
271
281
|
--color-border: var(--border);
|
|
272
282
|
--color-input: var(--input);
|
|
273
283
|
--color-ring: var(--ring);
|
|
284
|
+
--color-scrim: var(--scrim);
|
|
274
285
|
--color-sidebar: var(--sidebar);
|
|
275
286
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
276
287
|
--color-sidebar-primary: var(--sidebar-primary);
|
|
@@ -470,7 +481,7 @@ export function aliasStep(status, srcRel) {
|
|
|
470
481
|
`configure the \`@/\` alias — the transplanted files import through it, and \`tsc\` fails without it:`,
|
|
471
482
|
];
|
|
472
483
|
if (status.tsconfig) {
|
|
473
|
-
// No `baseUrl`: paths resolve relative to the tsconfig since TS 4.
|
|
484
|
+
// No `baseUrl`: paths resolve relative to the tsconfig since TS 4.1, and on
|
|
474
485
|
// TS 6 the option is deprecated hard enough to fail the build (TS5101,
|
|
475
486
|
// measured on a stock vite react-ts project — the first snippet shipped it
|
|
476
487
|
// and traded six missing-module errors for one deprecation error).
|
|
@@ -497,6 +508,7 @@ export async function transplantBlock(block, targetDir) {
|
|
|
497
508
|
const written = [];
|
|
498
509
|
const npmDeps = new Set(["clsx", "tailwind-merge"]);
|
|
499
510
|
const visited = new Set();
|
|
511
|
+
const requiredVars = new Set();
|
|
500
512
|
function write(file) {
|
|
501
513
|
const dest = destFor(file, srcRoot);
|
|
502
514
|
const content = rewriteImports(file.content);
|
|
@@ -508,6 +520,13 @@ export async function transplantBlock(block, targetDir) {
|
|
|
508
520
|
npmDeps.add(pkg);
|
|
509
521
|
}
|
|
510
522
|
async function resolve(item) {
|
|
523
|
+
// Required CSS vars are collected transitively, not just off the top-level
|
|
524
|
+
// block. A component-tier asset pulled in as a registryDependency defines
|
|
525
|
+
// its own token needs, and reading only the block's meta let those slip
|
|
526
|
+
// through unchecked — the check passed while the transplanted asset lost
|
|
527
|
+
// its color (block-contract §8-5, carried contract ④).
|
|
528
|
+
for (const name of item.meta?.requiredCssVars ?? [])
|
|
529
|
+
requiredVars.add(name);
|
|
511
530
|
for (const dep of item.dependencies ?? [])
|
|
512
531
|
npmDeps.add(dep.split("@").slice(0, dep.startsWith("@") ? 2 : 1).join("@"));
|
|
513
532
|
for (const ref of item.registryDependencies ?? []) {
|
|
@@ -532,7 +551,7 @@ export async function transplantBlock(block, targetDir) {
|
|
|
532
551
|
writeFileSync(utils, `import { clsx, type ClassValue } from "clsx"\nimport { twMerge } from "tailwind-merge"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n`);
|
|
533
552
|
written.push(utils);
|
|
534
553
|
}
|
|
535
|
-
return { written, npmDeps, requiredCssVars:
|
|
554
|
+
return { written, npmDeps, requiredCssVars: [...requiredVars] };
|
|
536
555
|
}
|
|
537
556
|
// ---------------------------------------------------------------------------
|
|
538
557
|
// Orchestrator — brief → DESIGN.md → tokens → transplant → required-var check → verify
|
package/dist/load.js
CHANGED
|
@@ -15,6 +15,31 @@ export function loadTokensCss() {
|
|
|
15
15
|
export function loadRecipes() {
|
|
16
16
|
return loadJson("recipes.json");
|
|
17
17
|
}
|
|
18
|
+
export function loadKnowledge() {
|
|
19
|
+
return loadJson("knowledge.json");
|
|
20
|
+
}
|
|
21
|
+
/** Case-insensitive substring search over id, title, tags (high) and description (low). */
|
|
22
|
+
export function searchKnowledge(docs, query) {
|
|
23
|
+
const needle = query.trim().toLowerCase();
|
|
24
|
+
if (!needle)
|
|
25
|
+
return [];
|
|
26
|
+
const scored = [];
|
|
27
|
+
for (const doc of docs) {
|
|
28
|
+
const id = doc.id.toLowerCase();
|
|
29
|
+
const title = doc.title.toLowerCase();
|
|
30
|
+
const tags = doc.tags.map((t) => String(t).toLowerCase());
|
|
31
|
+
let score = 0;
|
|
32
|
+
if (id === needle || title === needle || tags.includes(needle))
|
|
33
|
+
score = 100;
|
|
34
|
+
else if (id.includes(needle) || title.includes(needle) || tags.some((t) => t.includes(needle)))
|
|
35
|
+
score = 60;
|
|
36
|
+
else if (doc.description.toLowerCase().includes(needle))
|
|
37
|
+
score = 20;
|
|
38
|
+
if (score > 0)
|
|
39
|
+
scored.push({ doc, score });
|
|
40
|
+
}
|
|
41
|
+
return scored.sort((a, b) => b.score - a.score || a.doc.id.localeCompare(b.doc.id)).map((s) => s.doc);
|
|
42
|
+
}
|
|
18
43
|
/** Case-insensitive substring search over id, names, aliases, one-liner, prompt phrases. */
|
|
19
44
|
export function searchTerms(terms, query) {
|
|
20
45
|
const needle = query.trim().toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askewly/design",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Query, inject, and verify the Askewly Design system (tokens, UI vocabulary, recipes) from your terminal or coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://ui.askewly.com",
|