@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
package/README.md CHANGED
@@ -1,3 +1,129 @@
1
- # Temporary Holding Version
1
+ # ctds
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ CTDS — 하이브리드 디자인 시스템 설치/온보딩 CLI.
4
+
5
+ ## 요구사항
6
+
7
+ - Node.js 18+
8
+ - Next.js App Router 프로젝트 (`app/` 또는 `src/app/` 디렉토리)
9
+
10
+ ## 설치 (end user)
11
+
12
+ ```bash
13
+ cd my-next-app
14
+ npx ctds init
15
+ ```
16
+
17
+ `init`이 하는 일:
18
+
19
+ 1. 프로젝트 감지 (`package.json` + `app/` 디렉토리 확인)
20
+ 2. 다음 자산 복사:
21
+ - `components/` → `components/ctds/` (또는 `src/components/ctds/`)
22
+ — 재사용 React 컴포넌트 + **`tokens.css`** (폰트 + 토큰 + 축 셀렉터 전체)
23
+ - `prompts/` → `prompts/`
24
+ - `.claude/skills/ctds/` → `.claude/skills/ctds/`
25
+ 3. `app/ctds/page.tsx` 생성 (온보딩 라우트)
26
+ 4. **수동으로 넣을 두 가지**를 콘솔에 출력:
27
+ - `app/globals.css` 최상단에 `@import "../components/ctds/tokens.css";`
28
+ - `app/layout.tsx`에 `<ThemeController>` wrapper
29
+
30
+ 설치 후:
31
+
32
+ ```bash
33
+ npm run dev
34
+ # http://localhost:3000/ctds 접속 → 온보딩 → "적용"
35
+ ```
36
+
37
+ "적용"을 누르면 프로젝트 루트에 두 파일이 생성됩니다:
38
+
39
+ - `ctds.config.json` — 축 값. AI 에이전트(Claude Code 등)와 `ThemeController`가 참조.
40
+ - `DESIGN.md` — 테마·축 산문 요약. 사람·AI 모두 읽음.
41
+
42
+ 두 파일 모두 **커밋**하세요. 다른 개발자나 AI가 참조할 수 있어야 합니다.
43
+
44
+ ## 옵션
45
+
46
+ ```bash
47
+ npx ctds init # 기본
48
+ npx ctds init --dry # 아무것도 쓰지 않고 생성 예정 파일만 출력
49
+ npx ctds init --force # 기존 파일 있어도 덮어쓰기
50
+ ```
51
+
52
+ 디버그:
53
+ ```bash
54
+ CTDS_DEBUG=1 npx ctds init
55
+ ```
56
+
57
+ ## 로컬 개발 (contributor)
58
+
59
+ CTDS 레포에서 CLI를 수정·테스트할 때:
60
+
61
+ ```bash
62
+ cd packages/ctds-cli
63
+ npm link # 전역에 `ctds` 바이너리 심링크
64
+
65
+ cd /tmp/test-next-app # 테스트할 Next.js 플젝
66
+ ctds init --dry # 자산이 CTDS 레포에서 바로 긁힘 (bundle 없이)
67
+ ```
68
+
69
+ `getAssetSources()`가 `assets/components`가 없으면 자동으로 레포 루트
70
+ (`../../web/components`, `../../prompts`, `../../.claude/skills/ctds`)에서 긁어옴.
71
+
72
+ ## Publish
73
+
74
+ ```bash
75
+ cd packages/ctds-cli
76
+ npm run bundle-assets # 레포 자산 → packages/ctds-cli/assets/ 로 복사
77
+ npm publish --access public
78
+ ```
79
+
80
+ ## 알려진 미완성 (end-to-end 2단계 테스트 전)
81
+
82
+ **지금 상태**: CTDS 레포 자체에서 `/ctds` 적용 플로우(1단계)는 작동. 다른 Next.js 플젝에서 `ctds init` 후 매끄럽게 쓰기(2단계)는 아래 간극들 때문에 터질 가능성이 큼. 다음 세션에서 하나씩 해결 예정.
83
+
84
+ ### ~~1. `tokens.css`가 분리돼 있지 않음~~ ✅ 해결 (2026-10-07)
85
+ `web/app/globals.css`에서 토큰·축 셀렉터 블록(1~718줄)을 `web/components/tokens.css`로 떼냄. 폰트 로드까지 포함. CLI 복사 후 `app/globals.css` 상단에 `@import "../components/ctds/tokens.css";` 한 줄이면 CTDS 완전 작동.
86
+
87
+ ### ~~2. `ThemeController`가 `web/app/data.ts` 참조~~ ✅ 해결 (2026-10-07)
88
+ `web/app/data.ts`를 `web/components/_data.ts`로 통째 이동. 축 타입 + preset 데이터 전부. 참조하던 파일(ThemeController, OnboardingAxisPanel, Icon 등) import 경로 전부 `@/components/_data` 또는 `../_data`로 교체. `components/` 복사 시 자동으로 따라감.
89
+
90
+ ### ~~3. 복사된 컴포넌트의 `@/components/*` alias~~ ✅ 해결 (2026-10-07)
91
+ `components/` 내부의 모든 `@/components/<name>` → `../<name>` 상대경로로 재작성. `@/lib/cn`·`@/lib/color`가 쓰이던 유틸은 `components/_cn.ts`·`components/_color.ts`로 이동하고 참조를 상대경로로 교체. CLI 복사 후 사용자 플젝 tsconfig alias와 무관하게 작동.
92
+
93
+ ### ~~4. `OnboardingPage` export 형식~~ ✅ 해결 (2026-10-07)
94
+ 온보딩 UI 전체를 `web/app/onboarding/` → `web/components/Onboarding/`로 이동. OnboardingPage를 named export로 변경. CTDS 레포의 `/onboarding`은 default-export wrapper로 유지. ThemeController·axisOptions도 `web/components/` 아래로 이동. 복사된 사용자 플젝에서 `../../components/ctds/Onboarding`로 named import.
95
+
96
+ ### ~~5. 의존성 호환~~ ✅ 해결 (2026-10-07)
97
+ CLI가 사용자 플젝 `package.json`의 deps를 체크해, 빠진 CTDS 런타임 패키지만 `npm install ...` 명령어로 안내. `src/utils/deps.js`의 `CTDS_RUNTIME_DEPS` 참조. Next 16, React 18.3에서 end-to-end 테스트 통과.
98
+
99
+ ### ~~6. Server action `process.cwd()` 가정~~ ✅ 해결 (2026-10-07)
100
+ `actions.ts`의 `findProjectRoot()`가 cwd부터 상위로 올라가며 `package.json`이 있는 **가장 바깥 디렉토리**를 선택. CTDS 자기 레포 테스트 케이스(web/ vs 루트)와 end-to-end 테스트 플젝 모두 올바른 루트 찾음.
101
+
102
+ ---
103
+
104
+ ## 알려진 자잘한 간극 (end-to-end 테스트에서 발견, 치명적 아님)
105
+
106
+ ### 7. `.visually-hidden` 전역 유틸 ✅ 해결
107
+ SelectableCard가 `className="visually-hidden"`을 참조하는데 기존엔 `globals.css`에만 있어서 복사 안 됨. `web/components/_utils.css` 신규 파일로 분리, `tokens.css`가 `@import "./_utils.css"`. 결과: 네이티브 라디오/체크박스가 올바르게 숨겨짐.
108
+
109
+ ### 8. `web/public/onboarding/*.png` 미복사 ✅ 해결
110
+ 온보딩 테마 호버 미리보기 이미지(pure.png/spare.png/expressive.png) 복사 플랜에 추가. 번들러(`bundle-assets.mjs`)와 repo fallback(`getAssetSources`) 둘 다 `public/onboarding` 포함.
111
+
112
+ ---
113
+
114
+ ## end-to-end 테스트 결과 (2026-10-07)
115
+
116
+ Fresh Next.js 16 플젝에 `node bin/ctds.js init` 실행 → CLI 안내대로 수동 설정 → `/ctds` 접속 → 온보딩 완료 → "적용".
117
+
118
+ - ✓ 자산 복사 (components/ctds/, prompts/, .claude/skills/ctds/, public/onboarding/)
119
+ - ✓ `/ctds/page.tsx` 생성
120
+ - ✓ 수동 설정 안내 출력 (globals.css import, layout.tsx wrapper, npm install deps)
121
+ - ✓ dev 서버 렌더링 (라디오·타이틀·배경 정상)
122
+ - ✓ 적용 → **test 플젝 루트에** `ctds.config.json` + `DESIGN.md` 생성
123
+ - ✓ `findProjectRoot()`가 cwd(`/private/tmp/ctds-e2e`) 그대로 선택
124
+
125
+ ---
126
+
127
+ ## 라이선스
128
+
129
+ MIT
@@ -0,0 +1,177 @@
1
+ /* Root ─────────────────────────────────────────────────
2
+ 3영역 grid. layout이 구조, variant가 표현을 담당.
3
+ app-scroll 패러다임: AppFrame이 뷰포트 높이(100dvh)를 꽉 채우고, 바깥 html/body는
4
+ 스크롤하지 않는다. topbar/sidebar는 각자 grid cell로 자리만 잡으면 되고(sticky·
5
+ 측정·오프셋 전부 불필요), 콘텐츠는 .contentSlot 자체에서 세로 스크롤. */
6
+ .root {
7
+ display: grid;
8
+ height: 100dvh;
9
+ min-height: 0;
10
+ overflow: hidden;
11
+ }
12
+
13
+ /* sidebar-first — 사이드바가 세로 전체, 탑바는 오른쪽 영역 위.
14
+ Linear · Slack · Notion 패턴. */
15
+ .root[data-layout="sidebar-first"] {
16
+ grid-template-columns: auto 1fr;
17
+ grid-template-rows: auto 1fr;
18
+ grid-template-areas:
19
+ "sidebar topbar"
20
+ "sidebar content";
21
+ }
22
+
23
+ .root[data-layout="sidebar-first"][data-has-tabs] {
24
+ grid-template-rows: auto auto 1fr;
25
+ grid-template-areas:
26
+ "sidebar topbar"
27
+ "sidebar tabs"
28
+ "sidebar content";
29
+ }
30
+
31
+ /* topbar-first — 탑바가 가로 전체, 사이드바는 아래 왼쪽.
32
+ Jira · Vercel · Figma 홈 패턴. */
33
+ .root[data-layout="topbar-first"] {
34
+ grid-template-columns: auto 1fr;
35
+ grid-template-rows: auto 1fr;
36
+ grid-template-areas:
37
+ "topbar topbar"
38
+ "sidebar content";
39
+ }
40
+
41
+ .root[data-layout="topbar-first"][data-has-tabs] {
42
+ grid-template-rows: auto auto 1fr;
43
+ grid-template-areas:
44
+ "topbar topbar"
45
+ "tabs tabs"
46
+ "sidebar content";
47
+ }
48
+
49
+ /* 슬롯 생략 분기 ───────────────────────────────────────
50
+ 사이드바만 / 탑바만 / 콘텐츠만 — content가 남는 공간 전체를 먹도록. */
51
+
52
+ /* 탑바만 */
53
+ .root:not([data-has-sidebar]):not([data-has-topbar]) {
54
+ grid-template-columns: 1fr;
55
+ grid-template-rows: 1fr;
56
+ grid-template-areas: "content";
57
+ }
58
+
59
+ .root[data-has-topbar]:not([data-has-sidebar]) {
60
+ grid-template-columns: 1fr;
61
+ grid-template-rows: auto 1fr;
62
+ grid-template-areas:
63
+ "topbar"
64
+ "content";
65
+ }
66
+
67
+ .root[data-has-topbar][data-has-tabs]:not([data-has-sidebar]) {
68
+ grid-template-columns: 1fr;
69
+ grid-template-rows: auto auto 1fr;
70
+ grid-template-areas:
71
+ "topbar"
72
+ "tabs"
73
+ "content";
74
+ }
75
+
76
+ .root[data-has-sidebar]:not([data-has-topbar]) {
77
+ grid-template-columns: auto 1fr;
78
+ grid-template-rows: 1fr;
79
+ grid-template-areas: "sidebar content";
80
+ }
81
+
82
+ .sidebarSlot {
83
+ grid-area: sidebar;
84
+ display: flex;
85
+ min-height: 0;
86
+ /* 사이드바 영역은 자기 cell 높이 안에 자식(Sidebar)을 가둔다. 자식 콘텐츠가
87
+ 길어지면 자식 내부 Body가 스크롤로 해결 — 영역 밖으로 삐져나오지 않도록. */
88
+ overflow: hidden;
89
+ background: var(--bg);
90
+ }
91
+
92
+ .topbarSlot {
93
+ grid-area: topbar;
94
+ display: flex;
95
+ min-width: 0;
96
+ background: var(--bg);
97
+ }
98
+
99
+ .tabsSlot {
100
+ grid-area: tabs;
101
+ display: flex;
102
+ min-width: 0;
103
+ }
104
+
105
+ /* 콘텐츠 cell이 유일한 세로 스크롤 컨테이너. topbar/sidebar는 그대로 자리에 있고
106
+ 이 안쪽만 스크롤 — body-scroll + sticky 조합의 jank(측정 지연, 사이드바 흔들림)
107
+ 가 발생하지 않는다. */
108
+ .contentSlot {
109
+ grid-area: content;
110
+ min-width: 0;
111
+ min-height: 0;
112
+ overflow-y: auto;
113
+ overflow-x: hidden;
114
+ }
115
+
116
+ /* outlined ────────────────────────────────────────────
117
+ 표현은 topbar 아래 hairline 한 줄만. 전체 테두리·radius·그림자 없음.
118
+ 사이드바 좌측도 동일하게 hairline 한 줄만 — 바깥 카드 느낌이 생기지 않도록.
119
+ tabs가 있으면 topbar와 tabs 사이는 선 없이 붙고, tabs 아래에 hairline을 둠. */
120
+ .root[data-variant="outlined"] .topbarSlot {
121
+ border-block-end: var(--hairline) solid var(--border);
122
+ }
123
+
124
+ .root[data-variant="outlined"][data-has-tabs] .topbarSlot {
125
+ border-block-end: none;
126
+ }
127
+
128
+ .root[data-variant="outlined"] .tabsSlot {
129
+ border-block-end: var(--hairline) solid var(--border);
130
+ }
131
+
132
+ .root[data-variant="outlined"] .sidebarSlot {
133
+ border-inline-end: var(--hairline) solid var(--border);
134
+ }
135
+
136
+ /* floating ────────────────────────────────────────────
137
+ sidebar/topbar/content가 각각 카드로 떠 있음. */
138
+ .root[data-variant="floating"] {
139
+ gap: var(--space-3);
140
+ padding: var(--space-3);
141
+ background: var(--bg-subtle);
142
+ }
143
+
144
+ .root[data-variant="floating"] .sidebarSlot,
145
+ .root[data-variant="floating"] .topbarSlot,
146
+ .root[data-variant="floating"] .tabsSlot,
147
+ .root[data-variant="floating"] .contentSlot {
148
+ background: var(--bg);
149
+ border-radius: var(--radius-lg);
150
+ box-shadow: var(--shadow-sm);
151
+ }
152
+
153
+ /* flat ────────────────────────────────────────────────
154
+ 표현 없음. grid만. */
155
+
156
+ /* 좁은 뷰포트 ─────────────────────────────────────────
157
+ 900px 이하: 사이드바 영역 숨김. grid-template을 1열로 override해서
158
+ content가 전폭을 받도록. layout(sidebar-first/topbar-first) 양쪽 모두 커버. */
159
+ @media (max-width: 900px) {
160
+ .root[data-has-sidebar] .sidebarSlot {
161
+ display: none;
162
+ }
163
+ .root[data-has-sidebar][data-layout="sidebar-first"],
164
+ .root[data-has-sidebar][data-layout="topbar-first"] {
165
+ grid-template-columns: 1fr;
166
+ grid-template-areas:
167
+ "topbar"
168
+ "content";
169
+ }
170
+ .root[data-has-sidebar][data-has-tabs][data-layout="sidebar-first"],
171
+ .root[data-has-sidebar][data-has-tabs][data-layout="topbar-first"] {
172
+ grid-template-areas:
173
+ "topbar"
174
+ "tabs"
175
+ "content";
176
+ }
177
+ }
@@ -0,0 +1,93 @@
1
+ "use client";
2
+
3
+ import {
4
+ Children,
5
+ forwardRef,
6
+ isValidElement,
7
+ type HTMLAttributes,
8
+ type ReactNode,
9
+ } from "react";
10
+
11
+ import { cn } from "../_cn";
12
+ import styles from "./AppFrame.module.css";
13
+
14
+ export type AppFrameVariant = "outlined" | "floating" | "flat";
15
+ export type AppFrameLayout = "sidebar-first" | "topbar-first";
16
+
17
+ export interface AppFrameProps extends HTMLAttributes<HTMLDivElement> {
18
+ /** 컨테이너 표현(bg/border/shadow). 기본 outlined. */
19
+ variant?: AppFrameVariant;
20
+ /**
21
+ * 레이아웃 구조.
22
+ * - sidebar-first: 사이드바가 세로 전체, 탑바는 오른쪽 영역 위에만. Linear/Slack.
23
+ * - topbar-first: 탑바가 가로 전체, 사이드바는 아래 왼쪽에만. Jira/Vercel.
24
+ * 기본 sidebar-first.
25
+ */
26
+ layout?: AppFrameLayout;
27
+ }
28
+
29
+ /* Slots ────────────────────────────────────────────────
30
+ grid-area 배정용 얇은 wrapper. */
31
+ function AppFrameSidebar({ children }: { children: ReactNode }) {
32
+ return <div className={styles.sidebarSlot}>{children}</div>;
33
+ }
34
+
35
+ function AppFrameTopbar({ children }: { children: ReactNode }) {
36
+ return <div className={styles.topbarSlot}>{children}</div>;
37
+ }
38
+
39
+ function AppFrameTabs({ children }: { children: ReactNode }) {
40
+ return <div className={styles.tabsSlot}>{children}</div>;
41
+ }
42
+
43
+ function AppFrameContent({ children }: { children: ReactNode }) {
44
+ return <div className={styles.contentSlot}>{children}</div>;
45
+ }
46
+
47
+ /* Root ─────────────────────────────────────────────────
48
+ children을 훑어 어떤 슬롯이 들어왔는지 감지. 비어있는 슬롯이 있으면
49
+ data-has-* attribute로 CSS에게 알려 grid-template을 조정하게 한다. */
50
+ const AppFrameRoot = forwardRef<HTMLDivElement, AppFrameProps>(function AppFrameRoot(
51
+ { variant = "outlined", layout = "sidebar-first", className, children, ...rest },
52
+ ref,
53
+ ) {
54
+ let hasSidebar = false;
55
+ let hasTopbar = false;
56
+ let hasTabs = false;
57
+ Children.forEach(children, (child) => {
58
+ if (!isValidElement(child)) return;
59
+ if (child.type === AppFrameSidebar) hasSidebar = true;
60
+ else if (child.type === AppFrameTopbar) hasTopbar = true;
61
+ else if (child.type === AppFrameTabs) hasTabs = true;
62
+ });
63
+
64
+ return (
65
+ <div
66
+ ref={ref}
67
+ className={cn(styles.root, className)}
68
+ data-variant={variant}
69
+ data-layout={layout}
70
+ data-has-sidebar={hasSidebar || undefined}
71
+ data-has-topbar={hasTopbar || undefined}
72
+ data-has-tabs={hasTabs || undefined}
73
+ {...rest}
74
+ >
75
+ {children}
76
+ </div>
77
+ );
78
+ });
79
+
80
+ type AppFrameComponent = typeof AppFrameRoot & {
81
+ Sidebar: typeof AppFrameSidebar;
82
+ Topbar: typeof AppFrameTopbar;
83
+ Tabs: typeof AppFrameTabs;
84
+ Content: typeof AppFrameContent;
85
+ };
86
+
87
+ const AppFrame = AppFrameRoot as AppFrameComponent;
88
+ AppFrame.Sidebar = AppFrameSidebar;
89
+ AppFrame.Topbar = AppFrameTopbar;
90
+ AppFrame.Tabs = AppFrameTabs;
91
+ AppFrame.Content = AppFrameContent;
92
+
93
+ export { AppFrame };
@@ -0,0 +1,6 @@
1
+ export { AppFrame } from "./AppFrame";
2
+ export type {
3
+ AppFrameProps,
4
+ AppFrameVariant,
5
+ AppFrameLayout,
6
+ } from "./AppFrame";
@@ -0,0 +1,71 @@
1
+ /* AttachmentChip — [FileIcon] filename [X]
2
+ 레퍼런스: ChatInput 안에서 쓰이는 둥근 사각 — radius-md, hairline border. */
3
+
4
+ .root {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ gap: var(--space-2);
8
+ min-width: 0;
9
+ background: var(--bg);
10
+ border: var(--hairline) solid var(--border);
11
+ border-radius: var(--radius-md);
12
+ color: var(--fg);
13
+ transition: var(--transition-colors);
14
+ }
15
+
16
+ /* 패딩만 size 차이. 오른쪽은 X 버튼 자리라 좁게. */
17
+ .sizeSm {
18
+ padding: var(--space-1) var(--space-1) var(--space-1) var(--space-1);
19
+ font-size: var(--text-xs);
20
+ }
21
+ .sizeMd {
22
+ padding: var(--space-1_5) var(--space-1_5) var(--space-1_5) var(--space-1_5);
23
+ font-size: var(--text-sm);
24
+ }
25
+
26
+ /* 파일명 */
27
+ .label {
28
+ flex: 1 1 auto;
29
+ min-width: 0;
30
+ overflow: hidden;
31
+ text-overflow: ellipsis;
32
+ white-space: nowrap;
33
+ color: var(--fg-strong);
34
+ line-height: 1.2;
35
+ padding-right: var(--space-1);
36
+ }
37
+
38
+ /* X 버튼 — size에 따라 width/height가 변하도록 토큰 조합 */
39
+ .removeBtn {
40
+ display: inline-flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ padding: 0;
44
+ border: 0;
45
+ background: transparent;
46
+ color: var(--fg-muted);
47
+ border-radius: var(--radius-sm);
48
+ cursor: pointer;
49
+ transition: var(--transition-colors);
50
+ width: var(--space-5);
51
+ height: var(--space-5);
52
+ flex: 0 0 auto;
53
+ }
54
+ .sizeSm .removeBtn {
55
+ width: var(--space-4);
56
+ height: var(--space-4);
57
+ }
58
+
59
+ .removeBtn:hover {
60
+ background: var(--bg-subtle);
61
+ color: var(--fg);
62
+ }
63
+ .removeBtn:focus-visible {
64
+ outline: none;
65
+ box-shadow: var(--ring);
66
+ }
67
+
68
+ .removeIcon {
69
+ width: 70%;
70
+ height: 70%;
71
+ }
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import type { HTMLAttributes, MouseEvent } from "react";
4
+ import { X as PhX } from "@phosphor-icons/react";
5
+ import { FileIcon, type FileIconSize } from "@/components/FileIcon";
6
+ import styles from "./AttachmentChip.module.css";
7
+
8
+ export type AttachmentChipSize = "sm" | "md";
9
+
10
+ export interface AttachmentChipProps
11
+ extends Omit<HTMLAttributes<HTMLSpanElement>, "children" | "onError"> {
12
+ /** 파일명. 확장자가 FileIcon 색/라벨을 결정. */
13
+ filename: string;
14
+ /** 지정 시 우측에 X 노출, 클릭 시 호출. */
15
+ onRemove?: (e: MouseEvent<HTMLButtonElement>) => void;
16
+ /** sm=padding·icon 작게 / md=기본. */
17
+ size?: AttachmentChipSize;
18
+ }
19
+
20
+ const sizeClass: Record<AttachmentChipSize, string> = {
21
+ sm: styles.sizeSm,
22
+ md: styles.sizeMd,
23
+ };
24
+
25
+ const fileIconSize: Record<AttachmentChipSize, FileIconSize> = {
26
+ sm: "sm",
27
+ md: "md",
28
+ };
29
+
30
+ /** 긴 파일명을 중간 생략으로 자르되 확장자는 유지.
31
+ * "very-long-report-name.pdf" → "very-long-rep…pdf" */
32
+ function middleEllipsis(filename: string, max = 28): string {
33
+ if (filename.length <= max) return filename;
34
+ const dot = filename.lastIndexOf(".");
35
+ if (dot < 0 || dot < filename.length - 6) {
36
+ return filename.slice(0, max - 1) + "…";
37
+ }
38
+ const ext = filename.slice(dot); // ".pdf"
39
+ const head = filename.slice(0, Math.max(1, max - ext.length - 1));
40
+ return head + "…" + ext;
41
+ }
42
+
43
+ export function AttachmentChip({
44
+ filename,
45
+ onRemove,
46
+ size = "md",
47
+ className,
48
+ ...rest
49
+ }: AttachmentChipProps) {
50
+ const cls = [styles.root, sizeClass[size], className]
51
+ .filter(Boolean)
52
+ .join(" ");
53
+
54
+ const label = middleEllipsis(filename);
55
+
56
+ return (
57
+ <span className={cls} {...rest}>
58
+ <FileIcon ext={filename} size={fileIconSize[size]} />
59
+ <span className={styles.label} title={filename}>
60
+ {label}
61
+ </span>
62
+ {onRemove ? (
63
+ <button
64
+ type="button"
65
+ className={styles.removeBtn}
66
+ onClick={onRemove}
67
+ aria-label="첨부 제거"
68
+ >
69
+ <PhX className={styles.removeIcon} weight="regular" />
70
+ </button>
71
+ ) : null}
72
+ </span>
73
+ );
74
+ }
@@ -0,0 +1,2 @@
1
+ export { AttachmentChip } from "./AttachmentChip";
2
+ export type { AttachmentChipProps, AttachmentChipSize } from "./AttachmentChip";
@@ -0,0 +1,107 @@
1
+ /* AttachmentThumb — 정사각 썸네일.
2
+ 비율은 FileIcon과 동일 (radius 0.25, glyph 0.60). 사이즈 리터럴은 라인별 ignore. */
3
+
4
+ .root {
5
+ --at-size: 40px; /* lint-hardcoded: ignore */
6
+
7
+ position: relative;
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ width: var(--at-size);
12
+ height: var(--at-size);
13
+ border-radius: calc(var(--at-size) * 0.25);
14
+ flex: 0 0 auto;
15
+ user-select: none;
16
+ transition: var(--transition-colors);
17
+ /* overflow:hidden X — bg clip과 img radius는 각자 처리.
18
+ Remove 버튼이 바깥으로 overlap되도록. */
19
+ }
20
+
21
+ .sizeSm { --at-size: 32px; /* lint-hardcoded: ignore */ }
22
+ .sizeMd { --at-size: 40px; /* lint-hardcoded: ignore */ }
23
+ .sizeLg { --at-size: 56px; /* lint-hardcoded: ignore */ }
24
+
25
+ /* 이미지 */
26
+ .hasImage {
27
+ background: var(--bg-subtle);
28
+ }
29
+
30
+ /* Image glyph placeholder — src 없을 때. 색은 TSX의 CSS 변수로 주입. */
31
+ .placeholder {
32
+ background: var(--at-ph-bg-light);
33
+ color: var(--at-ph-fg-light);
34
+ }
35
+ :global([data-mode="dark"]) .placeholder {
36
+ background: var(--at-ph-bg-dark);
37
+ color: var(--at-ph-fg-dark);
38
+ }
39
+ .img {
40
+ width: 100%;
41
+ height: 100%;
42
+ object-fit: cover;
43
+ display: block;
44
+ border-radius: inherit;
45
+ }
46
+
47
+ /* image-square glyph — FileIcon의 generic과 동일 size × 0.60 */
48
+ .glyph {
49
+ width: calc(var(--at-size) * 0.60);
50
+ height: calc(var(--at-size) * 0.60);
51
+ }
52
+
53
+ /* Loading pulse — 토큰 조합 */
54
+ .loading {
55
+ background: var(--bg-subtle);
56
+ animation: at-pulse calc(var(--duration-slower) * 5) var(--ease-in-out) infinite;
57
+ }
58
+ @keyframes at-pulse {
59
+ 0%, 100% { opacity: 1; }
60
+ 50% { opacity: 0.55; }
61
+ }
62
+
63
+ /* Remove 버튼 — hover 시만 노출. 코너 overlap. */
64
+ .removeBtn {
65
+ position: absolute;
66
+ top: calc(var(--at-size) * -0.12);
67
+ right: calc(var(--at-size) * -0.12);
68
+ width: calc(var(--at-size) * 0.42);
69
+ height: calc(var(--at-size) * 0.42);
70
+ display: inline-flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ padding: 0;
74
+ border: 0;
75
+ border-radius: var(--radius-full);
76
+ background: var(--bg);
77
+ color: var(--fg);
78
+ box-shadow:
79
+ inset 0 0 0 1px color-mix(in oklch, var(--fg) 15%, transparent),
80
+ var(--shadow-xs);
81
+ cursor: pointer;
82
+ opacity: 0;
83
+ transform: scale(0.9);
84
+ transition:
85
+ var(--transition-colors),
86
+ opacity var(--duration-fast) var(--ease-out),
87
+ transform var(--duration-fast) var(--ease-out);
88
+ z-index: var(--z-raise);
89
+ }
90
+ .root[data-removable]:hover .removeBtn,
91
+ .removeBtn:focus-visible {
92
+ opacity: 1;
93
+ transform: scale(1);
94
+ }
95
+ .removeBtn:hover {
96
+ background: var(--bg-subtle);
97
+ }
98
+ .removeBtn:focus-visible {
99
+ outline: none;
100
+ box-shadow:
101
+ inset 0 0 0 1px color-mix(in oklch, var(--fg) 15%, transparent),
102
+ var(--ring);
103
+ }
104
+ .removeIcon {
105
+ width: 62%;
106
+ height: 62%;
107
+ }