@conscience-technology/ctds 0.0.0-stage → 0.1.1

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 (289) hide show
  1. package/README.md +128 -2
  2. package/assets/components/AppFrame/AppFrame.module.css +177 -0
  3. package/assets/components/AppFrame/AppFrame.tsx +93 -0
  4. package/assets/components/AppFrame/index.ts +6 -0
  5. package/assets/components/AttachmentChip/AttachmentChip.module.css +71 -0
  6. package/assets/components/AttachmentChip/AttachmentChip.tsx +74 -0
  7. package/assets/components/AttachmentChip/index.ts +2 -0
  8. package/assets/components/AttachmentThumb/AttachmentThumb.module.css +107 -0
  9. package/assets/components/AttachmentThumb/AttachmentThumb.tsx +91 -0
  10. package/assets/components/AttachmentThumb/index.ts +2 -0
  11. package/assets/components/Avatar/Avatar.module.css +167 -0
  12. package/assets/components/Avatar/Avatar.tsx +228 -0
  13. package/assets/components/Avatar/index.ts +2 -0
  14. package/assets/components/AvatarUpload/AvatarUpload.module.css +130 -0
  15. package/assets/components/AvatarUpload/AvatarUpload.tsx +139 -0
  16. package/assets/components/AvatarUpload/index.ts +2 -0
  17. package/assets/components/Badge/Badge.module.css +76 -0
  18. package/assets/components/Badge/Badge.tsx +68 -0
  19. package/assets/components/Badge/index.ts +2 -0
  20. package/assets/components/Breadcrumb/Breadcrumb.module.css +213 -0
  21. package/assets/components/Breadcrumb/Breadcrumb.tsx +279 -0
  22. package/assets/components/Breadcrumb/index.ts +8 -0
  23. package/assets/components/Button/Button.module.css +216 -0
  24. package/assets/components/Button/Button.tsx +149 -0
  25. package/assets/components/Button/index.ts +2 -0
  26. package/assets/components/Callout/Callout.module.css +146 -0
  27. package/assets/components/Callout/Callout.tsx +138 -0
  28. package/assets/components/Callout/index.ts +2 -0
  29. package/assets/components/ChatInput/ChatInput.module.css +254 -0
  30. package/assets/components/ChatInput/ChatInput.tsx +322 -0
  31. package/assets/components/ChatInput/ModelSelector.tsx +40 -0
  32. package/assets/components/ChatInput/SendButton.tsx +41 -0
  33. package/assets/components/ChatInput/index.ts +10 -0
  34. package/assets/components/Checkbox/Checkbox.module.css +66 -0
  35. package/assets/components/Checkbox/Checkbox.tsx +36 -0
  36. package/assets/components/Checkbox/index.ts +2 -0
  37. package/assets/components/Chip/Chip.module.css +128 -0
  38. package/assets/components/Chip/Chip.tsx +103 -0
  39. package/assets/components/Chip/index.ts +2 -0
  40. package/assets/components/ChoiceCard/ChoiceCard.module.css +142 -0
  41. package/assets/components/ChoiceCard/ChoiceCard.tsx +132 -0
  42. package/assets/components/ChoiceCard/index.ts +2 -0
  43. package/assets/components/ContextMenu/ContextMenu.module.css +102 -0
  44. package/assets/components/ContextMenu/ContextMenu.tsx +392 -0
  45. package/assets/components/ContextMenu/index.ts +9 -0
  46. package/assets/components/DatePicker/Calendar.tsx +234 -0
  47. package/assets/components/DatePicker/DESIGN_NOTES.md +106 -0
  48. package/assets/components/DatePicker/DESIGN_NOTES_TIME.md +125 -0
  49. package/assets/components/DatePicker/DatePicker.module.css +295 -0
  50. package/assets/components/DatePicker/DatePicker.tsx +387 -0
  51. package/assets/components/DatePicker/TimeSelector.tsx +69 -0
  52. package/assets/components/DatePicker/index.ts +8 -0
  53. package/assets/components/DatePicker/presets.ts +80 -0
  54. package/assets/components/DatePicker/utils.ts +148 -0
  55. package/assets/components/Disclosure/Disclosure.module.css +73 -0
  56. package/assets/components/Disclosure/Disclosure.tsx +129 -0
  57. package/assets/components/Disclosure/index.ts +6 -0
  58. package/assets/components/Drawer/Drawer.module.css +257 -0
  59. package/assets/components/Drawer/Drawer.tsx +126 -0
  60. package/assets/components/Drawer/index.ts +7 -0
  61. package/assets/components/EmptyState/EmptyState.module.css +102 -0
  62. package/assets/components/EmptyState/EmptyState.tsx +80 -0
  63. package/assets/components/EmptyState/index.ts +2 -0
  64. package/assets/components/FileIcon/FileIcon.module.css +66 -0
  65. package/assets/components/FileIcon/FileIcon.tsx +84 -0
  66. package/assets/components/FileIcon/fileExtMap.ts +112 -0
  67. package/assets/components/FileIcon/index.ts +9 -0
  68. package/assets/components/FilterButton/FilterButton.module.css +130 -0
  69. package/assets/components/FilterButton/FilterButton.tsx +94 -0
  70. package/assets/components/FilterButton/index.ts +2 -0
  71. package/assets/components/GalleryItem/GalleryItem.module.css +220 -0
  72. package/assets/components/GalleryItem/GalleryItem.tsx +163 -0
  73. package/assets/components/GalleryItem/index.ts +7 -0
  74. package/assets/components/Icon/Icon.tsx +616 -0
  75. package/assets/components/Icon/context.tsx +39 -0
  76. package/assets/components/Icon/index.ts +4 -0
  77. package/assets/components/IconButton/IconButton.module.css +109 -0
  78. package/assets/components/IconButton/IconButton.tsx +110 -0
  79. package/assets/components/IconButton/index.ts +2 -0
  80. package/assets/components/Input/Input.module.css +338 -0
  81. package/assets/components/Input/Input.tsx +371 -0
  82. package/assets/components/Input/index.ts +2 -0
  83. package/assets/components/ListItem/ListItem.module.css +318 -0
  84. package/assets/components/ListItem/ListItem.tsx +262 -0
  85. package/assets/components/ListItem/index.ts +9 -0
  86. package/assets/components/MenuItem/MenuItem.module.css +437 -0
  87. package/assets/components/MenuItem/MenuItem.tsx +529 -0
  88. package/assets/components/MenuItem/index.ts +8 -0
  89. package/assets/components/MetricCard/MetricCard.module.css +153 -0
  90. package/assets/components/MetricCard/MetricCard.tsx +140 -0
  91. package/assets/components/MetricCard/index.ts +2 -0
  92. package/assets/components/Modal/Modal.module.css +175 -0
  93. package/assets/components/Modal/Modal.tsx +98 -0
  94. package/assets/components/Modal/index.ts +2 -0
  95. package/assets/components/Onboarding/DocsIndex.tsx +82 -0
  96. package/assets/components/Onboarding/LayoutBuilder.tsx +160 -0
  97. package/assets/components/Onboarding/Onboarding.module.css +1394 -0
  98. package/assets/components/Onboarding/OnboardingAxisPanel.tsx +515 -0
  99. package/assets/components/Onboarding/OnboardingPage.tsx +1193 -0
  100. package/assets/components/Onboarding/PresetsGallery.tsx +65 -0
  101. package/assets/components/Onboarding/actions.ts +250 -0
  102. package/assets/components/Onboarding/index.ts +8 -0
  103. package/assets/components/Onboarding/palette.css +40 -0
  104. package/assets/components/Onboarding/wizardData.ts +265 -0
  105. package/assets/components/Pagination/Pagination.module.css +82 -0
  106. package/assets/components/Pagination/Pagination.tsx +248 -0
  107. package/assets/components/Pagination/index.ts +6 -0
  108. package/assets/components/ProgressBar/ProgressBar.module.css +64 -0
  109. package/assets/components/ProgressBar/ProgressBar.tsx +54 -0
  110. package/assets/components/ProgressBar/index.ts +2 -0
  111. package/assets/components/QuotationBar/QuotationBar.module.css +61 -0
  112. package/assets/components/QuotationBar/QuotationBar.tsx +48 -0
  113. package/assets/components/QuotationBar/index.ts +2 -0
  114. package/assets/components/Radio/Radio.module.css +63 -0
  115. package/assets/components/Radio/Radio.tsx +33 -0
  116. package/assets/components/Radio/index.ts +2 -0
  117. package/assets/components/Select/Select.module.css +151 -0
  118. package/assets/components/Select/Select.tsx +425 -0
  119. package/assets/components/Select/index.ts +10 -0
  120. package/assets/components/SelectableCard/SelectableCard.module.css +177 -0
  121. package/assets/components/SelectableCard/SelectableCard.tsx +83 -0
  122. package/assets/components/SelectableCard/index.ts +2 -0
  123. package/assets/components/Shortcut/Shortcut.module.css +56 -0
  124. package/assets/components/Shortcut/Shortcut.tsx +156 -0
  125. package/assets/components/Shortcut/index.ts +2 -0
  126. package/assets/components/Sidebar/Sidebar.module.css +242 -0
  127. package/assets/components/Sidebar/Sidebar.tsx +264 -0
  128. package/assets/components/Sidebar/index.ts +7 -0
  129. package/assets/components/SidebarSplit/SidebarSplit.module.css +126 -0
  130. package/assets/components/SidebarSplit/SidebarSplit.tsx +222 -0
  131. package/assets/components/SidebarSplit/index.ts +6 -0
  132. package/assets/components/Slider/Slider.module.css +120 -0
  133. package/assets/components/Slider/Slider.tsx +88 -0
  134. package/assets/components/Slider/index.ts +2 -0
  135. package/assets/components/SplitButton/SplitButton.module.css +190 -0
  136. package/assets/components/SplitButton/SplitButton.tsx +145 -0
  137. package/assets/components/SplitButton/index.ts +6 -0
  138. package/assets/components/Stepper/Stepper.module.css +182 -0
  139. package/assets/components/Stepper/Stepper.tsx +185 -0
  140. package/assets/components/Stepper/index.ts +2 -0
  141. package/assets/components/Switch/Switch.module.css +116 -0
  142. package/assets/components/Switch/Switch.tsx +58 -0
  143. package/assets/components/Switch/index.ts +2 -0
  144. package/assets/components/TableCell/TableCell.module.css +137 -0
  145. package/assets/components/TableCell/TableCellAvatar.tsx +42 -0
  146. package/assets/components/TableCell/TableCellBadge.tsx +23 -0
  147. package/assets/components/TableCell/TableCellIcon.tsx +39 -0
  148. package/assets/components/TableCell/TableCellImage.tsx +49 -0
  149. package/assets/components/TableCell/TableCellLink.tsx +33 -0
  150. package/assets/components/TableCell/TableCellMeter.tsx +55 -0
  151. package/assets/components/TableCell/TableCellNumber.tsx +43 -0
  152. package/assets/components/TableCell/TableCellText.tsx +30 -0
  153. package/assets/components/TableCell/index.ts +23 -0
  154. package/assets/components/TableHeader/TableHeader.module.css +287 -0
  155. package/assets/components/TableHeader/TableHeader.tsx +590 -0
  156. package/assets/components/TableHeader/index.ts +9 -0
  157. package/assets/components/TableRow/TableRow.module.css +290 -0
  158. package/assets/components/TableRow/TableRow.tsx +253 -0
  159. package/assets/components/TableRow/index.ts +8 -0
  160. package/assets/components/TableRowEdit/TableRowEdit.module.css +126 -0
  161. package/assets/components/TableRowEdit/TableRowEdit.tsx +247 -0
  162. package/assets/components/TableRowEdit/index.ts +2 -0
  163. package/assets/components/TableRowSelect/TableRowSelect.module.css +23 -0
  164. package/assets/components/TableRowSelect/TableRowSelect.tsx +175 -0
  165. package/assets/components/TableRowSelect/index.ts +2 -0
  166. package/assets/components/Tabs/Tabs.module.css +234 -0
  167. package/assets/components/Tabs/Tabs.tsx +426 -0
  168. package/assets/components/Tabs/index.ts +9 -0
  169. package/assets/components/Textarea/Textarea.module.css +318 -0
  170. package/assets/components/Textarea/Textarea.tsx +426 -0
  171. package/assets/components/Textarea/index.ts +7 -0
  172. package/assets/components/ThemeController.tsx +271 -0
  173. package/assets/components/TimePicker/TimePicker.module.css +317 -0
  174. package/assets/components/TimePicker/TimePicker.tsx +821 -0
  175. package/assets/components/TimePicker/index.ts +8 -0
  176. package/assets/components/TimePicker/utils.ts +115 -0
  177. package/assets/components/Toast/Toast.module.css +264 -0
  178. package/assets/components/Toast/Toast.tsx +243 -0
  179. package/assets/components/Toast/index.ts +15 -0
  180. package/assets/components/Toolbar/Toolbar.module.css +72 -0
  181. package/assets/components/Toolbar/Toolbar.tsx +75 -0
  182. package/assets/components/Toolbar/index.ts +2 -0
  183. package/assets/components/Tooltip/Tooltip.module.css +42 -0
  184. package/assets/components/Tooltip/Tooltip.tsx +332 -0
  185. package/assets/components/Tooltip/index.ts +2 -0
  186. package/assets/components/Topbar/Topbar.module.css +26 -0
  187. package/assets/components/Topbar/Topbar.tsx +47 -0
  188. package/assets/components/Topbar/index.ts +2 -0
  189. package/assets/components/_cn.ts +3 -0
  190. package/assets/components/_color.ts +48 -0
  191. package/assets/components/_data.ts +659 -0
  192. package/assets/components/_utils.css +19 -0
  193. package/assets/components/axisOptions.ts +24 -0
  194. package/assets/components/tokens.css +742 -0
  195. package/assets/prompts/GUIDE.md +251 -0
  196. package/assets/prompts/OVERRIDE.md +170 -0
  197. package/assets/prompts/PRINCIPLES.md +130 -0
  198. package/assets/prompts/axes/density.md +38 -0
  199. package/assets/prompts/axes/font.md +36 -0
  200. package/assets/prompts/axes/hue.md +36 -0
  201. package/assets/prompts/axes/icon.md +48 -0
  202. package/assets/prompts/axes/mode.md +50 -0
  203. package/assets/prompts/axes/motion.md +77 -0
  204. package/assets/prompts/axes/palette.md +35 -0
  205. package/assets/prompts/axes/shape.md +33 -0
  206. package/assets/prompts/axes/voice.md +54 -0
  207. package/assets/prompts/components/README.md +84 -0
  208. package/assets/prompts/components/app-frame.md +94 -0
  209. package/assets/prompts/components/attachment-chip.md +46 -0
  210. package/assets/prompts/components/attachment-thumb.md +49 -0
  211. package/assets/prompts/components/avatar.md +86 -0
  212. package/assets/prompts/components/badge.md +104 -0
  213. package/assets/prompts/components/breadcrumb.md +77 -0
  214. package/assets/prompts/components/button.md +99 -0
  215. package/assets/prompts/components/callout.md +81 -0
  216. package/assets/prompts/components/chat-input.md +84 -0
  217. package/assets/prompts/components/checkbox.md +46 -0
  218. package/assets/prompts/components/chip.md +84 -0
  219. package/assets/prompts/components/choice-card.md +127 -0
  220. package/assets/prompts/components/context-menu.md +145 -0
  221. package/assets/prompts/components/date-picker.md +104 -0
  222. package/assets/prompts/components/drawer.md +152 -0
  223. package/assets/prompts/components/empty-state.md +69 -0
  224. package/assets/prompts/components/file-icon.md +73 -0
  225. package/assets/prompts/components/filter-button.md +42 -0
  226. package/assets/prompts/components/form-field.md +74 -0
  227. package/assets/prompts/components/gallery-item.md +158 -0
  228. package/assets/prompts/components/icon-button.md +82 -0
  229. package/assets/prompts/components/icon.md +68 -0
  230. package/assets/prompts/components/input.md +95 -0
  231. package/assets/prompts/components/list-item.md +235 -0
  232. package/assets/prompts/components/menu-item.md +234 -0
  233. package/assets/prompts/components/metric-card.md +132 -0
  234. package/assets/prompts/components/modal.md +131 -0
  235. package/assets/prompts/components/pagination.md +92 -0
  236. package/assets/prompts/components/progress-bar.md +61 -0
  237. package/assets/prompts/components/quotation-bar.md +39 -0
  238. package/assets/prompts/components/radio.md +41 -0
  239. package/assets/prompts/components/select.md +104 -0
  240. package/assets/prompts/components/selectable-card.md +67 -0
  241. package/assets/prompts/components/shortcut.md +76 -0
  242. package/assets/prompts/components/sidebar.md +107 -0
  243. package/assets/prompts/components/slider.md +89 -0
  244. package/assets/prompts/components/split-button.md +71 -0
  245. package/assets/prompts/components/stepper.md +93 -0
  246. package/assets/prompts/components/switch.md +86 -0
  247. package/assets/prompts/components/table-cell.md +87 -0
  248. package/assets/prompts/components/table-header.md +157 -0
  249. package/assets/prompts/components/table-row-edit.md +129 -0
  250. package/assets/prompts/components/table-row-select.md +82 -0
  251. package/assets/prompts/components/table-row.md +156 -0
  252. package/assets/prompts/components/tabs.md +122 -0
  253. package/assets/prompts/components/textarea.md +105 -0
  254. package/assets/prompts/components/toast.md +149 -0
  255. package/assets/prompts/components/tooltip.md +71 -0
  256. package/assets/prompts/components/topbar.md +148 -0
  257. package/assets/prompts/primitives/emerge.md +43 -0
  258. package/assets/prompts/primitives/fade.md +31 -0
  259. package/assets/prompts/primitives/scale.md +32 -0
  260. package/assets/prompts/primitives/scroll.md +75 -0
  261. package/assets/prompts/primitives/slide.md +31 -0
  262. package/assets/prompts/themes/_SCHEMA.md +135 -0
  263. package/assets/prompts/themes/_TEMPLATE.md +73 -0
  264. package/assets/prompts/themes/expressive.md +118 -0
  265. package/assets/prompts/themes/pure.md +87 -0
  266. package/assets/prompts/themes/spare.md +73 -0
  267. package/assets/prompts/tokens/breakpoint.md +29 -0
  268. package/assets/prompts/tokens/color.md +78 -0
  269. package/assets/prompts/tokens/radius.md +33 -0
  270. package/assets/prompts/tokens/shadow.md +35 -0
  271. package/assets/prompts/tokens/spacing.md +86 -0
  272. package/assets/prompts/tokens/typography.md +76 -0
  273. package/assets/prompts/tokens/z-index.md +35 -0
  274. package/assets/public/expressive.png +0 -0
  275. package/assets/public/pure.png +0 -0
  276. package/assets/public/spare.png +0 -0
  277. package/assets/skill/SKILL.md +36 -0
  278. package/bin/ctds.js +76 -0
  279. package/package.json +35 -4
  280. package/src/commands/init.js +215 -0
  281. package/src/commands/update.js +275 -0
  282. package/src/utils/assets.js +78 -0
  283. package/src/utils/deps.js +51 -0
  284. package/src/utils/detect.js +47 -0
  285. package/src/utils/diff.js +52 -0
  286. package/src/utils/hash.js +16 -0
  287. package/src/utils/log.js +41 -0
  288. package/src/utils/meta.js +73 -0
  289. package/src/utils/prompt.js +28 -0
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import { useTheme } from "../ThemeController";
4
+ import { Button } from "../Button";
5
+ import { themePresets } from "../_data";
6
+ import styles from "./Onboarding.module.css";
7
+
8
+ export function PresetsGallery() {
9
+ const { applyPreset, palette, hue, shape, density, motionMode } = useTheme();
10
+
11
+ return (
12
+ <div className={styles.cardGrid}>
13
+ {themePresets.map((p) => {
14
+ const active =
15
+ palette === p.values.palette &&
16
+ hue === p.values.hue &&
17
+ shape === p.values.shape &&
18
+ density === p.values.density &&
19
+ motionMode === p.values.motion;
20
+
21
+ return (
22
+ <div
23
+ key={p.id}
24
+ className={styles.card}
25
+ data-active={active || undefined}
26
+ >
27
+ <div className={styles.cardHead}>
28
+ <span className={styles.cardTitle}>{p.label}</span>
29
+ <div className={styles.cardSwatches} aria-hidden>
30
+ <span
31
+ className={styles.cardSwatch}
32
+ style={{
33
+ background: `oklch(0.55 0.14 ${p.values.hue})`,
34
+ }}
35
+ />
36
+ {p.values.accentHue != null && (
37
+ <span
38
+ className={styles.cardSwatch}
39
+ style={{
40
+ background: `oklch(0.55 0.14 ${p.values.accentHue})`,
41
+ }}
42
+ />
43
+ )}
44
+ </div>
45
+ </div>
46
+ <p className={styles.cardDesc}>{p.desc}</p>
47
+ <p className={styles.cardDesc}>
48
+ {p.values.palette} · {p.values.shape} · {p.values.density} ·{" "}
49
+ {p.values.motion}
50
+ </p>
51
+ <div className={styles.cardFoot}>
52
+ <Button
53
+ variant={active ? "primary" : "ghost"}
54
+ size="sm"
55
+ onClick={() => applyPreset(p.values)}
56
+ >
57
+ {active ? "적용됨" : "적용"}
58
+ </Button>
59
+ </div>
60
+ </div>
61
+ );
62
+ })}
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,250 @@
1
+ "use server";
2
+
3
+ /**
4
+ * 온보딩 완료 시 플젝 파일에 선택을 영구 반영하는 server action.
5
+ *
6
+ * CTDS는 "사용자가 자기 플젝에 복사해서 쓰는" 모델. `npx ctds init`으로 설치하면
7
+ * 이 서버 액션이 사용자 플젝의 /ctds 라우트에 심어지고, "적용" 버튼이 눌리면
8
+ * 사용자 플젝 루트에 두 파일이 쓰인다:
9
+ *
10
+ * 1) ctds.config.json — 축 값 JSON. 머신 리더블 소스.
11
+ * ThemeController가 import해서 SSR 초기값으로 사용.
12
+ * AI 에이전트(Claude Code 등)도 이걸 읽어서 축 참조.
13
+ * 2) DESIGN.md — 축 값 + 테마 산문 요약. 사람·AI 모두 읽는 설명.
14
+ *
15
+ * layout.tsx 자동 패치는 하지 않음 — 사용자 플젝마다 구조가 달라 정규식 치환이
16
+ * 깨지기 쉬움. ThemeController가 ctds.config.json을 initialConfig prop으로
17
+ * 받으면 됨.
18
+ *
19
+ * 로컬 dev 환경에서만 돌아간다는 가정. 프로덕션 서버리스에서는 FS 쓰기 거부.
20
+ */
21
+
22
+ import { writeFile, access, readFile } from "node:fs/promises";
23
+ import { join, dirname, parse } from "node:path";
24
+ import type {
25
+ DensityValue,
26
+ IconFamily,
27
+ LangValue,
28
+ MotionValue,
29
+ PaletteValue,
30
+ ShapeValue,
31
+ VoiceValue,
32
+ } from "../_data";
33
+
34
+ export type AppliedConfig = {
35
+ theme: "none" | "pure" | "spare" | "expressive";
36
+ mode: "light" | "dark";
37
+ palette: PaletteValue;
38
+ hue: number;
39
+ accentHue?: number | null;
40
+ shape: ShapeValue;
41
+ density: DensityValue;
42
+ motion: MotionValue;
43
+ iconFamily: IconFamily;
44
+ voice: VoiceValue;
45
+ lang: LangValue;
46
+ focusRing: "on" | "off";
47
+ /** 참고용 메타. */
48
+ appliedAt: string;
49
+ };
50
+
51
+ export type ApplyResult =
52
+ | { ok: true; files: string[] }
53
+ | { ok: false; error: string; files: string[] };
54
+
55
+ const TARGET_FILES = ["ctds.config.json", "DESIGN.md"] as const;
56
+
57
+ /**
58
+ * 사용자 플젝 루트 결정. Next.js dev server의 process.cwd()가 꼭 플젝 루트가 아님:
59
+ * - 모노레포에서 `web/`에 Next.js 있으면 cwd가 web/이 됨 (CTDS 레포 자체가 그런 경우)
60
+ * - pnpm/yarn workspaces에서도 비슷
61
+ * - 사용자가 자기 플젝 루트에 CTDS를 설치해 /ctds를 심은 경우 cwd=플젝루트가 맞음
62
+ *
63
+ * 로직:
64
+ * 1) cwd에서 시작해 위로 올라가며 모든 package.json 수집
65
+ * 2) 가장 바깥쪽 package.json 디렉토리를 "진짜 프로젝트 루트"로 선택
66
+ * - 보통 사용자 플젝: 바로 cwd에 package.json 하나뿐 → cwd 선택 (OK)
67
+ * - 모노레포/workspaces: 바깥에 workspace root package.json 발견 → 그걸 선택
68
+ * - CTDS 자기 레포: web/package.json과 루트 package.json 둘 다 → 루트 선택
69
+ * 3) 아무것도 못 찾으면 cwd 폴백
70
+ */
71
+ async function findProjectRoot(startDir: string): Promise<string> {
72
+ const { root: fsRoot } = parse(startDir);
73
+ const pkgDirs: string[] = [];
74
+ let dir = startDir;
75
+ while (true) {
76
+ const pkgPath = join(dir, "package.json");
77
+ try {
78
+ await readFile(pkgPath, "utf8");
79
+ pkgDirs.push(dir);
80
+ } catch {
81
+ // package.json 없거나 파싱 실패 — 그냥 넘어감
82
+ }
83
+ if (dir === fsRoot) break;
84
+ const parent = dirname(dir);
85
+ if (parent === dir) break;
86
+ dir = parent;
87
+ }
88
+ // pkgDirs는 cwd → 상위 순이라 마지막이 가장 바깥.
89
+ return pkgDirs.length > 0 ? pkgDirs[pkgDirs.length - 1] : startDir;
90
+ }
91
+
92
+ const PROJECT_ROOT = () => findProjectRoot(process.cwd());
93
+
94
+ async function fileExists(path: string): Promise<boolean> {
95
+ try {
96
+ await access(path);
97
+ return true;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+
103
+ /** 적용 전에 호출해서 기존에 어떤 파일이 있는지 확인. 다이얼로그에서 "덮어쓸래?" 묻는 용. */
104
+ export async function checkExistingFiles(): Promise<{ existing: string[] }> {
105
+ const root = await PROJECT_ROOT();
106
+ const existing: string[] = [];
107
+ for (const f of TARGET_FILES) {
108
+ if (await fileExists(join(root, f))) existing.push(f);
109
+ }
110
+ return { existing };
111
+ }
112
+
113
+ /** 두 아티팩트를 쓴다. 하나라도 실패하면 그 지점에서 중단하고 어디까지 썼는지 리턴.
114
+ * overwrite=false(기본값)에서 기존 파일 있으면 거부. 호출자가 명시적으로 덮어쓰기 승인하면 true. */
115
+ export async function applyOnboarding(
116
+ config: AppliedConfig,
117
+ options: { overwrite?: boolean } = {}
118
+ ): Promise<ApplyResult> {
119
+ // 프로덕션 서버리스에서 /ctds가 실수로 노출됐을 때 FS 쓰기 거부.
120
+ if (process.env.NODE_ENV === "production") {
121
+ return {
122
+ ok: false,
123
+ error:
124
+ "/ctds 는 로컬 개발 환경 전용이에요. 프로덕션 빌드에선 파일을 쓸 수 없습니다. 로컬에서 `npm run dev` 로 돌려주세요.",
125
+ files: [],
126
+ };
127
+ }
128
+
129
+ if (!options.overwrite) {
130
+ const { existing } = await checkExistingFiles();
131
+ if (existing.length > 0) {
132
+ return {
133
+ ok: false,
134
+ error: `이미 존재: ${existing.join(", ")}. 덮어쓰려면 확인 후 다시 적용해주세요.`,
135
+ files: [],
136
+ };
137
+ }
138
+ }
139
+
140
+ const root = await PROJECT_ROOT();
141
+ const written: string[] = [];
142
+ try {
143
+ const configPath = join(root, "ctds.config.json");
144
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
145
+ written.push("ctds.config.json");
146
+
147
+ const designPath = join(root, "DESIGN.md");
148
+ await writeFile(designPath, renderDesignMd(config), "utf8");
149
+ written.push("DESIGN.md");
150
+
151
+ return { ok: true, files: written };
152
+ } catch (e) {
153
+ return {
154
+ ok: false,
155
+ error: e instanceof Error ? e.message : String(e),
156
+ files: written,
157
+ };
158
+ }
159
+ }
160
+
161
+ /** DESIGN.md 생성. 테마가 "none"이면 커스텀 조합 설명, 그 외엔 테마 프롬프트 기반. */
162
+ function renderDesignMd(c: AppliedConfig): string {
163
+ const date = c.appliedAt.slice(0, 10);
164
+ const themeLine =
165
+ c.theme === "none"
166
+ ? "**직접 조합** — 테마 preset 없이 사용자가 각 축을 직접 선택했습니다."
167
+ : `**${c.theme}** 테마 기반. 자세한 원칙·Do/Don't는 [prompts/themes/${c.theme}.md](prompts/themes/${c.theme}.md) 참조.`;
168
+
169
+ const hex =
170
+ c.accentHue != null
171
+ ? `· 보조 hue=${c.accentHue}°`
172
+ : "";
173
+
174
+ return `# 디자인 결정
175
+
176
+ > 생성일: ${date} · 온보딩 적용 결과
177
+ > 머신 리더블 소스는 [ctds.config.json](ctds.config.json)에.
178
+
179
+ ${themeLine}
180
+
181
+ ## 축 값
182
+
183
+ | Axis | Value | 설명 |
184
+ |---|---|---|
185
+ | Mode | \`${c.mode}\` | ${c.mode === "light" ? "밝은 배경" : "어두운 배경"} |
186
+ | Palette | \`${c.palette}\` | ${paletteDesc(c.palette)} |
187
+ | Hue | \`${c.hue}°\` ${hex} | 브랜드 색상 축 |
188
+ | Shape | \`${c.shape}\` | ${shapeDesc(c.shape)} |
189
+ | Density | \`${c.density}\` | ${densityDesc(c.density)} |
190
+ | Motion | \`${c.motion}\` | ${motionDesc(c.motion)} |
191
+ | Icon Family | \`${c.iconFamily}\` | 아이콘 세트 |
192
+ | Voice | \`${c.voice}\` | ${voiceDesc(c.voice)} |
193
+ | Lang | \`${c.lang}\` | UI 언어 |
194
+ | Focus Ring | \`${c.focusRing}\` | 포커스 링 ${c.focusRing === "on" ? "표시" : "숨김"} |
195
+
196
+ ## AI 에이전트에게
197
+
198
+ 이 프로젝트는 위 축 값으로 고정됐습니다. 컴포넌트·페이지를 생성할 때:
199
+
200
+ - **하드코딩 금지** — 색/간격/radius/shadow/motion은 전부 CTDS 토큰 경유.
201
+ - 축 값은 [ctds.config.json](ctds.config.json)이 소스 오브 트루스.
202
+ 런타임엔 \`ThemeController\`가 관리하며, 사용자가 축을 바꿀 때마다 \`<html data-*>\` 속성이 갱신됩니다.
203
+ - 토큰·축의 상세 규칙은 [prompts/](prompts/) 하위 문서들을 참조하세요.
204
+ ${
205
+ c.theme === "none"
206
+ ? ""
207
+ : `- 이 프로젝트의 "성격"은 [prompts/themes/${c.theme}.md](prompts/themes/${c.theme}.md)의 Do/Don't·Signature·Negative에 정의돼 있습니다.`
208
+ }
209
+
210
+ ## 변경 이력
211
+
212
+ - ${date}: 온보딩 적용
213
+ `;
214
+ }
215
+
216
+ function paletteDesc(v: PaletteValue): string {
217
+ return {
218
+ mono: "채도 0 · 무채색 지배 · primary는 소수 지점만",
219
+ vivid: "원본 hue · 진한 채도",
220
+ standard: "원본 hue · 중간 채도 (기본)",
221
+ calm: "warm shift · 낮은 채도",
222
+ pastel: "cool shift · 매우 밝은 명도",
223
+ }[v];
224
+ }
225
+ function shapeDesc(v: ShapeValue): string {
226
+ return { soft: "말랑, 캐주얼", standard: "표준", crisp: "딱딱, 전문" }[v];
227
+ }
228
+ function densityDesc(v: DensityValue): string {
229
+ return {
230
+ compact: "높은 밀도 (85%)",
231
+ default: "표준 (100%)",
232
+ comfortable: "낮은 밀도 (106%)",
233
+ }[v];
234
+ }
235
+ function motionDesc(v: MotionValue): string {
236
+ return {
237
+ bouncy: "쫀득한 오버슛",
238
+ standard: "짧고 매끈",
239
+ calm: "차분",
240
+ none: "애니 없음",
241
+ }[v];
242
+ }
243
+ function voiceDesc(v: VoiceValue): string {
244
+ return {
245
+ formal: "격식체 (입니다)",
246
+ neutral: "표준 존대",
247
+ casual: "친근체",
248
+ }[v];
249
+ }
250
+
@@ -0,0 +1,8 @@
1
+ /**
2
+ * CTDS 온보딩 패키지 — 사용자 플젝의 /ctds 라우트에서 쓰는 UI.
3
+ *
4
+ * 사용 예 (사용자 플젝의 app/ctds/page.tsx):
5
+ * import { OnboardingPage } from "@/components/ctds/Onboarding";
6
+ * export default function Page() { return <OnboardingPage />; }
7
+ */
8
+ export { OnboardingPage } from "./OnboardingPage";
@@ -0,0 +1,40 @@
1
+ /* Onboarding 페이지 전용 팔레트.
2
+ 축(hue/palette) 시스템과 독립된 고정 브랜드 컬러. 온보딩은 웜 아이보리 배경 +
3
+ 차분한 wine·brown rail 조합으로 톤을 고정. 사용자가 "Cloud Dancer + 세련된 브라운
4
+ + 와인" 요청. 축 값 변경 영향 안 받음. */
5
+
6
+ :root {
7
+ /* 아주 옅은 웜 오프화이트 — 거의 순백 (Cloud Dancer보다 밝게) */
8
+ --onb-bg: #F8F6F2;
9
+
10
+ /* Rail — 웜 차콜(거의 뉴트럴, warm undertone).
11
+ Wine·brown은 강조 없이 아주 미세하게만 배어남. */
12
+ --onb-rail: #2C2825;
13
+ --onb-rail-deep: #201D1A;
14
+
15
+ /* Accent — 정말 필요할 때만. Active state 강조에 subtle하게. */
16
+ --onb-accent: #6B4F4C;
17
+
18
+ /* Rail 위 텍스트/보조 */
19
+ --onb-rail-fg: #EEE9E2;
20
+ --onb-rail-fg-muted: #A69B94;
21
+ --onb-rail-fg-subtle: #6D6560;
22
+ --onb-rail-surface: rgba(238, 233, 226, 0.10);
23
+ --onb-rail-line: rgba(238, 233, 226, 0.14);
24
+ --onb-rail-dot-idle: rgba(238, 233, 226, 0.22);
25
+ --onb-rail-progress-dot: rgba(238, 233, 226, 0.28);
26
+
27
+ /* 콘텐츠 (우측) */
28
+ --onb-card-bg: #FFFFFF;
29
+ --onb-back-btn: #E8E4DD;
30
+ --onb-back-btn-hover: #DED8CE;
31
+ --onb-fg: #2A2622;
32
+ --onb-fg-muted: #6E655F;
33
+ --onb-border: rgba(42, 38, 34, 0.08);
34
+ --onb-border-strong: rgba(42, 38, 34, 0.14);
35
+
36
+ /* Mode preview swatch — 모드 축 미리보기용 고정 색. 현재 테마와 무관하게
37
+ light/dark 두 비주얼을 동시에 보여주기 위함. */
38
+ --onb-mode-light: #FFFFFF;
39
+ --onb-mode-dark: #1A1A1A;
40
+ }
@@ -0,0 +1,265 @@
1
+ import type {
2
+ DensityValue,
3
+ IconFamily,
4
+ MotionValue,
5
+ PaletteValue,
6
+ ShapeValue,
7
+ ThemePreset,
8
+ } from "../_data";
9
+
10
+ export type ThemeId = "none" | "pure" | "spare" | "expressive";
11
+ export type VibeId = "balanced" | "playful" | "serious";
12
+ export type DensityChoiceId = DensityValue;
13
+ export type LayoutId = "sidebar-only" | "topbar-sidebar" | "dual-sidebar";
14
+
15
+ // ── 디자인 스타일(테마) ─────────────────────────────────
16
+ // 테마를 고르면 뒤의 축(vibe/density 등) 디폴트가 묶여서 결정됩니다.
17
+ // 사용자는 뒤 단계에서 override 가능. "none"은 축을 하나씩 직접 고르는 흐름.
18
+ // 테마 정의 원본: prompts/themes/<slug>.md (pure / spare / expressive)
19
+
20
+ export type ThemeBadgeStatus = "success" | "warning" | "destructive" | "info";
21
+
22
+ export type ThemeChoice = {
23
+ id: ThemeId;
24
+ title: string;
25
+ /** 영어 접두. 뱃지로 title 앞에 노출. "none"은 뱃지 없음. */
26
+ badge?: string;
27
+ /** Badge status — soft variant에서 색이 결정됨. */
28
+ badgeStatus?: ThemeBadgeStatus;
29
+ hint: string;
30
+ examples: string;
31
+ };
32
+
33
+ export const THEME_CHOICES: ThemeChoice[] = [
34
+ {
35
+ id: "none",
36
+ title: "직접 원하는 스타일로 만들기",
37
+ hint: "분위기 · 컬러 · 밀도 · 레이아웃을 단계별로 직접 설정합니다. CTDS 기본값에서 시작.",
38
+ examples: "자체 디자인 가이드가 있거나, 축을 세밀하게 조정하고 싶은 경우",
39
+ },
40
+ {
41
+ id: "spare",
42
+ title: "차분하고 정돈된",
43
+ badge: "Spare",
44
+ badgeStatus: "info",
45
+ hint: "정해둔 격자 안에서만 움직여요. 매일 열어도 피로하지 않게, 꼭 필요한 것만 담아요.",
46
+ examples: "Vercel · Supabase · Raycast · Linear 같은 앱 · 대시보드 · 설정 · 문서",
47
+ },
48
+ {
49
+ id: "pure",
50
+ title: "덜어냄으로 세련된",
51
+ badge: "Pure",
52
+ badgeStatus: "success",
53
+ hint: "선 · 카드 · 그림자처럼 가두는 장치를 빼요. 톤 차이와 여백만으로 자연스럽게 흐르게.",
54
+ examples: "Stripe · Framer · Linear 랜딩. 첫인상이 중요한 소개 페이지",
55
+ },
56
+ {
57
+ id: "expressive",
58
+ title: "밝고 통통 튀는",
59
+ badge: "Expressive",
60
+ badgeStatus: "warning",
61
+ hint: "파스텔 색으로 분류하고, 카드가 살짝 떠 있어요. 매일 열고 싶은 생기 있는 UI.",
62
+ examples: "Notion Calendar · Superhuman · Arc 같은 스케줄 · 메일 · 메시지 앱",
63
+ },
64
+ ];
65
+
66
+ export type VibeChoice = {
67
+ id: VibeId;
68
+ title: string;
69
+ hint: string;
70
+ examples: string;
71
+ values: {
72
+ shape: ShapeValue;
73
+ motion: MotionValue;
74
+ /** 선택된 vibe의 "serious" variant가 palette=mono를 승계하는 데 쓰임. */
75
+ palette?: PaletteValue;
76
+ };
77
+ };
78
+
79
+ export const VIBE_CHOICES: VibeChoice[] = [
80
+ {
81
+ id: "balanced",
82
+ title: "무난한 표준",
83
+ hint: "SaaS 기본. 코너·모션 모두 중간값. 튀지도 심심하지도 않게.",
84
+ examples: "일반 SaaS, 사내 도구, 이커머스 관리",
85
+ values: { shape: "standard", motion: "standard" },
86
+ },
87
+ {
88
+ id: "playful",
89
+ title: "활력 있고 통통 튀는",
90
+ hint: "코너는 둥글고, 모션은 쫀득하게, 색은 보조색까지. 브랜드가 기억되어야 하는 화면.",
91
+ examples: "스타트업 프로덕트, 마케팅 사이트, 온보딩·튜토리얼",
92
+ values: { shape: "soft", motion: "bouncy" },
93
+ },
94
+ {
95
+ id: "serious",
96
+ title: "격식있는 프로 도구",
97
+ hint: "모노톤. 색은 최소 지점에만. 코너는 각지고 모션은 조용히.",
98
+ examples: "관리자 대시보드, 분석 도구, 인프라 콘솔, 공공기관, 금융",
99
+ values: { shape: "crisp", motion: "calm", palette: "mono" },
100
+ },
101
+ ];
102
+
103
+ // ── 컬러 & 톤 ─────────────────────────────────────────
104
+
105
+ export type PaletteChoice = {
106
+ id: PaletteValue;
107
+ title: string;
108
+ hint: string;
109
+ examples: string;
110
+ };
111
+
112
+ export const PALETTE_CHOICES: PaletteChoice[] = [
113
+ {
114
+ id: "vivid",
115
+ title: "선명하게",
116
+ hint: "진하고 또렷한 색",
117
+ examples: "스타트업, 마케팅, 캠페인 페이지",
118
+ },
119
+ {
120
+ id: "standard",
121
+ title: "표준",
122
+ hint: "무난한 기본 채도",
123
+ examples: "일반 웹앱, 관리 도구",
124
+ },
125
+ {
126
+ id: "calm",
127
+ title: "차분하게",
128
+ hint: "탁한 웜 톤",
129
+ examples: "공공기관, 금융, 신중한 브랜드",
130
+ },
131
+ {
132
+ id: "pastel",
133
+ title: "밝고 파스텔",
134
+ hint: "연하고 밝은 톤",
135
+ examples: "교육, 콘텐츠, 웰니스, 어린이 프로덕트",
136
+ },
137
+ ];
138
+
139
+ export type HuePreset = {
140
+ id: string;
141
+ label: string;
142
+ hue: number;
143
+ desc: string;
144
+ };
145
+
146
+ export const HUE_PRESET_CHOICES: HuePreset[] = [
147
+ { id: "ocean", label: "Ocean", hue: 210, desc: "신뢰 · 금융/공공" },
148
+ { id: "forest", label: "Forest", hue: 145, desc: "안정 · 헬스케어" },
149
+ { id: "sunset", label: "Sunset", hue: 25, desc: "활력 · 스타트업" },
150
+ { id: "lavender", label: "Lavender", hue: 280, desc: "창의 · 교육" },
151
+ { id: "crimson", label: "Crimson", hue: 355, desc: "강조 · 프로모션" },
152
+ { id: "slate", label: "Slate", hue: 220, desc: "무채색 · 관리 도구" },
153
+ ];
154
+
155
+ export type DensityChoice = {
156
+ id: DensityChoiceId;
157
+ title: string;
158
+ hint: string;
159
+ examples: string;
160
+ };
161
+
162
+ export const DENSITY_CHOICES: DensityChoice[] = [
163
+ {
164
+ id: "compact",
165
+ title: "데이터가 많음",
166
+ hint: "한 화면에 최대한 많은 행·열이 보여야 함. 클릭 대상이 작아도 괜찮음.",
167
+ examples: "관리자 테이블, 엑셀 대체 도구, 데이터 그리드",
168
+ },
169
+ {
170
+ id: "default",
171
+ title: "표준 밀도",
172
+ hint: "일반 웹앱 기본값. 정보와 여백이 균형 있게.",
173
+ examples: "일반 SaaS, 대부분의 페이지",
174
+ },
175
+ {
176
+ id: "comfortable",
177
+ title: "여유롭게",
178
+ hint: "터치 대상이 넉넉해야 하거나, 콘텐츠에 집중하게 하고 싶을 때.",
179
+ examples: "태블릿·모바일 앱, 콘텐츠 리더, 랜딩 페이지",
180
+ },
181
+ ];
182
+
183
+ export type LayoutChoice = {
184
+ id: LayoutId;
185
+ title: string;
186
+ hint: string;
187
+ examples: string;
188
+ sidebar: "left" | "right" | "dual" | "none";
189
+ topbar: boolean;
190
+ };
191
+
192
+ export const LAYOUT_CHOICES: LayoutChoice[] = [
193
+ {
194
+ id: "dual-sidebar",
195
+ title: "옵션이 많은 정교한 편집 도구",
196
+ hint: "왼쪽은 파일·레이어 트리, 오른쪽은 속성 패널. 도구 자체가 UI의 주인공.",
197
+ examples: "Figma, Framer, VS Code, Blender",
198
+ sidebar: "dual",
199
+ topbar: true,
200
+ },
201
+ {
202
+ id: "topbar-sidebar",
203
+ title: "탐색 경로가 중요한 문서·앱",
204
+ hint: "상단에 워크스페이스/브래드크럼, 왼쪽에 섹션 트리. 어디에 있는지가 늘 명확해야 함.",
205
+ examples: "Notion, GitHub, Linear (프로젝트 뷰), Confluence",
206
+ sidebar: "left",
207
+ topbar: true,
208
+ },
209
+ {
210
+ id: "sidebar-only",
211
+ title: "메뉴는 짧고 콘텐츠가 주인공",
212
+ hint: "네비게이션은 왼쪽에 최소한으로. 상단 바 없이 콘텐츠에 몰입.",
213
+ examples: "Linear (이슈 뷰), Vercel Dashboard, Slack",
214
+ sidebar: "left",
215
+ topbar: false,
216
+ },
217
+ ];
218
+
219
+ /**
220
+ * 각 테마가 묶는 축 preset. prompts/themes/<slug>.md 의 Preset 표 그대로.
221
+ * Theme이 안 담는 축(palette/hue)은 중립 기본값.
222
+ * applyPreset에 넘기면 6개 축이 한 번에 스왑됨.
223
+ */
224
+ export const THEME_PRESETS: Record<Exclude<ThemeId, "none">, ThemePreset> = {
225
+ spare: {
226
+ mode: "light",
227
+ palette: "standard",
228
+ hue: 210,
229
+ shape: "crisp",
230
+ density: "default",
231
+ motion: "calm",
232
+ iconFamily: "phosphor" as IconFamily,
233
+ lang: "ko",
234
+ },
235
+ pure: {
236
+ mode: "light",
237
+ palette: "standard",
238
+ hue: 210,
239
+ shape: "soft",
240
+ density: "comfortable",
241
+ motion: "calm",
242
+ iconFamily: "phosphor" as IconFamily,
243
+ lang: "ko",
244
+ },
245
+ expressive: {
246
+ mode: "light",
247
+ palette: "pastel",
248
+ hue: 280,
249
+ shape: "soft",
250
+ density: "default",
251
+ motion: "bouncy",
252
+ iconFamily: "phosphor" as IconFamily,
253
+ lang: "ko",
254
+ },
255
+ };
256
+
257
+ /**
258
+ * 테마가 vibe 축(shape/motion/icon/accent 묶음)을 커버하므로, 테마 선택 시
259
+ * vibe step을 건너뛰고 요약에만 노출. 각 테마의 축 조합과 가장 가까운 vibe 매핑.
260
+ */
261
+ export const THEME_VIBE_FALLBACK: Record<Exclude<ThemeId, "none">, VibeId> = {
262
+ spare: "serious", // crisp · calm · regular — serious와 가장 가까움
263
+ pure: "serious", // soft · calm — motion/weight 공유, shape만 다름
264
+ expressive: "playful", // soft · bouncy · colorful — playful과 거의 일치
265
+ };