oh-my-design-cli 0.1.2 → 1.0.0

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.
Files changed (74) hide show
  1. package/.claude/hooks/post-edit-watch.cjs +99 -0
  2. package/.claude/hooks/session-end-foldin.cjs +96 -0
  3. package/.claude/hooks/session-state-loader.cjs +64 -0
  4. package/.claude/hooks/skill-activation.cjs +73 -0
  5. package/.claude/settings.json +55 -0
  6. package/.claude/skills/skill-rules.json +87 -0
  7. package/AGENTS.md +111 -0
  8. package/README.md +75 -202
  9. package/agents/AGENT.md +53 -0
  10. package/agents/omd-3d-blender.md +269 -0
  11. package/agents/omd-a11y-auditor.md +97 -0
  12. package/agents/omd-asset-curator.md +260 -0
  13. package/agents/omd-critic.md +181 -0
  14. package/agents/omd-master.md +548 -0
  15. package/agents/omd-microcopy.md +63 -0
  16. package/agents/omd-persona-tester.md +118 -0
  17. package/agents/omd-ui-junior.md +129 -0
  18. package/agents/omd-ux-engineer.md +265 -0
  19. package/agents/omd-ux-researcher.md +62 -0
  20. package/agents/omd-ux-writer.md +181 -0
  21. package/data/opt-out-corpus.json +141 -0
  22. package/data/reference-fingerprints.json +1495 -0
  23. package/dist/bin/oh-my-design.js +3 -818
  24. package/dist/bin/oh-my-design.js.map +1 -1
  25. package/dist/install-skills-SVIYKXOE.js +442 -0
  26. package/dist/install-skills-SVIYKXOE.js.map +1 -0
  27. package/package.json +23 -23
  28. package/scripts/context.cjs +91 -0
  29. package/scripts/postinstall.cjs +54 -0
  30. package/skills/omd-apply/SKILL.md +64 -53
  31. package/skills/omd-harness/SKILL.md +271 -0
  32. package/skills/omd-learn/SKILL.md +55 -35
  33. package/skills/omd-remember/SKILL.md +93 -15
  34. package/skills/omd-sync/SKILL.md +140 -16
  35. package/dist/chunk-6YNSV3VY.js +0 -35
  36. package/dist/chunk-6YNSV3VY.js.map +0 -1
  37. package/dist/chunk-MHFYGZSO.js +0 -337
  38. package/dist/chunk-MHFYGZSO.js.map +0 -1
  39. package/dist/chunk-N2JG6N4Q.js +0 -264
  40. package/dist/chunk-N2JG6N4Q.js.map +0 -1
  41. package/dist/chunk-OOQQEUGX.js +0 -46
  42. package/dist/chunk-OOQQEUGX.js.map +0 -1
  43. package/dist/chunk-OR5DHENY.js +0 -250
  44. package/dist/chunk-OR5DHENY.js.map +0 -1
  45. package/dist/customizer-CM76752R.js +0 -8
  46. package/dist/customizer-CM76752R.js.map +0 -1
  47. package/dist/index.d.ts +0 -559
  48. package/dist/index.js +0 -3113
  49. package/dist/index.js.map +0 -1
  50. package/dist/init-UMM4XIV5.js +0 -675
  51. package/dist/init-UMM4XIV5.js.map +0 -1
  52. package/dist/install-skills-CM6VXFZJ.js +0 -152
  53. package/dist/install-skills-CM6VXFZJ.js.map +0 -1
  54. package/dist/learn-33LHKEJA.js +0 -140
  55. package/dist/learn-33LHKEJA.js.map +0 -1
  56. package/dist/reference-YMNAOXJQ.js +0 -47
  57. package/dist/reference-YMNAOXJQ.js.map +0 -1
  58. package/dist/reference-parser-TM3CJPNE.js +0 -10
  59. package/dist/reference-parser-TM3CJPNE.js.map +0 -1
  60. package/dist/remember-UAFA5B2O.js +0 -78
  61. package/dist/remember-UAFA5B2O.js.map +0 -1
  62. package/dist/sync-FDYRKNFE.js +0 -417
  63. package/dist/sync-FDYRKNFE.js.map +0 -1
  64. package/dist/templates/templates/design-md.hbs +0 -44
  65. package/dist/templates/templates/partials/agent-prompt-guide.hbs +0 -28
  66. package/dist/templates/templates/partials/color-palette.hbs +0 -49
  67. package/dist/templates/templates/partials/component-stylings.hbs +0 -28
  68. package/dist/templates/templates/partials/depth-elevation.hbs +0 -31
  69. package/dist/templates/templates/partials/dos-donts.hbs +0 -13
  70. package/dist/templates/templates/partials/layout.hbs +0 -30
  71. package/dist/templates/templates/partials/responsive.hbs +0 -25
  72. package/dist/templates/templates/partials/shadcn-tokens.hbs +0 -64
  73. package/dist/templates/templates/partials/typography.hbs +0 -43
  74. package/dist/templates/templates/partials/visual-theme.hbs +0 -26
@@ -5,34 +5,158 @@ description: "DESIGN.md shim 파일들(CLAUDE.md / AGENTS.md / .cursor/rules/omd
5
5
 
6
6
  # omd:sync — Shim Maintenance
7
7
 
8
- DESIGN.md가 모든 주요 AI 코딩 에이전트 (Claude Code, Codex, OpenCode, Cursor)에게 보이도록 shim 파일 3종을 관리한다.
8
+ DESIGN.md가 모든 주요 AI 코딩 에이전트(Claude Code, Codex, OpenCode, Cursor)에게 보이도록 shim 파일 3종을 관리한다. **CLI 호출 없음** — Read/Write/Edit 툴로 직접 처리.
9
9
 
10
- ## 관리 대상
10
+ ## 관리 대상 (3 파일)
11
11
 
12
- - `CLAUDE.md` Claude Code용 `@./DESIGN.md` import
13
- - `AGENTS.md` — Codex CLI + OpenCode 공용 pointer
14
- - `.cursor/rules/omd-design.mdc` Cursor auto-attach rule
12
+ | ID | 경로 | 모드 |
13
+ |---|---|---|
14
+ | `claude` | `CLAUDE.md` | block (managed marker만) |
15
+ | `agents` | `AGENTS.md` | block (managed marker만) |
16
+ | `cursor` | `.cursor/rules/omd-design.mdc` | whole (전체 파일이 omd 전용) |
15
17
 
16
- 파일은 `<!-- omd:start v=1 hash=... -->` ~ `<!-- omd:end -->` marker block으로 관리된다. Cursor mdc는 frontmatter 포함 전체 파일이 관리 단위.
18
+ block 모드는 `<!-- omd:start v=1 hash=<sha256:12> -->` ~ `<!-- omd:end -->` 마커 안만 관리, 나머지 사용자 콘텐츠 보존. whole 모드는 frontmatter 포함 전체 파일이 관리 단위.
17
19
 
18
- ## 사용
20
+ ## 템플릿 (정확히 이 본문 — 절대 paraphrase 금지)
19
21
 
20
- 사용자 요청에 따라 다음 중 하나를 Bash 툴로 실행:
22
+ ### CLAUDE.md body
23
+
24
+ ```markdown
25
+ # Design System (oh-my-design)
26
+
27
+ The authoritative brand & UI spec is **@./DESIGN.md**.
28
+ Read before any UI/styling/microcopy/motion work.
29
+
30
+ Preference log (pending corrections): @./.omd/preferences.md
31
+
32
+ Precedence: DESIGN.md > preferences.md > your defaults.
33
+ ```
34
+
35
+ ### AGENTS.md body
36
+
37
+ ```markdown
38
+ ## Design System (oh-my-design)
39
+
40
+ **Before any UI, styling, copy, or motion change, open and read `./DESIGN.md` in full.** It is the authoritative brand/design spec. Treat its tokens, voice, and component rules as binding unless the user overrides in chat.
41
+
42
+ If present, read `./.omd/preferences.md` — pending corrections not yet folded into DESIGN.md. Apply them; flag conflicts.
43
+ ```
44
+
45
+ ### .cursor/rules/omd-design.mdc (whole, frontmatter 포함)
46
+
47
+ ```mdc
48
+ ---
49
+ description: Authoritative brand & UI design system. Read DESIGN.md before UI work.
50
+ globs:
51
+ - "**/*.tsx"
52
+ - "**/*.jsx"
53
+ - "**/*.vue"
54
+ - "**/*.svelte"
55
+ - "**/*.css"
56
+ - "**/*.scss"
57
+ - "**/tailwind.config.*"
58
+ - "**/components/**"
59
+ - "**/app/**/page.*"
60
+ alwaysApply: false
61
+ ---
62
+
63
+ <!-- omd:start v=1 hash=<HASH> -->
64
+ The authoritative design spec lives at `@DESIGN.md` (repo root). Open and read before generating/modifying UI.
65
+
66
+ Pending preference corrections: `@.omd/preferences.md`.
67
+
68
+ Precedence: DESIGN.md > preferences.md > framework defaults.
69
+ <!-- omd:end -->
70
+ ```
71
+
72
+ ## 해시 계산
73
+
74
+ `<HASH>` = sha256 of the body content (마커 제외, body 텍스트만), 12자 hex prefix:
21
75
 
22
76
  ```bash
23
- omd sync # 인터랙티브 drift 시 overwrite/skip/show/quit 프롬프트
24
- omd sync --force # drift 포함 무조건 덮어쓰기
25
- omd sync --check # 상태만 검사 (CI/pre-commit, unclean 시 exit 1)
77
+ node -e "console.log(require('crypto').createHash('sha256').update(process.argv[1]).digest('hex').slice(0,12))" "<body 텍스트>"
78
+ ```
79
+
80
+ block 모드 마커 형식 정확히:
81
+ ```
82
+ <!-- omd:start v=1 hash=ab12cd34ef56 -->
83
+ <body>
84
+ <!-- omd:end -->
85
+ ```
86
+
87
+ ## 실행 절차
88
+
89
+ 사용자가 어떤 모드를 요청하는지 분기:
90
+ - **인터랙티브 (디폴트)** — drift 발견 시 사용자에게 묻기
91
+ - **--force 의도** ("강제 덮어쓰기") — drift 무시하고 덮어씀
92
+ - **--check 의도** ("상태만 검사") — 파일 상태만 출력, write 안 함
93
+
94
+ 각 shim별로:
95
+
96
+ ### Step 1 — Read existing
97
+ 파일 없으면 → status: `missing`, 새로 write 진행
98
+ 파일 있으면 → 내용 파싱
99
+
100
+ ### Step 2 — block 모드 파싱
101
+ `<!-- omd:start v=N hash=H -->` 라인 찾기:
102
+ - 없으면 → status: `missing` (block 부재, 사용자 content 외에 새로 추가 필요)
103
+ - 있으면 → marker 사이의 본문 추출. 추출된 본문의 sha256:12를 계산해서 marker의 `hash=H`와 비교
104
+ - 불일치 → status: `drifted` (사용자가 수동 편집함)
105
+ - 일치 + 본문 == 템플릿 → status: `clean`
106
+ - 일치 + 본문 != 템플릿 → status: `out-of-date` (omd 템플릿이 갱신됨)
107
+
108
+ ### Step 3 — whole 모드 파싱
109
+ existing 전체 content와 rendered 템플릿 비교:
110
+ - 동일 → `clean`
111
+ - 차이 → `drifted`
112
+
113
+ ### Step 4 — drift 처리
114
+ - **인터랙티브**: drift된 shim별로 "${path}는 수동 편집됐어요. 덮어쓸까요? (yes/no/show diff)" 묻기
115
+ - **--force**: drift 무시 덮어씀
116
+ - **--check**: drift 있으면 exit 1 동등 — 사용자에게 "drift detected" 보고 후 종료
117
+
118
+ ### Step 5 — Write
119
+ - block 모드: existing 안의 marker block만 새 hash + 새 body로 교체. 마커 외부 사용자 content는 보존
120
+ - whole 모드: 파일 전체를 새 rendered content로 교체. 디렉토리 (`.cursor/rules/`) 없으면 mkdir
121
+
122
+ ### Step 6 — sync-lock 갱신
123
+ `.omd/sync.lock.json` 기록 (없으면 만든다):
124
+ ```json
125
+ {
126
+ "design_md_hash": "<DESIGN.md sha256:12>",
127
+ "targets": {
128
+ "CLAUDE.md": "<hash>",
129
+ "AGENTS.md": "<hash>",
130
+ ".cursor/rules/omd-design.mdc": "<hash>"
131
+ },
132
+ "updated_at": "<ISO timestamp>"
133
+ }
134
+ ```
135
+
136
+ DESIGN.md 해시:
137
+ ```bash
138
+ [ -f DESIGN.md ] && node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync('DESIGN.md')).digest('hex').slice(0,12))"
139
+ ```
140
+
141
+ ## 결과 보고
142
+
143
+ ```
144
+ CLAUDE.md (block) — updated
145
+ AGENTS.md (block) — unchanged
146
+ .cursor/rules/omd-design.mdc — created
147
+ DESIGN.md hash: ab12cd34ef56
26
148
  ```
27
149
 
28
150
  ## 언제 실행하나
29
151
 
30
- - `DESIGN.md` 변경 직후 (shim hash 갱신)
31
- - 새 프로젝트에 처음 도입 (shim 3종 생성)
152
+ - DESIGN.md 변경 직후 (shim hash 갱신용)
153
+ - 새 프로젝트 도입 (3종 생성)
32
154
  - `.claude` / `.cursor` 디렉토리 추가 후
33
- - CI에서 상태 확인 (`--check`)
155
+ - "drift 확인" 요청
34
156
 
35
157
  ## 금지
36
158
 
37
- - shim 파일을 직접 편집하지 것. 반드시 `omd sync` 경유.
38
- - marker block 외부 유저 content는 보존된다 (CLAUDE.md / AGENTS.md만). mdc는 omd 전용이라 전체 덮어쓰기.
159
+ - 마커 본문에 임의 추가/축약 금지 템플릿 정확히 사용
160
+ - block 모드 파일에서 마커 외부 사용자 content 절대 삭제 금지
161
+ - `.omd/sync.lock.json` 무시 금지 — 항상 갱신
162
+ - DESIGN.md가 없어도 shim은 만들 수 있음 (DESIGN.md 생성 후에 hash만 채움)
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/core/agent-detect.ts
4
- import { existsSync } from "fs";
5
- import { join } from "path";
6
- function detectCallingAgent() {
7
- const env = process.env;
8
- if (env.CLAUDECODE === "1" || env.CLAUDE_CODE === "1" || env.CLAUDE_CODE_TASK_ID) {
9
- return "claude-code";
10
- }
11
- if (env.CODEX_SESSION_ID || env.CODEX || env.OPENAI_CODEX) {
12
- return "codex";
13
- }
14
- if (env.OPENCODE || env.OPENCODE_SESSION) {
15
- return "opencode";
16
- }
17
- if (env.CURSOR_SESSION_ID || env.CURSOR_AGENT) {
18
- return "cursor";
19
- }
20
- return "unknown";
21
- }
22
- function detectInstalledAgents(projectRoot) {
23
- return {
24
- claudeCode: existsSync(join(projectRoot, ".claude")) || existsSync(join(projectRoot, "CLAUDE.md")),
25
- codex: existsSync(join(projectRoot, ".codex")) || existsSync(join(projectRoot, "AGENTS.md")) || existsSync(join(projectRoot, "AGENTS.override.md")),
26
- opencode: existsSync(join(projectRoot, ".opencode")) || existsSync(join(projectRoot, "opencode.json")) || existsSync(join(projectRoot, "opencode.jsonc")),
27
- cursor: existsSync(join(projectRoot, ".cursor")) || existsSync(join(projectRoot, ".cursorrules"))
28
- };
29
- }
30
-
31
- export {
32
- detectCallingAgent,
33
- detectInstalledAgents
34
- };
35
- //# sourceMappingURL=chunk-6YNSV3VY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/agent-detect.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type AgentId = 'claude-code' | 'codex' | 'opencode' | 'cursor' | 'unknown';\n\nexport function detectCallingAgent(): AgentId {\n const env = process.env;\n\n if (env.CLAUDECODE === '1' || env.CLAUDE_CODE === '1' || env.CLAUDE_CODE_TASK_ID) {\n return 'claude-code';\n }\n if (env.CODEX_SESSION_ID || env.CODEX || env.OPENAI_CODEX) {\n return 'codex';\n }\n if (env.OPENCODE || env.OPENCODE_SESSION) {\n return 'opencode';\n }\n if (env.CURSOR_SESSION_ID || env.CURSOR_AGENT) {\n return 'cursor';\n }\n\n return 'unknown';\n}\n\nexport interface AgentPresence {\n claudeCode: boolean;\n codex: boolean;\n opencode: boolean;\n cursor: boolean;\n}\n\nexport function detectInstalledAgents(projectRoot: string): AgentPresence {\n return {\n claudeCode:\n existsSync(join(projectRoot, '.claude')) ||\n existsSync(join(projectRoot, 'CLAUDE.md')),\n codex:\n existsSync(join(projectRoot, '.codex')) ||\n existsSync(join(projectRoot, 'AGENTS.md')) ||\n existsSync(join(projectRoot, 'AGENTS.override.md')),\n opencode:\n existsSync(join(projectRoot, '.opencode')) ||\n existsSync(join(projectRoot, 'opencode.json')) ||\n existsSync(join(projectRoot, 'opencode.jsonc')),\n cursor:\n existsSync(join(projectRoot, '.cursor')) ||\n existsSync(join(projectRoot, '.cursorrules')),\n };\n}\n"],"mappings":";;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AAId,SAAS,qBAA8B;AAC5C,QAAM,MAAM,QAAQ;AAEpB,MAAI,IAAI,eAAe,OAAO,IAAI,gBAAgB,OAAO,IAAI,qBAAqB;AAChF,WAAO;AAAA,EACT;AACA,MAAI,IAAI,oBAAoB,IAAI,SAAS,IAAI,cAAc;AACzD,WAAO;AAAA,EACT;AACA,MAAI,IAAI,YAAY,IAAI,kBAAkB;AACxC,WAAO;AAAA,EACT;AACA,MAAI,IAAI,qBAAqB,IAAI,cAAc;AAC7C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AASO,SAAS,sBAAsB,aAAoC;AACxE,SAAO;AAAA,IACL,YACE,WAAW,KAAK,aAAa,SAAS,CAAC,KACvC,WAAW,KAAK,aAAa,WAAW,CAAC;AAAA,IAC3C,OACE,WAAW,KAAK,aAAa,QAAQ,CAAC,KACtC,WAAW,KAAK,aAAa,WAAW,CAAC,KACzC,WAAW,KAAK,aAAa,oBAAoB,CAAC;AAAA,IACpD,UACE,WAAW,KAAK,aAAa,WAAW,CAAC,KACzC,WAAW,KAAK,aAAa,eAAe,CAAC,KAC7C,WAAW,KAAK,aAAa,gBAAgB,CAAC;AAAA,IAChD,QACE,WAAW,KAAK,aAAa,SAAS,CAAC,KACvC,WAAW,KAAK,aAAa,cAAc,CAAC;AAAA,EAChD;AACF;","names":[]}
@@ -1,337 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/utils/color.ts
4
- function hexToRgb(hex) {
5
- const h = hex.replace("#", "");
6
- return [
7
- parseInt(h.slice(0, 2), 16),
8
- parseInt(h.slice(2, 4), 16),
9
- parseInt(h.slice(4, 6), 16)
10
- ];
11
- }
12
- function rgbToHex(r, g, b) {
13
- return "#" + [r, g, b].map((v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0")).join("");
14
- }
15
- function hexToHsl(hex) {
16
- const [r, g, b] = hexToRgb(hex).map((v) => v / 255);
17
- const max = Math.max(r, g, b);
18
- const min = Math.min(r, g, b);
19
- const l = (max + min) / 2;
20
- let h = 0;
21
- let s = 0;
22
- if (max !== min) {
23
- const d = max - min;
24
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
25
- switch (max) {
26
- case r:
27
- h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
28
- break;
29
- case g:
30
- h = ((b - r) / d + 2) / 6;
31
- break;
32
- case b:
33
- h = ((r - g) / d + 4) / 6;
34
- break;
35
- }
36
- }
37
- return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
38
- }
39
- function hslToHex(h, s, l) {
40
- const sn = s / 100;
41
- const ln = l / 100;
42
- const a = sn * Math.min(ln, 1 - ln);
43
- const f = (n) => {
44
- const k = (n + h / 30) % 12;
45
- const color = ln - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
46
- return Math.round(255 * color);
47
- };
48
- return rgbToHex(f(0), f(8), f(4));
49
- }
50
- function hslString(hex) {
51
- const [h, s, l] = hexToHsl(hex);
52
- return `${h} ${s}% ${l}%`;
53
- }
54
- function generateColorScale(hex) {
55
- const [h, s] = hexToHsl(hex);
56
- const lightnesses = {
57
- 50: 97,
58
- 100: 94,
59
- 200: 86,
60
- 300: 77,
61
- 400: 66,
62
- 500: 55,
63
- 600: 47,
64
- 700: 39,
65
- 800: 32,
66
- 900: 24,
67
- 950: 14
68
- };
69
- const scale = {};
70
- for (const [key, l] of Object.entries(lightnesses)) {
71
- scale[key] = hslToHex(h, s, l);
72
- }
73
- return scale;
74
- }
75
- function isLight(hex) {
76
- const [, , l] = hexToHsl(hex);
77
- return l > 55;
78
- }
79
- function contrastForeground(bgHex) {
80
- return isLight(bgHex) ? "#09090b" : "#fafafa";
81
- }
82
- function lighten(hex, amount) {
83
- const [h, s, l] = hexToHsl(hex);
84
- return hslToHex(h, s, Math.min(100, l + amount));
85
- }
86
- function darken(hex, amount) {
87
- const [h, s, l] = hexToHsl(hex);
88
- return hslToHex(h, s, Math.max(0, l - amount));
89
- }
90
- function generateChartColors(primaryHex) {
91
- const [h, s, l] = hexToHsl(primaryHex);
92
- return [
93
- primaryHex,
94
- hslToHex((h + 40) % 360, s, l),
95
- hslToHex((h + 80) % 360, s, l),
96
- hslToHex((h + 160) % 360, s, l),
97
- hslToHex((h + 220) % 360, s, l)
98
- ];
99
- }
100
-
101
- // src/core/customizer.ts
102
- function applyOverrides(ref, overrides, mode, components) {
103
- let md = ref.designMd;
104
- const effectivePrimary = overrides.primaryColor || ref.colors.primary;
105
- const effectiveFont = overrides.fontFamily || ref.typography.primary;
106
- const effectiveWeight = overrides.headingWeight || ref.typography.headingWeight;
107
- const effectiveRadius = overrides.borderRadius || ref.radius.replace(/[-–].*/, "").trim();
108
- const effectiveBg = ref.colors.background;
109
- const effectiveFg = ref.colors.foreground;
110
- md = md.replace(/[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1FA00}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}]/gu, "");
111
- if (mode === "customized") {
112
- md = md.replace(/^# .+$/m, `# Custom Design System (based on ${ref.name})`);
113
- if (overrides.primaryColor && overrides.primaryColor !== ref.colors.primary) {
114
- const re = new RegExp(ref.colors.primary.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
115
- md = re[Symbol.replace](md, overrides.primaryColor);
116
- }
117
- if (overrides.fontFamily && overrides.fontFamily !== ref.typography.primary) {
118
- md = md.replaceAll(ref.typography.primary, overrides.fontFamily);
119
- }
120
- }
121
- if (components && components.length > 0) {
122
- md += `
123
-
124
- ---
125
-
126
- ## Included Components
127
-
128
- The following components are part of this design system:
129
-
130
- `;
131
- md += components.map((c) => `- ${c.charAt(0).toUpperCase() + c.slice(1).replace(/-/g, " ")}`).join("\n");
132
- md += "\n";
133
- }
134
- md += buildIconographySection();
135
- const shadcnCss = generateShadcnCss(effectivePrimary, effectiveBg, effectiveFg, effectiveRadius, ref, overrides);
136
- md += buildDocumentPolicies();
137
- const previewData = buildPreviewData(ref, overrides, effectivePrimary, effectiveBg, effectiveFg, effectiveFont, effectiveWeight, effectiveRadius);
138
- return { designMd: md, shadcnCss, previewData };
139
- }
140
- function generateShadcnCss(primary, background, foreground, radius, ref, overrides) {
141
- const scale = generateColorScale(primary);
142
- const accent = ref.colors.accent || hslToHex((hexToHsl(primary)[0] + 30) % 360, 60, 55);
143
- const border = ref.colors.border || lighten(foreground, 75);
144
- const muted = lighten(background === "#ffffff" ? "#f5f5f5" : background, 5);
145
- const destructive = "#ef4444";
146
- const chart = generateChartColors(primary);
147
- const radiusRem = radius === "9999px" ? "9999px" : `${parseInt(radius) / 16}rem`;
148
- const vars = {
149
- "--background": hslString(background),
150
- "--foreground": hslString(foreground),
151
- "--card": hslString(background === "#ffffff" ? "#ffffff" : lighten(background, 3)),
152
- "--card-foreground": hslString(foreground),
153
- "--popover": hslString(background === "#ffffff" ? "#ffffff" : lighten(background, 5)),
154
- "--popover-foreground": hslString(foreground),
155
- "--primary": hslString(primary),
156
- "--primary-foreground": hslString(contrastForeground(primary)),
157
- "--secondary": hslString(scale[100]),
158
- "--secondary-foreground": hslString(foreground),
159
- "--muted": hslString(muted),
160
- "--muted-foreground": hslString(lighten(foreground, 40)),
161
- "--accent": hslString(accent),
162
- "--accent-foreground": hslString(contrastForeground(accent)),
163
- "--destructive": hslString(destructive),
164
- "--destructive-foreground": hslString(contrastForeground(destructive)),
165
- "--border": hslString(border),
166
- "--input": hslString(border),
167
- "--ring": hslString(primary),
168
- "--radius": radiusRem,
169
- "--chart-1": hslString(chart[0]),
170
- "--chart-2": hslString(chart[1]),
171
- "--chart-3": hslString(chart[2]),
172
- "--chart-4": hslString(chart[3]),
173
- "--chart-5": hslString(chart[4])
174
- };
175
- const lines = ["@layer base {", " :root {"];
176
- for (const [k, v] of Object.entries(vars)) {
177
- lines.push(` ${k}: ${v};`);
178
- }
179
- lines.push(" }");
180
- if (overrides.darkMode) {
181
- const darkBg = hslToHex(hexToHsl(primary)[0], 15, 7);
182
- const darkFg = "#fafafa";
183
- const darkBorder = hslToHex(hexToHsl(primary)[0], 10, 18);
184
- const darkMuted = hslToHex(hexToHsl(primary)[0], 10, 15);
185
- lines.push("", " .dark {");
186
- const darkVars = {
187
- "--background": hslString(darkBg),
188
- "--foreground": hslString(darkFg),
189
- "--card": hslString(lighten(darkBg, 3)),
190
- "--card-foreground": hslString(darkFg),
191
- "--popover": hslString(lighten(darkBg, 5)),
192
- "--popover-foreground": hslString(darkFg),
193
- "--primary": hslString(primary),
194
- "--primary-foreground": hslString(contrastForeground(primary)),
195
- "--secondary": hslString(hslToHex(hexToHsl(primary)[0], 15, 20)),
196
- "--secondary-foreground": hslString(darkFg),
197
- "--muted": hslString(darkMuted),
198
- "--muted-foreground": hslString(darken(darkFg, 35)),
199
- "--accent": hslString(accent),
200
- "--accent-foreground": hslString(contrastForeground(accent)),
201
- "--destructive": hslString(destructive),
202
- "--destructive-foreground": hslString(contrastForeground(destructive)),
203
- "--border": hslString(darkBorder),
204
- "--input": hslString(lighten(darkBorder, 5)),
205
- "--ring": hslString(primary),
206
- "--chart-1": hslString(chart[0]),
207
- "--chart-2": hslString(chart[1]),
208
- "--chart-3": hslString(chart[2]),
209
- "--chart-4": hslString(chart[3]),
210
- "--chart-5": hslString(chart[4])
211
- };
212
- for (const [k, v] of Object.entries(darkVars)) {
213
- lines.push(` ${k}: ${v};`);
214
- }
215
- lines.push(" }");
216
- }
217
- lines.push("}");
218
- return lines.join("\n");
219
- }
220
- function buildPreviewData(ref, overrides, primary, background, foreground, font, headingWeight, radius) {
221
- const scale = generateColorScale(primary);
222
- const accent = ref.colors.accent || hslToHex((hexToHsl(primary)[0] + 30) % 360, 60, 55);
223
- const border = ref.colors.border || lighten(foreground, 75);
224
- const chart = generateChartColors(primary);
225
- return {
226
- name: overrides.primaryColor || overrides.fontFamily ? `Custom (based on ${ref.name})` : ref.name,
227
- basedOn: ref.name,
228
- primary,
229
- background,
230
- foreground,
231
- font,
232
- headingWeight,
233
- radius,
234
- shadcnCss: "",
235
- // filled later
236
- designMd: "",
237
- // filled later
238
- colors: {
239
- primary,
240
- accent,
241
- muted: lighten(background === "#ffffff" ? "#f5f5f5" : background, 5),
242
- destructive: "#ef4444",
243
- border,
244
- scale,
245
- chart
246
- },
247
- darkMode: overrides.darkMode
248
- };
249
- }
250
- function buildIconographySection() {
251
- return `
252
-
253
- ---
254
-
255
- ## Iconography & SVG Guidelines
256
-
257
- ### Icon Library
258
-
259
- Use a single, consistent icon library throughout the project. Recommended options:
260
-
261
- - **Lucide React** (\`lucide-react\`): Default for shadcn/ui projects. 1,400+ icons, tree-shakeable, consistent 24x24 grid.
262
- - **Radix Icons** (\`@radix-ui/react-icons\`): 300+ icons, 15x15 grid, minimal and geometric.
263
- - **Heroicons** (\`@heroicons/react\`): 300+ icons by Tailwind team, outline and solid variants.
264
-
265
- Pick ONE library and use it everywhere. Do not mix icon libraries within the same project.
266
-
267
- ### SVG Usage Rules
268
-
269
- - All icons must be inline SVG components (not \`<img>\` tags) for color and size control.
270
- - Icon size follows the type scale: 16px (inline), 20px (buttons), 24px (standalone).
271
- - Icon color inherits from \`currentColor\` -- never hard-code fill/stroke colors.
272
- - For custom/brand icons, export as SVG components with \`currentColor\` fills.
273
- - Stroke width: 1.5px-2px for outline icons. Keep consistent across the project.
274
-
275
- ### Icon Sizing Scale
276
-
277
- | Context | Size | Usage |
278
- |---------|------|-------|
279
- | Inline text | 16px (1rem) | Badges, labels, breadcrumbs |
280
- | Button icon | 18px (1.125rem) | Icon buttons, CTA icons |
281
- | Standalone | 24px (1.5rem) | Navigation, card icons |
282
- | Feature | 32-48px | Hero sections, empty states |
283
-
284
- ### SVG Optimization
285
-
286
- - Run all custom SVGs through SVGO before committing.
287
- - Remove unnecessary attributes: \`xmlns\`, \`xml:space\`, editor metadata.
288
- - Use \`viewBox\` instead of fixed \`width\`/\`height\` for scalability.
289
- `;
290
- }
291
- function buildDocumentPolicies() {
292
- return `
293
-
294
- ---
295
-
296
- ## Document Policies
297
-
298
- ### No Emojis
299
-
300
- This design system must not use emojis in any UI element, component, label, status indicator, or documentation.
301
- Use SVG icons from the chosen icon library instead. Emojis render inconsistently across platforms and break visual coherence.
302
-
303
- - Status indicators: use colored dots or icon components, not emoji.
304
- - Section markers: use text prefixes ("DO:" / "DON'T:") or icons, not checkmark/cross emojis.
305
- - Navigation: use icon components, not emoji.
306
-
307
- ### Format Compliance
308
-
309
- This document follows the Google Stitch DESIGN.md 9-section format:
310
- 1. Visual Theme & Atmosphere
311
- 2. Color Palette & Roles
312
- 3. Typography Rules
313
- 4. Component Stylings
314
- 5. Layout Principles
315
- 6. Depth & Elevation
316
- 7. Do's and Don'ts
317
- 8. Responsive Behavior
318
- 9. Agent Prompt Guide
319
-
320
- Extended with:
321
- - Iconography & SVG Guidelines
322
- - Document Policies
323
-
324
- Total target length: 250-400 lines. Keep sections concise and actionable.
325
- `;
326
- }
327
-
328
- export {
329
- hexToHsl,
330
- hslToHex,
331
- generateColorScale,
332
- contrastForeground,
333
- lighten,
334
- darken,
335
- applyOverrides
336
- };
337
- //# sourceMappingURL=chunk-MHFYGZSO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/color.ts","../src/core/customizer.ts"],"sourcesContent":["import type { ColorScale, SemanticColor } from '../core/types.js';\n\n// ── Hex ↔ HSL conversions ────────────────────────────────────────\n\nexport function hexToRgb(hex: string): [number, number, number] {\n const h = hex.replace('#', '');\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ];\n}\n\nexport function rgbToHex(r: number, g: number, b: number): string {\n return (\n '#' +\n [r, g, b]\n .map((v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0'))\n .join('')\n );\n}\n\nexport function hexToHsl(hex: string): [number, number, number] {\n const [r, g, b] = hexToRgb(hex).map((v) => v / 255);\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n let h = 0;\n let s = 0;\n\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;\n case g: h = ((b - r) / d + 2) / 6; break;\n case b: h = ((r - g) / d + 4) / 6; break;\n }\n }\n\n return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];\n}\n\nexport function hslToHex(h: number, s: number, l: number): string {\n const sn = s / 100;\n const ln = l / 100;\n const a = sn * Math.min(ln, 1 - ln);\n const f = (n: number) => {\n const k = (n + h / 30) % 12;\n const color = ln - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);\n return Math.round(255 * color);\n };\n return rgbToHex(f(0), f(8), f(4));\n}\n\n/** Format HSL for shadcn CSS variables: \"210 40% 98%\" */\nexport function hslString(hex: string): string {\n const [h, s, l] = hexToHsl(hex);\n return `${h} ${s}% ${l}%`;\n}\n\n// ── Palette generation ───────────────────────────────────────────\n\n/** Generate a full 11-stop color scale from a single hex */\nexport function generateColorScale(hex: string): ColorScale {\n const [h, s] = hexToHsl(hex);\n\n const lightnesses: Record<keyof ColorScale, number> = {\n 50: 97, 100: 94, 200: 86, 300: 77,\n 400: 66, 500: 55, 600: 47, 700: 39,\n 800: 32, 900: 24, 950: 14,\n };\n\n const scale = {} as ColorScale;\n for (const [key, l] of Object.entries(lightnesses)) {\n (scale as unknown as Record<string, string>)[key] = hslToHex(h, s, l);\n }\n return scale;\n}\n\n/** Determine if a color is \"light\" (needs dark foreground) */\nexport function isLight(hex: string): boolean {\n const [, , l] = hexToHsl(hex);\n return l > 55;\n}\n\n/** Pick a contrasting foreground for a given background */\nexport function contrastForeground(bgHex: string): string {\n return isLight(bgHex) ? '#09090b' : '#fafafa';\n}\n\n/** Create a semantic color pair (base + auto-contrast foreground) */\nexport function semanticColor(base: string): SemanticColor {\n return { base, foreground: contrastForeground(base) };\n}\n\n/** Lighten a hex color by a percentage (0-100) */\nexport function lighten(hex: string, amount: number): string {\n const [h, s, l] = hexToHsl(hex);\n return hslToHex(h, s, Math.min(100, l + amount));\n}\n\n/** Darken a hex color by a percentage (0-100) */\nexport function darken(hex: string, amount: number): string {\n const [h, s, l] = hexToHsl(hex);\n return hslToHex(h, s, Math.max(0, l - amount));\n}\n\n/** Desaturate a color */\nexport function desaturate(hex: string, amount: number): string {\n const [h, s, l] = hexToHsl(hex);\n return hslToHex(h, Math.max(0, s - amount), l);\n}\n\n/** Generate chart colors from primary by rotating hue */\nexport function generateChartColors(primaryHex: string): [string, string, string, string, string] {\n const [h, s, l] = hexToHsl(primaryHex);\n return [\n primaryHex,\n hslToHex((h + 40) % 360, s, l),\n hslToHex((h + 80) % 360, s, l),\n hslToHex((h + 160) % 360, s, l),\n hslToHex((h + 220) % 360, s, l),\n ];\n}\n","import type { ReferenceEntry } from './reference-parser.js';\nimport type { CustomOverrides } from '../cli/prompts.js';\nimport {\n generateColorScale,\n contrastForeground,\n hslString,\n hexToHsl,\n hslToHex,\n lighten,\n darken,\n generateChartColors,\n} from '../utils/color.js';\n\n/**\n * Apply user overrides to a reference DESIGN.md.\n *\n * Strategy:\n * - \"as-is\": return the original DESIGN.md content with minimal additions\n * - \"customized\": perform text-level replacements for color/font/radius/weight\n * and append a customization summary + shadcn CSS variables block\n *\n * This is intentionally NOT an AI call. It's deterministic string transformation.\n */\nexport function applyOverrides(\n ref: ReferenceEntry,\n overrides: CustomOverrides,\n mode: 'as-is' | 'customized',\n components?: string[],\n): { designMd: string; shadcnCss: string; previewData: PreviewData } {\n let md = ref.designMd;\n\n const effectivePrimary = overrides.primaryColor || ref.colors.primary;\n const effectiveFont = overrides.fontFamily || ref.typography.primary;\n const effectiveWeight = overrides.headingWeight || ref.typography.headingWeight;\n const effectiveRadius = overrides.borderRadius || ref.radius.replace(/[-–].*/, '').trim();\n const effectiveBg = ref.colors.background;\n const effectiveFg = ref.colors.foreground;\n\n // Strip emojis. The unicode range covers ✅ (U+2705) and ❌ (U+274C) too,\n // so any DO:/DON'T: prefix conversion would never match — references use\n // explicit \"**DO**\" / \"**DON'T**\" markdown instead.\n md = md.replace(/[\\u{1F300}-\\u{1F9FF}\\u{2600}-\\u{26FF}\\u{2700}-\\u{27BF}\\u{FE00}-\\u{FE0F}\\u{1FA00}-\\u{1FAFF}\\u{200D}\\u{20E3}\\u{E0020}-\\u{E007F}]/gu, '');\n\n if (mode === 'customized') {\n // Direct replacement — one source of truth for AI agents\n md = md.replace(/^# .+$/m, `# Custom Design System (based on ${ref.name})`);\n\n if (overrides.primaryColor && overrides.primaryColor !== ref.colors.primary) {\n const re = new RegExp(ref.colors.primary.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'gi');\n md = re[Symbol.replace](md, overrides.primaryColor);\n }\n if (overrides.fontFamily && overrides.fontFamily !== ref.typography.primary) {\n md = md.replaceAll(ref.typography.primary, overrides.fontFamily);\n }\n }\n\n // Append component list\n if (components && components.length > 0) {\n md += `\\n\\n---\\n\\n## Included Components\\n\\nThe following components are part of this design system:\\n\\n`;\n md += components.map(c => `- ${c.charAt(0).toUpperCase() + c.slice(1).replace(/-/g, ' ')}`).join('\\n');\n md += '\\n';\n }\n\n // Append iconography section\n md += buildIconographySection();\n\n // Generate shadcn CSS for preview data only (no longer appended to DESIGN.md)\n const shadcnCss = generateShadcnCss(effectivePrimary, effectiveBg, effectiveFg, effectiveRadius, ref, overrides);\n\n // Append document policies\n md += buildDocumentPolicies();\n\n // Build preview data\n const previewData = buildPreviewData(ref, overrides, effectivePrimary, effectiveBg, effectiveFg, effectiveFont, effectiveWeight, effectiveRadius);\n\n return { designMd: md, shadcnCss, previewData };\n}\n\n// ── Text replacements ────────────────────────────────────────────\n\nfunction replaceColor(md: string, oldHex: string, newHex: string): string {\n // Replace the exact hex (case-insensitive)\n const regex = new RegExp(escapeRegex(oldHex), 'gi');\n return md.replace(regex, newHex);\n}\n\nfunction replaceFont(md: string, oldFont: string, newFont: string): string {\n const regex = new RegExp(escapeRegex(oldFont), 'g');\n return md.replace(regex, newFont);\n}\n\nfunction replaceWeight(md: string, oldWeight: string, newWeight: string): string {\n // Only replace weight in Display/Heading rows and key characteristics\n // Be careful not to replace all numbers\n const regex = new RegExp(`(Display.*?\\\\|\\\\s*)${oldWeight}(\\\\s*\\\\|)`, 'g');\n let result = md.replace(regex, `$1${newWeight}$2`);\n // Also replace \"weight X\" patterns\n result = result.replace(\n new RegExp(`weight ${oldWeight}`, 'g'),\n `weight ${newWeight}`,\n );\n return result;\n}\n\nfunction replaceRadius(md: string, oldRadius: string, newRadius: string): string {\n // Replace \"Xpx\" radius patterns\n const oldBase = oldRadius.replace(/[-–].*/, '').trim();\n if (oldBase === newRadius) return md;\n const regex = new RegExp(`${escapeRegex(oldBase)}`, 'g');\n return md.replace(regex, newRadius);\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n// ── Customization summary section ────────────────────────────────\n\nfunction buildCustomizationSummary(ref: ReferenceEntry, overrides: CustomOverrides): string {\n const changes: string[] = [];\n if (overrides.primaryColor) {\n changes.push(`- **Primary color**: ${ref.colors.primary} → ${overrides.primaryColor}`);\n }\n if (overrides.fontFamily) {\n changes.push(`- **Font**: ${ref.typography.primary} → ${overrides.fontFamily}`);\n }\n if (overrides.headingWeight) {\n changes.push(`- **Heading weight**: ${ref.typography.headingWeight} → ${overrides.headingWeight}`);\n }\n if (overrides.borderRadius) {\n changes.push(`- **Border radius**: ${ref.radius} → ${overrides.borderRadius}`);\n }\n if (overrides.additionalNotes) {\n changes.push(`- **Additional notes**: ${overrides.additionalNotes}`);\n }\n\n if (changes.length === 0) return '';\n\n return `\n\n---\n\n## Customization Applied\n\n> Based on **${ref.name}** design system with the following modifications:\n\n${changes.join('\\n')}\n`;\n}\n\n// ── shadcn CSS generation ────────────────────────────────────────\n\nfunction generateShadcnCss(\n primary: string,\n background: string,\n foreground: string,\n radius: string,\n ref: ReferenceEntry,\n overrides: CustomOverrides,\n): string {\n const scale = generateColorScale(primary);\n const accent = ref.colors.accent || hslToHex((hexToHsl(primary)[0] + 30) % 360, 60, 55);\n const border = ref.colors.border || lighten(foreground, 75);\n const muted = lighten(background === '#ffffff' ? '#f5f5f5' : background, 5);\n const destructive = '#ef4444';\n const chart = generateChartColors(primary);\n\n const radiusRem = radius === '9999px' ? '9999px' : `${parseInt(radius) / 16}rem`;\n\n const vars: Record<string, string> = {\n '--background': hslString(background),\n '--foreground': hslString(foreground),\n '--card': hslString(background === '#ffffff' ? '#ffffff' : lighten(background, 3)),\n '--card-foreground': hslString(foreground),\n '--popover': hslString(background === '#ffffff' ? '#ffffff' : lighten(background, 5)),\n '--popover-foreground': hslString(foreground),\n '--primary': hslString(primary),\n '--primary-foreground': hslString(contrastForeground(primary)),\n '--secondary': hslString(scale[100]),\n '--secondary-foreground': hslString(foreground),\n '--muted': hslString(muted),\n '--muted-foreground': hslString(lighten(foreground, 40)),\n '--accent': hslString(accent),\n '--accent-foreground': hslString(contrastForeground(accent)),\n '--destructive': hslString(destructive),\n '--destructive-foreground': hslString(contrastForeground(destructive)),\n '--border': hslString(border),\n '--input': hslString(border),\n '--ring': hslString(primary),\n '--radius': radiusRem,\n '--chart-1': hslString(chart[0]),\n '--chart-2': hslString(chart[1]),\n '--chart-3': hslString(chart[2]),\n '--chart-4': hslString(chart[3]),\n '--chart-5': hslString(chart[4]),\n };\n\n const lines = ['@layer base {', ' :root {'];\n for (const [k, v] of Object.entries(vars)) {\n lines.push(` ${k}: ${v};`);\n }\n lines.push(' }');\n\n // Dark mode\n if (overrides.darkMode) {\n const darkBg = hslToHex(hexToHsl(primary)[0], 15, 7);\n const darkFg = '#fafafa';\n const darkBorder = hslToHex(hexToHsl(primary)[0], 10, 18);\n const darkMuted = hslToHex(hexToHsl(primary)[0], 10, 15);\n\n lines.push('', ' .dark {');\n const darkVars: Record<string, string> = {\n '--background': hslString(darkBg),\n '--foreground': hslString(darkFg),\n '--card': hslString(lighten(darkBg, 3)),\n '--card-foreground': hslString(darkFg),\n '--popover': hslString(lighten(darkBg, 5)),\n '--popover-foreground': hslString(darkFg),\n '--primary': hslString(primary),\n '--primary-foreground': hslString(contrastForeground(primary)),\n '--secondary': hslString(hslToHex(hexToHsl(primary)[0], 15, 20)),\n '--secondary-foreground': hslString(darkFg),\n '--muted': hslString(darkMuted),\n '--muted-foreground': hslString(darken(darkFg, 35)),\n '--accent': hslString(accent),\n '--accent-foreground': hslString(contrastForeground(accent)),\n '--destructive': hslString(destructive),\n '--destructive-foreground': hslString(contrastForeground(destructive)),\n '--border': hslString(darkBorder),\n '--input': hslString(lighten(darkBorder, 5)),\n '--ring': hslString(primary),\n '--chart-1': hslString(chart[0]),\n '--chart-2': hslString(chart[1]),\n '--chart-3': hslString(chart[2]),\n '--chart-4': hslString(chart[3]),\n '--chart-5': hslString(chart[4]),\n };\n for (const [k, v] of Object.entries(darkVars)) {\n lines.push(` ${k}: ${v};`);\n }\n lines.push(' }');\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction buildShadcnSection(css: string): string {\n return `\n\n---\n\n## 10. shadcn/ui Theme\n\nCopy this CSS block into your \\`globals.css\\` to apply this design system to shadcn/ui components.\n\n\\`\\`\\`css\n${css}\n\\`\\`\\`\n`;\n}\n\n// ── Preview Data ─────────────────────────────────────────────────\n\nexport interface PreviewData {\n name: string;\n basedOn: string;\n primary: string;\n background: string;\n foreground: string;\n font: string;\n headingWeight: string;\n radius: string;\n shadcnCss: string;\n designMd: string;\n colors: {\n primary: string;\n accent: string;\n muted: string;\n destructive: string;\n border: string;\n scale: Record<string, string>;\n chart: string[];\n };\n darkMode: boolean;\n}\n\nfunction buildPreviewData(\n ref: ReferenceEntry,\n overrides: CustomOverrides,\n primary: string,\n background: string,\n foreground: string,\n font: string,\n headingWeight: string,\n radius: string,\n): PreviewData {\n const scale = generateColorScale(primary);\n const accent = ref.colors.accent || hslToHex((hexToHsl(primary)[0] + 30) % 360, 60, 55);\n const border = ref.colors.border || lighten(foreground, 75);\n const chart = generateChartColors(primary);\n\n return {\n name: overrides.primaryColor || overrides.fontFamily ? `Custom (based on ${ref.name})` : ref.name,\n basedOn: ref.name,\n primary,\n background,\n foreground,\n font,\n headingWeight,\n radius,\n shadcnCss: '', // filled later\n designMd: '', // filled later\n colors: {\n primary,\n accent,\n muted: lighten(background === '#ffffff' ? '#f5f5f5' : background, 5),\n destructive: '#ef4444',\n border,\n scale: scale as unknown as Record<string, string>,\n chart,\n },\n darkMode: overrides.darkMode,\n };\n}\n\n// ── Iconography section ──────────────────────────────────────────\n\nfunction buildIconographySection(): string {\n return `\n\n---\n\n## Iconography & SVG Guidelines\n\n### Icon Library\n\nUse a single, consistent icon library throughout the project. Recommended options:\n\n- **Lucide React** (\\`lucide-react\\`): Default for shadcn/ui projects. 1,400+ icons, tree-shakeable, consistent 24x24 grid.\n- **Radix Icons** (\\`@radix-ui/react-icons\\`): 300+ icons, 15x15 grid, minimal and geometric.\n- **Heroicons** (\\`@heroicons/react\\`): 300+ icons by Tailwind team, outline and solid variants.\n\nPick ONE library and use it everywhere. Do not mix icon libraries within the same project.\n\n### SVG Usage Rules\n\n- All icons must be inline SVG components (not \\`<img>\\` tags) for color and size control.\n- Icon size follows the type scale: 16px (inline), 20px (buttons), 24px (standalone).\n- Icon color inherits from \\`currentColor\\` -- never hard-code fill/stroke colors.\n- For custom/brand icons, export as SVG components with \\`currentColor\\` fills.\n- Stroke width: 1.5px-2px for outline icons. Keep consistent across the project.\n\n### Icon Sizing Scale\n\n| Context | Size | Usage |\n|---------|------|-------|\n| Inline text | 16px (1rem) | Badges, labels, breadcrumbs |\n| Button icon | 18px (1.125rem) | Icon buttons, CTA icons |\n| Standalone | 24px (1.5rem) | Navigation, card icons |\n| Feature | 32-48px | Hero sections, empty states |\n\n### SVG Optimization\n\n- Run all custom SVGs through SVGO before committing.\n- Remove unnecessary attributes: \\`xmlns\\`, \\`xml:space\\`, editor metadata.\n- Use \\`viewBox\\` instead of fixed \\`width\\`/\\`height\\` for scalability.\n`;\n}\n\n// ── Document policies ────────────────────────────────────────────\n\nfunction buildDocumentPolicies(): string {\n return `\n\n---\n\n## Document Policies\n\n### No Emojis\n\nThis design system must not use emojis in any UI element, component, label, status indicator, or documentation.\nUse SVG icons from the chosen icon library instead. Emojis render inconsistently across platforms and break visual coherence.\n\n- Status indicators: use colored dots or icon components, not emoji.\n- Section markers: use text prefixes (\"DO:\" / \"DON'T:\") or icons, not checkmark/cross emojis.\n- Navigation: use icon components, not emoji.\n\n### Format Compliance\n\nThis document follows the Google Stitch DESIGN.md 9-section format:\n1. Visual Theme & Atmosphere\n2. Color Palette & Roles\n3. Typography Rules\n4. Component Stylings\n5. Layout Principles\n6. Depth & Elevation\n7. Do's and Don'ts\n8. Responsive Behavior\n9. Agent Prompt Guide\n\nExtended with:\n- Iconography & SVG Guidelines\n- Document Policies\n\nTotal target length: 250-400 lines. Keep sections concise and actionable.\n`;\n}\n"],"mappings":"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","names":[]}