bsmnt 0.0.2 → 0.1.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 (430) hide show
  1. package/.github/workflows/release.yml +2 -0
  2. package/CHANGELOG.md +21 -0
  3. package/CLAUDE.md +42 -23
  4. package/README.md +33 -11
  5. package/biome.json +1 -0
  6. package/bun.lock +1 -1
  7. package/docs/architecture.drawio +266 -0
  8. package/docs/architecture.mermaid +91 -0
  9. package/package.json +1 -1
  10. package/{bin → packages/cli/bin}/index.js +28 -27
  11. package/packages/cli/package.json +16 -0
  12. package/{src → packages/cli/src}/commands/add-integration.js +23 -25
  13. package/{src → packages/cli/src}/commands/create.js +104 -133
  14. package/packages/create-basement-app/integrations/basehub/config.js +21 -0
  15. package/packages/create-basement-app/integrations/sanity/config.js +46 -0
  16. package/{src → packages/create-basement-app/integrations/sanity}/mergers/check-integration-merger.js +1 -1
  17. package/{src → packages/create-basement-app/integrations/sanity}/mergers/layout-merger.js +1 -1
  18. package/{src → packages/create-basement-app/integrations/sanity}/mergers/sitemap-merger.js +1 -1
  19. package/packages/create-basement-app/package.json +10 -0
  20. package/packages/create-basement-app/src/configs/animations.js +28 -0
  21. package/packages/create-basement-app/src/index.js +15 -0
  22. package/packages/create-basement-app/src/mergers/check-integration-merger.js +105 -0
  23. package/{src → packages/create-basement-app/src}/mergers/config.js +1 -7
  24. package/{src → packages/create-basement-app/src}/mergers/index.js +87 -93
  25. package/packages/create-basement-app/src/mergers/layout-merger.js +223 -0
  26. package/packages/create-basement-app/src/mergers/sitemap-merger.js +121 -0
  27. package/packages/create-basement-app/template-hooks/config.js +38 -0
  28. package/packages/create-basement-app/templates/default/.biome/plugins/README.md +21 -0
  29. package/packages/create-basement-app/templates/default/.biome/plugins/no-anchor-element.grit +12 -0
  30. package/packages/create-basement-app/templates/default/.biome/plugins/no-relative-parent-imports.grit +10 -0
  31. package/packages/create-basement-app/templates/default/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  32. package/packages/create-basement-app/templates/default/.cursor/rules/README.md +184 -0
  33. package/packages/create-basement-app/templates/default/.cursor/rules/architecture.mdc +437 -0
  34. package/packages/create-basement-app/templates/default/.cursor/rules/components.mdc +436 -0
  35. package/packages/create-basement-app/templates/default/.cursor/rules/integrations.mdc +447 -0
  36. package/packages/create-basement-app/templates/default/.cursor/rules/main.mdc +278 -0
  37. package/packages/create-basement-app/templates/default/.cursor/rules/styling.mdc +433 -0
  38. package/packages/create-basement-app/templates/default/.editorconfig +40 -0
  39. package/packages/create-basement-app/templates/default/.env.example +81 -0
  40. package/packages/create-basement-app/templates/default/.gitattributes +19 -0
  41. package/packages/create-basement-app/templates/default/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  42. package/packages/create-basement-app/templates/default/.github/workflows/lighthouse-to-slack.yml +136 -0
  43. package/packages/create-basement-app/templates/default/.vscode/extensions.json +20 -0
  44. package/packages/create-basement-app/templates/default/.vscode/settings.json +105 -0
  45. package/packages/create-basement-app/templates/default/README.md +221 -0
  46. package/packages/create-basement-app/templates/default/_gitignore +67 -0
  47. package/packages/create-basement-app/templates/default/app/favicon.ico +0 -0
  48. package/packages/create-basement-app/templates/default/app/layout.tsx +104 -0
  49. package/packages/create-basement-app/templates/default/app/page.tsx +275 -0
  50. package/packages/create-basement-app/templates/default/app/robots.ts +15 -0
  51. package/packages/create-basement-app/templates/default/app/sitemap.ts +16 -0
  52. package/packages/create-basement-app/templates/default/biome.json +250 -0
  53. package/packages/create-basement-app/templates/default/components/basement.svg +1 -0
  54. package/packages/create-basement-app/templates/default/components/layout/footer/index.tsx +27 -0
  55. package/packages/create-basement-app/templates/default/components/layout/header/index.tsx +11 -0
  56. package/packages/create-basement-app/templates/default/components/layout/theme/index.tsx +66 -0
  57. package/packages/create-basement-app/templates/default/components/layout/wrapper/index.tsx +65 -0
  58. package/packages/create-basement-app/templates/default/components/ui/README.md +77 -0
  59. package/packages/create-basement-app/templates/default/components/ui/image/README.md +37 -0
  60. package/packages/create-basement-app/templates/default/components/ui/image/index.tsx +224 -0
  61. package/packages/create-basement-app/templates/default/components/ui/link/index.tsx +146 -0
  62. package/packages/create-basement-app/templates/default/lib/README.md +33 -0
  63. package/packages/create-basement-app/templates/default/lib/hooks/index.ts +12 -0
  64. package/packages/create-basement-app/templates/default/lib/hooks/use-device-detection.ts +81 -0
  65. package/packages/create-basement-app/templates/default/lib/hooks/use-media-breakpoint.ts +15 -0
  66. package/packages/create-basement-app/templates/default/lib/hooks/use-prefetch.ts +74 -0
  67. package/packages/create-basement-app/templates/default/lib/scripts/dev.ts +52 -0
  68. package/packages/create-basement-app/templates/default/lib/scripts/generate-component.ts +322 -0
  69. package/packages/create-basement-app/templates/default/lib/scripts/generate-page.ts +193 -0
  70. package/packages/create-basement-app/templates/default/lib/scripts/generate.ts +79 -0
  71. package/packages/create-basement-app/templates/default/lib/scripts/utils.ts +246 -0
  72. package/packages/create-basement-app/templates/default/lib/store/app.ts +11 -0
  73. package/packages/create-basement-app/templates/default/lib/store/index.ts +11 -0
  74. package/packages/create-basement-app/templates/default/lib/styles/README.md +64 -0
  75. package/packages/create-basement-app/templates/default/lib/styles/cn.ts +7 -0
  76. package/packages/create-basement-app/templates/default/lib/styles/colors.ts +63 -0
  77. package/packages/create-basement-app/templates/default/lib/styles/config.ts +34 -0
  78. package/packages/create-basement-app/templates/default/lib/styles/css/global.css +85 -0
  79. package/packages/create-basement-app/templates/default/lib/styles/css/index.css +6 -0
  80. package/packages/create-basement-app/templates/default/lib/styles/css/reset.css +166 -0
  81. package/packages/create-basement-app/templates/default/lib/styles/css/root.css +68 -0
  82. package/packages/create-basement-app/templates/default/lib/styles/css/tailwind.css +132 -0
  83. package/packages/create-basement-app/templates/default/lib/styles/easings.ts +21 -0
  84. package/packages/create-basement-app/templates/default/lib/styles/fonts.ts +28 -0
  85. package/packages/create-basement-app/templates/default/lib/styles/index.ts +12 -0
  86. package/packages/create-basement-app/templates/default/lib/styles/layout.mjs +27 -0
  87. package/packages/create-basement-app/templates/default/lib/styles/scripts/README.md +29 -0
  88. package/packages/create-basement-app/templates/default/lib/styles/scripts/generate-root.ts +57 -0
  89. package/packages/create-basement-app/templates/default/lib/styles/scripts/generate-tailwind.ts +162 -0
  90. package/packages/create-basement-app/templates/default/lib/styles/scripts/postcss-functions.mjs +168 -0
  91. package/packages/create-basement-app/templates/default/lib/styles/scripts/setup-styles.ts +24 -0
  92. package/packages/create-basement-app/templates/default/lib/styles/scripts/utils.ts +20 -0
  93. package/packages/create-basement-app/templates/default/lib/styles/typography.ts +36 -0
  94. package/packages/create-basement-app/templates/default/lib/utils/README.md +40 -0
  95. package/packages/create-basement-app/templates/default/lib/utils/css.d.ts +21 -0
  96. package/packages/create-basement-app/templates/default/lib/utils/easings.ts +240 -0
  97. package/packages/create-basement-app/templates/default/lib/utils/fetch.ts +84 -0
  98. package/packages/create-basement-app/templates/default/lib/utils/math.test.ts +221 -0
  99. package/packages/create-basement-app/templates/default/lib/utils/math.ts +236 -0
  100. package/packages/create-basement-app/templates/default/lib/utils/metadata.ts +126 -0
  101. package/packages/create-basement-app/templates/default/lib/utils/strings.test.ts +166 -0
  102. package/packages/create-basement-app/templates/default/lib/utils/strings.ts +246 -0
  103. package/packages/create-basement-app/templates/default/lib/utils/types.d.ts +15 -0
  104. package/packages/create-basement-app/templates/default/lib/utils/viewport.test.ts +256 -0
  105. package/packages/create-basement-app/templates/default/lib/utils/viewport.ts +193 -0
  106. package/packages/create-basement-app/templates/default/next.config.ts +142 -0
  107. package/packages/create-basement-app/templates/default/package.json +62 -0
  108. package/packages/create-basement-app/templates/default/postcss.config.mjs +42 -0
  109. package/packages/create-basement-app/templates/default/public/fonts/geist/Geist-Mono.woff2 +0 -0
  110. package/packages/create-basement-app/templates/default/tsconfig.json +43 -0
  111. package/packages/create-basement-app/templates/experiment/.biome/plugins/README.md +21 -0
  112. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-anchor-element.grit +12 -0
  113. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-relative-parent-imports.grit +10 -0
  114. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  115. package/packages/create-basement-app/templates/experiment/.cursor/rules/README.md +184 -0
  116. package/packages/create-basement-app/templates/experiment/.cursor/rules/architecture.mdc +437 -0
  117. package/packages/create-basement-app/templates/experiment/.cursor/rules/components.mdc +436 -0
  118. package/packages/create-basement-app/templates/experiment/.cursor/rules/integrations.mdc +447 -0
  119. package/packages/create-basement-app/templates/experiment/.cursor/rules/main.mdc +278 -0
  120. package/packages/create-basement-app/templates/experiment/.cursor/rules/styling.mdc +433 -0
  121. package/packages/create-basement-app/templates/experiment/.editorconfig +40 -0
  122. package/packages/create-basement-app/templates/experiment/.env.example +81 -0
  123. package/packages/create-basement-app/templates/experiment/.gitattributes +19 -0
  124. package/packages/create-basement-app/templates/experiment/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  125. package/packages/create-basement-app/templates/experiment/.github/workflows/lighthouse-to-slack.yml +136 -0
  126. package/packages/create-basement-app/templates/experiment/.vscode/extensions.json +20 -0
  127. package/packages/create-basement-app/templates/experiment/.vscode/settings.json +105 -0
  128. package/packages/create-basement-app/templates/experiment/README.md +221 -0
  129. package/packages/create-basement-app/templates/experiment/_gitignore +67 -0
  130. package/packages/create-basement-app/templates/experiment/app/favicon.ico +0 -0
  131. package/packages/create-basement-app/templates/experiment/app/layout.tsx +104 -0
  132. package/packages/create-basement-app/templates/experiment/app/page.tsx +275 -0
  133. package/packages/create-basement-app/templates/experiment/app/robots.ts +15 -0
  134. package/packages/create-basement-app/templates/experiment/app/sitemap.ts +16 -0
  135. package/packages/create-basement-app/templates/experiment/biome.json +250 -0
  136. package/packages/create-basement-app/templates/experiment/components/basement.svg +1 -0
  137. package/packages/create-basement-app/templates/experiment/components/layout/footer/index.tsx +27 -0
  138. package/packages/create-basement-app/templates/experiment/components/layout/header/index.tsx +58 -0
  139. package/packages/create-basement-app/templates/experiment/components/layout/navigation-menu.tsx +127 -0
  140. package/packages/create-basement-app/templates/experiment/components/layout/theme/index.tsx +66 -0
  141. package/packages/create-basement-app/templates/experiment/components/layout/wrapper/index.tsx +65 -0
  142. package/packages/create-basement-app/templates/experiment/components/ui/README.md +77 -0
  143. package/packages/create-basement-app/templates/experiment/components/ui/image/README.md +37 -0
  144. package/packages/create-basement-app/templates/experiment/components/ui/image/index.tsx +224 -0
  145. package/packages/create-basement-app/templates/experiment/components/ui/link/index.tsx +146 -0
  146. package/packages/create-basement-app/templates/experiment/lib/README.md +33 -0
  147. package/packages/create-basement-app/templates/experiment/lib/constants.ts +12 -0
  148. package/packages/create-basement-app/templates/experiment/lib/hooks/index.ts +12 -0
  149. package/packages/create-basement-app/templates/experiment/lib/hooks/use-device-detection.ts +81 -0
  150. package/packages/create-basement-app/templates/experiment/lib/hooks/use-media-breakpoint.ts +15 -0
  151. package/packages/create-basement-app/templates/experiment/lib/hooks/use-prefetch.ts +74 -0
  152. package/packages/create-basement-app/templates/experiment/lib/integrations/.gitkeep +0 -0
  153. package/packages/create-basement-app/templates/experiment/lib/scripts/dev.ts +52 -0
  154. package/packages/create-basement-app/templates/experiment/lib/scripts/generate-component.ts +322 -0
  155. package/packages/create-basement-app/templates/experiment/lib/scripts/generate-page.ts +193 -0
  156. package/packages/create-basement-app/templates/experiment/lib/scripts/generate.ts +79 -0
  157. package/packages/create-basement-app/templates/experiment/lib/scripts/utils.ts +246 -0
  158. package/packages/create-basement-app/templates/experiment/lib/store/app.ts +11 -0
  159. package/packages/create-basement-app/templates/experiment/lib/store/index.ts +11 -0
  160. package/packages/create-basement-app/templates/experiment/lib/styles/README.md +64 -0
  161. package/packages/create-basement-app/templates/experiment/lib/styles/cn.ts +7 -0
  162. package/packages/create-basement-app/templates/experiment/lib/styles/colors.ts +63 -0
  163. package/packages/create-basement-app/templates/experiment/lib/styles/config.ts +34 -0
  164. package/packages/create-basement-app/templates/experiment/lib/styles/css/global.css +85 -0
  165. package/packages/create-basement-app/templates/experiment/lib/styles/css/index.css +6 -0
  166. package/packages/create-basement-app/templates/experiment/lib/styles/css/reset.css +166 -0
  167. package/packages/create-basement-app/templates/experiment/lib/styles/css/root.css +68 -0
  168. package/packages/create-basement-app/templates/experiment/lib/styles/css/tailwind.css +132 -0
  169. package/packages/create-basement-app/templates/experiment/lib/styles/easings.ts +21 -0
  170. package/packages/create-basement-app/templates/experiment/lib/styles/fonts.ts +28 -0
  171. package/packages/create-basement-app/templates/experiment/lib/styles/index.ts +12 -0
  172. package/packages/create-basement-app/templates/experiment/lib/styles/layout.mjs +27 -0
  173. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/README.md +29 -0
  174. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/generate-root.ts +57 -0
  175. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/generate-tailwind.ts +162 -0
  176. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/postcss-functions.mjs +168 -0
  177. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/setup-styles.ts +24 -0
  178. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/utils.ts +20 -0
  179. package/packages/create-basement-app/templates/experiment/lib/styles/typography.ts +36 -0
  180. package/packages/create-basement-app/templates/experiment/lib/utils/README.md +40 -0
  181. package/packages/create-basement-app/templates/experiment/lib/utils/css.d.ts +21 -0
  182. package/packages/create-basement-app/templates/experiment/lib/utils/easings.ts +240 -0
  183. package/packages/create-basement-app/templates/experiment/lib/utils/fetch.ts +84 -0
  184. package/packages/create-basement-app/templates/experiment/lib/utils/math.test.ts +221 -0
  185. package/packages/create-basement-app/templates/experiment/lib/utils/math.ts +236 -0
  186. package/packages/create-basement-app/templates/experiment/lib/utils/metadata.ts +126 -0
  187. package/packages/create-basement-app/templates/experiment/lib/utils/strings.test.ts +166 -0
  188. package/packages/create-basement-app/templates/experiment/lib/utils/strings.ts +246 -0
  189. package/packages/create-basement-app/templates/experiment/lib/utils/types.d.ts +15 -0
  190. package/packages/create-basement-app/templates/experiment/lib/utils/viewport.test.ts +256 -0
  191. package/packages/create-basement-app/templates/experiment/lib/utils/viewport.ts +193 -0
  192. package/packages/create-basement-app/templates/experiment/next.config.ts +142 -0
  193. package/packages/create-basement-app/templates/experiment/package.json +69 -0
  194. package/packages/create-basement-app/templates/experiment/postcss.config.mjs +42 -0
  195. package/packages/create-basement-app/templates/experiment/public/fonts/geist/Geist-Mono.woff2 +0 -0
  196. package/packages/create-basement-app/templates/experiment/tsconfig.json +43 -0
  197. package/packages/create-basement-app/templates/webgl/.biome/plugins/README.md +21 -0
  198. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-anchor-element.grit +12 -0
  199. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-relative-parent-imports.grit +10 -0
  200. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  201. package/packages/create-basement-app/templates/webgl/.cursor/rules/README.md +184 -0
  202. package/packages/create-basement-app/templates/webgl/.cursor/rules/architecture.mdc +437 -0
  203. package/packages/create-basement-app/templates/webgl/.cursor/rules/components.mdc +436 -0
  204. package/packages/create-basement-app/templates/webgl/.cursor/rules/integrations.mdc +447 -0
  205. package/packages/create-basement-app/templates/webgl/.cursor/rules/main.mdc +278 -0
  206. package/packages/create-basement-app/templates/webgl/.cursor/rules/styling.mdc +433 -0
  207. package/packages/create-basement-app/templates/webgl/.editorconfig +40 -0
  208. package/packages/create-basement-app/templates/webgl/.env.example +81 -0
  209. package/packages/create-basement-app/templates/webgl/.gitattributes +19 -0
  210. package/packages/create-basement-app/templates/webgl/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  211. package/packages/create-basement-app/templates/webgl/.github/workflows/lighthouse-to-slack.yml +136 -0
  212. package/packages/create-basement-app/templates/webgl/.vscode/extensions.json +20 -0
  213. package/packages/create-basement-app/templates/webgl/.vscode/settings.json +105 -0
  214. package/packages/create-basement-app/templates/webgl/README.md +221 -0
  215. package/packages/create-basement-app/templates/webgl/_gitignore +67 -0
  216. package/packages/create-basement-app/templates/webgl/app/favicon.ico +0 -0
  217. package/packages/create-basement-app/templates/webgl/app/layout.tsx +104 -0
  218. package/packages/create-basement-app/templates/webgl/app/page.tsx +10 -0
  219. package/packages/create-basement-app/templates/webgl/app/robots.ts +15 -0
  220. package/packages/create-basement-app/templates/webgl/app/sitemap.ts +16 -0
  221. package/packages/create-basement-app/templates/webgl/biome.json +250 -0
  222. package/packages/create-basement-app/templates/webgl/components/basement.svg +1 -0
  223. package/packages/create-basement-app/templates/webgl/components/layout/footer/index.tsx +27 -0
  224. package/packages/create-basement-app/templates/webgl/components/layout/header/index.tsx +11 -0
  225. package/packages/create-basement-app/templates/webgl/components/layout/theme/index.tsx +66 -0
  226. package/packages/create-basement-app/templates/webgl/components/layout/wrapper/index.tsx +65 -0
  227. package/packages/create-basement-app/templates/webgl/components/ui/README.md +77 -0
  228. package/packages/create-basement-app/templates/webgl/components/ui/image/README.md +37 -0
  229. package/packages/create-basement-app/templates/webgl/components/ui/image/index.tsx +224 -0
  230. package/packages/create-basement-app/templates/webgl/components/ui/link/index.tsx +146 -0
  231. package/packages/create-basement-app/templates/webgl/components/webgl/canvas/dynamic.tsx +34 -0
  232. package/packages/create-basement-app/templates/webgl/components/webgl/canvas/index.tsx +43 -0
  233. package/packages/create-basement-app/templates/webgl/components/webgl/components/scene/index.tsx +21 -0
  234. package/packages/create-basement-app/templates/webgl/lib/README.md +33 -0
  235. package/packages/create-basement-app/templates/webgl/lib/hooks/index.ts +12 -0
  236. package/packages/create-basement-app/templates/webgl/lib/hooks/use-device-detection.ts +81 -0
  237. package/packages/create-basement-app/templates/webgl/lib/hooks/use-media-breakpoint.ts +15 -0
  238. package/packages/create-basement-app/templates/webgl/lib/hooks/use-prefetch.ts +74 -0
  239. package/packages/create-basement-app/templates/webgl/lib/integrations/.gitkeep +0 -0
  240. package/packages/create-basement-app/templates/webgl/lib/scripts/dev.ts +52 -0
  241. package/packages/create-basement-app/templates/webgl/lib/scripts/generate-component.ts +322 -0
  242. package/packages/create-basement-app/templates/webgl/lib/scripts/generate-page.ts +193 -0
  243. package/packages/create-basement-app/templates/webgl/lib/scripts/generate.ts +79 -0
  244. package/packages/create-basement-app/templates/webgl/lib/scripts/utils.ts +246 -0
  245. package/packages/create-basement-app/templates/webgl/lib/store/app.ts +11 -0
  246. package/packages/create-basement-app/templates/webgl/lib/store/index.ts +11 -0
  247. package/packages/create-basement-app/templates/webgl/lib/styles/README.md +64 -0
  248. package/packages/create-basement-app/templates/webgl/lib/styles/cn.ts +7 -0
  249. package/packages/create-basement-app/templates/webgl/lib/styles/colors.ts +63 -0
  250. package/packages/create-basement-app/templates/webgl/lib/styles/config.ts +34 -0
  251. package/packages/create-basement-app/templates/webgl/lib/styles/css/global.css +85 -0
  252. package/packages/create-basement-app/templates/webgl/lib/styles/css/index.css +6 -0
  253. package/packages/create-basement-app/templates/webgl/lib/styles/css/reset.css +166 -0
  254. package/packages/create-basement-app/templates/webgl/lib/styles/css/root.css +68 -0
  255. package/packages/create-basement-app/templates/webgl/lib/styles/css/tailwind.css +132 -0
  256. package/packages/create-basement-app/templates/webgl/lib/styles/easings.ts +21 -0
  257. package/packages/create-basement-app/templates/webgl/lib/styles/fonts.ts +28 -0
  258. package/packages/create-basement-app/templates/webgl/lib/styles/index.ts +12 -0
  259. package/packages/create-basement-app/templates/webgl/lib/styles/layout.mjs +27 -0
  260. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/README.md +29 -0
  261. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/generate-root.ts +57 -0
  262. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/generate-tailwind.ts +162 -0
  263. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/postcss-functions.mjs +168 -0
  264. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/setup-styles.ts +24 -0
  265. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/utils.ts +20 -0
  266. package/packages/create-basement-app/templates/webgl/lib/styles/typography.ts +36 -0
  267. package/packages/create-basement-app/templates/webgl/lib/utils/README.md +40 -0
  268. package/packages/create-basement-app/templates/webgl/lib/utils/css.d.ts +21 -0
  269. package/packages/create-basement-app/templates/webgl/lib/utils/easings.ts +240 -0
  270. package/packages/create-basement-app/templates/webgl/lib/utils/fetch.ts +84 -0
  271. package/packages/create-basement-app/templates/webgl/lib/utils/math.test.ts +221 -0
  272. package/packages/create-basement-app/templates/webgl/lib/utils/math.ts +236 -0
  273. package/packages/create-basement-app/templates/webgl/lib/utils/metadata.ts +126 -0
  274. package/packages/create-basement-app/templates/webgl/lib/utils/strings.test.ts +166 -0
  275. package/packages/create-basement-app/templates/webgl/lib/utils/strings.ts +246 -0
  276. package/packages/create-basement-app/templates/webgl/lib/utils/types.d.ts +15 -0
  277. package/packages/create-basement-app/templates/webgl/lib/utils/viewport.test.ts +256 -0
  278. package/packages/create-basement-app/templates/webgl/lib/utils/viewport.ts +193 -0
  279. package/packages/create-basement-app/templates/webgl/next.config.ts +142 -0
  280. package/packages/create-basement-app/templates/webgl/package.json +68 -0
  281. package/packages/create-basement-app/templates/webgl/postcss.config.mjs +42 -0
  282. package/packages/create-basement-app/templates/webgl/public/fonts/geist/Geist-Mono.woff2 +0 -0
  283. package/packages/create-basement-app/templates/webgl/tsconfig.json +43 -0
  284. package/packages/create-basement-app/templates/webgpu/.biome/plugins/README.md +21 -0
  285. package/packages/create-basement-app/templates/webgpu/.biome/plugins/no-anchor-element.grit +12 -0
  286. package/packages/create-basement-app/templates/webgpu/.biome/plugins/no-relative-parent-imports.grit +10 -0
  287. package/packages/create-basement-app/templates/webgpu/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  288. package/packages/create-basement-app/templates/webgpu/.cursor/rules/README.md +184 -0
  289. package/packages/create-basement-app/templates/webgpu/.cursor/rules/architecture.mdc +437 -0
  290. package/packages/create-basement-app/templates/webgpu/.cursor/rules/components.mdc +436 -0
  291. package/packages/create-basement-app/templates/webgpu/.cursor/rules/integrations.mdc +447 -0
  292. package/packages/create-basement-app/templates/webgpu/.cursor/rules/main.mdc +278 -0
  293. package/packages/create-basement-app/templates/webgpu/.cursor/rules/styling.mdc +433 -0
  294. package/packages/create-basement-app/templates/webgpu/.editorconfig +40 -0
  295. package/packages/create-basement-app/templates/webgpu/.env.example +81 -0
  296. package/packages/create-basement-app/templates/webgpu/.gitattributes +19 -0
  297. package/packages/create-basement-app/templates/webgpu/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  298. package/packages/create-basement-app/templates/webgpu/.github/workflows/lighthouse-to-slack.yml +136 -0
  299. package/packages/create-basement-app/templates/webgpu/.vscode/extensions.json +20 -0
  300. package/packages/create-basement-app/templates/webgpu/.vscode/settings.json +105 -0
  301. package/packages/create-basement-app/templates/webgpu/README.md +221 -0
  302. package/packages/create-basement-app/templates/webgpu/_gitignore +67 -0
  303. package/packages/create-basement-app/templates/webgpu/app/favicon.ico +0 -0
  304. package/packages/create-basement-app/templates/webgpu/app/layout.tsx +104 -0
  305. package/packages/create-basement-app/templates/webgpu/app/page.tsx +275 -0
  306. package/packages/create-basement-app/templates/webgpu/app/robots.ts +15 -0
  307. package/packages/create-basement-app/templates/webgpu/app/sitemap.ts +16 -0
  308. package/packages/create-basement-app/templates/webgpu/biome.json +250 -0
  309. package/packages/create-basement-app/templates/webgpu/components/basement.svg +1 -0
  310. package/packages/create-basement-app/templates/webgpu/components/layout/footer/index.tsx +27 -0
  311. package/packages/create-basement-app/templates/webgpu/components/layout/header/index.tsx +11 -0
  312. package/packages/create-basement-app/templates/webgpu/components/layout/theme/index.tsx +66 -0
  313. package/packages/create-basement-app/templates/webgpu/components/layout/wrapper/index.tsx +65 -0
  314. package/packages/create-basement-app/templates/webgpu/components/ui/README.md +77 -0
  315. package/packages/create-basement-app/templates/webgpu/components/ui/image/README.md +37 -0
  316. package/packages/create-basement-app/templates/webgpu/components/ui/image/index.tsx +224 -0
  317. package/packages/create-basement-app/templates/webgpu/components/ui/link/index.tsx +146 -0
  318. package/packages/create-basement-app/templates/webgpu/lib/README.md +33 -0
  319. package/packages/create-basement-app/templates/webgpu/lib/hooks/index.ts +12 -0
  320. package/packages/create-basement-app/templates/webgpu/lib/hooks/use-device-detection.ts +81 -0
  321. package/packages/create-basement-app/templates/webgpu/lib/hooks/use-media-breakpoint.ts +15 -0
  322. package/packages/create-basement-app/templates/webgpu/lib/hooks/use-prefetch.ts +74 -0
  323. package/packages/create-basement-app/templates/webgpu/lib/integrations/.gitkeep +0 -0
  324. package/packages/create-basement-app/templates/webgpu/lib/scripts/dev.ts +52 -0
  325. package/packages/create-basement-app/templates/webgpu/lib/scripts/generate-component.ts +322 -0
  326. package/packages/create-basement-app/templates/webgpu/lib/scripts/generate-page.ts +193 -0
  327. package/packages/create-basement-app/templates/webgpu/lib/scripts/generate.ts +79 -0
  328. package/packages/create-basement-app/templates/webgpu/lib/scripts/utils.ts +246 -0
  329. package/packages/create-basement-app/templates/webgpu/lib/store/app.ts +11 -0
  330. package/packages/create-basement-app/templates/webgpu/lib/store/index.ts +11 -0
  331. package/packages/create-basement-app/templates/webgpu/lib/styles/README.md +64 -0
  332. package/packages/create-basement-app/templates/webgpu/lib/styles/cn.ts +7 -0
  333. package/packages/create-basement-app/templates/webgpu/lib/styles/colors.ts +63 -0
  334. package/packages/create-basement-app/templates/webgpu/lib/styles/config.ts +34 -0
  335. package/packages/create-basement-app/templates/webgpu/lib/styles/css/global.css +85 -0
  336. package/packages/create-basement-app/templates/webgpu/lib/styles/css/index.css +6 -0
  337. package/packages/create-basement-app/templates/webgpu/lib/styles/css/reset.css +166 -0
  338. package/packages/create-basement-app/templates/webgpu/lib/styles/css/root.css +68 -0
  339. package/packages/create-basement-app/templates/webgpu/lib/styles/css/tailwind.css +132 -0
  340. package/packages/create-basement-app/templates/webgpu/lib/styles/easings.ts +21 -0
  341. package/packages/create-basement-app/templates/webgpu/lib/styles/fonts.ts +28 -0
  342. package/packages/create-basement-app/templates/webgpu/lib/styles/index.ts +12 -0
  343. package/packages/create-basement-app/templates/webgpu/lib/styles/layout.mjs +27 -0
  344. package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/README.md +29 -0
  345. package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/generate-root.ts +57 -0
  346. package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/generate-tailwind.ts +162 -0
  347. package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/postcss-functions.mjs +168 -0
  348. package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/setup-styles.ts +24 -0
  349. package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/utils.ts +20 -0
  350. package/packages/create-basement-app/templates/webgpu/lib/styles/typography.ts +36 -0
  351. package/packages/create-basement-app/templates/webgpu/lib/utils/README.md +40 -0
  352. package/packages/create-basement-app/templates/webgpu/lib/utils/css.d.ts +21 -0
  353. package/packages/create-basement-app/templates/webgpu/lib/utils/easings.ts +240 -0
  354. package/packages/create-basement-app/templates/webgpu/lib/utils/fetch.ts +84 -0
  355. package/packages/create-basement-app/templates/webgpu/lib/utils/math.test.ts +221 -0
  356. package/packages/create-basement-app/templates/webgpu/lib/utils/math.ts +236 -0
  357. package/packages/create-basement-app/templates/webgpu/lib/utils/metadata.ts +126 -0
  358. package/packages/create-basement-app/templates/webgpu/lib/utils/strings.test.ts +166 -0
  359. package/packages/create-basement-app/templates/webgpu/lib/utils/strings.ts +246 -0
  360. package/packages/create-basement-app/templates/webgpu/lib/utils/types.d.ts +15 -0
  361. package/packages/create-basement-app/templates/webgpu/lib/utils/viewport.test.ts +256 -0
  362. package/packages/create-basement-app/templates/webgpu/lib/utils/viewport.ts +193 -0
  363. package/packages/create-basement-app/templates/webgpu/next.config.ts +142 -0
  364. package/packages/create-basement-app/templates/webgpu/package.json +69 -0
  365. package/packages/create-basement-app/templates/webgpu/postcss.config.mjs +42 -0
  366. package/packages/create-basement-app/templates/webgpu/public/fonts/geist/Geist-Mono.woff2 +0 -0
  367. package/packages/create-basement-app/templates/webgpu/tsconfig.json +43 -0
  368. package/tasks/.last-branch +1 -0
  369. package/tasks/CLAUDE.md +104 -0
  370. package/tasks/archive/2026-02-09-next-starter-dynamic-layers/prd.json +153 -0
  371. package/tasks/archive/2026-02-09-next-starter-dynamic-layers/progress.txt +115 -0
  372. package/tasks/prd-project-restructure.md +375 -0
  373. package/tasks/prd.json +227 -91
  374. package/tasks/progress.txt +281 -87
  375. package/tasks/ralph.sh +113 -0
  376. package/layers/experiment/components/layout/header/index.tsx +0 -58
  377. package/layers/experiment/components/layout/navigation-menu.tsx +0 -127
  378. package/layers/experiment/lib/constants.ts +0 -12
  379. package/layers/webgl/app/page.tsx +0 -10
  380. package/layers/webgl/components/webgl/canvas/dynamic.tsx +0 -34
  381. package/layers/webgl/components/webgl/canvas/index.tsx +0 -43
  382. package/layers/webgl/components/webgl/components/scene/index.tsx +0 -21
  383. package/src/mergers/next-config-merger.js +0 -63
  384. /package/{src → packages/cli/src}/commands/setup-sanity.js +0 -0
  385. /package/{src → packages/cli/src}/commands/worktree.js +0 -0
  386. /package/{integrations/basehub → packages/create-basement-app/integrations/basehub/files}/README.md +0 -0
  387. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/draft-mode/disable/route.ts +0 -0
  388. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/draft-mode/enable/route.ts +0 -0
  389. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/revalidate/route.ts +0 -0
  390. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/layout.tsx +0 -0
  391. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/sitemap.ts +0 -0
  392. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/studio/[[...tool]]/page.tsx +0 -0
  393. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/studio/layout.tsx +0 -0
  394. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/components/ui/sanity-image/index.tsx +0 -0
  395. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/README.md +0 -0
  396. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/check-integration.ts +0 -0
  397. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/README.md +0 -0
  398. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/client.ts +0 -0
  399. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/components/disable-draft-mode.tsx +0 -0
  400. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/components/rich-text.tsx +0 -0
  401. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/env.ts +0 -0
  402. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/live/index.tsx +0 -0
  403. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/queries.ts +0 -0
  404. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.cli.ts +0 -0
  405. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.config.ts +0 -0
  406. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.types.ts +0 -0
  407. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schema.json +0 -0
  408. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/article.ts +0 -0
  409. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/example.ts +0 -0
  410. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/index.ts +0 -0
  411. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/link.ts +0 -0
  412. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/metadata.ts +0 -0
  413. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/navigation.ts +0 -0
  414. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/page.ts +0 -0
  415. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/richText.ts +0 -0
  416. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/structure.ts +0 -0
  417. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/utils/image.ts +0 -0
  418. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/utils/link.ts +0 -0
  419. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/scripts/copy-sanity-mcp.ts +0 -0
  420. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/scripts/generate-page.ts +0 -0
  421. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/utils/metadata.ts +0 -0
  422. /package/{plugins → packages/create-basement-app/plugins}/README.md +0 -0
  423. /package/{plugins → packages/create-basement-app/plugins}/no-anchor-element.grit +0 -0
  424. /package/{plugins → packages/create-basement-app/plugins}/no-relative-parent-imports.grit +0 -0
  425. /package/{plugins → packages/create-basement-app/plugins}/no-unnecessary-forwardref.grit +0 -0
  426. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-battery.ts +0 -0
  427. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-device-perf.ts +0 -0
  428. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-intersection-observer.ts +0 -0
  429. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-media.ts +0 -0
  430. /package/{layers/webgpu → packages/create-basement-app/templates/default/lib/integrations}/.gitkeep +0 -0
@@ -0,0 +1,246 @@
1
+ /**
2
+ * String & Object Utilities
3
+ *
4
+ * Helper functions for string manipulation, object operations, and refs.
5
+ */
6
+
7
+ import type { Ref } from "react"
8
+
9
+ // =============================================================================
10
+ // STRING UTILITIES
11
+ // =============================================================================
12
+
13
+ /**
14
+ * Converts text to URL-friendly slug format.
15
+ *
16
+ * @param text - Text to convert (must have toString method)
17
+ * @returns URL-safe slug string
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * slugify('Hello World!') // 'hello-world'
22
+ * slugify('Café & Restaurant') // 'cafe-restaurant'
23
+ * ```
24
+ */
25
+ export function slugify(text: { toString: () => string }) {
26
+ return text
27
+ .toString()
28
+ .normalize("NFKD")
29
+ .toLowerCase()
30
+ .trim()
31
+ .replace(/\s+/g, "-")
32
+ .replace(/[^\w-]+/g, "")
33
+ .replace(/--+/g, "-")
34
+ }
35
+
36
+ /**
37
+ * Converts first character to lowercase (camelCase format).
38
+ *
39
+ * @param inputString - String to convert
40
+ * @returns String with lowercase first character
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * convertToCamelCase('HelloWorld') // 'helloWorld'
45
+ * ```
46
+ */
47
+ export function convertToCamelCase(inputString: string) {
48
+ return inputString.charAt(0).toLowerCase() + inputString.slice(1)
49
+ }
50
+
51
+ /**
52
+ * Capitalizes the first letter of a string.
53
+ *
54
+ * @param inputString - String to capitalize
55
+ * @returns String with capitalized first letter
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * capitalizeFirstLetter('hello') // 'Hello'
60
+ * capitalizeFirstLetter('world') // 'World'
61
+ * ```
62
+ */
63
+ export function capitalizeFirstLetter(inputString: string) {
64
+ return inputString.charAt(0).toUpperCase() + inputString.slice(1)
65
+ }
66
+
67
+ /**
68
+ * Formats a number as a two-digit string (padding with zero).
69
+ *
70
+ * @param number - Number to format
71
+ * @returns Two-digit string representation
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * twoDigits(5) // '05'
76
+ * twoDigits(23) // '23'
77
+ * ```
78
+ */
79
+ export function twoDigits(number: number) {
80
+ return number > 9 ? `${number}` : `0${number}`
81
+ }
82
+
83
+ /**
84
+ * Adds commas as thousands separators to numbers.
85
+ *
86
+ * @param x - Number to format (must have toString method)
87
+ * @returns String with comma separators
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * numberWithCommas(1234) // '1,234'
92
+ * numberWithCommas(1234567) // '1,234,567'
93
+ * ```
94
+ */
95
+ export function numberWithCommas(x: { toString: () => string }) {
96
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
97
+ }
98
+
99
+ // =============================================================================
100
+ // ARRAY & OBJECT UTILITIES
101
+ // =============================================================================
102
+
103
+ export function checkIsArray<T>(value: T): T extends unknown[] ? T[0] : T {
104
+ return (Array.isArray(value) ? value[0] : value) as T extends unknown[]
105
+ ? T[0]
106
+ : T
107
+ }
108
+
109
+ /**
110
+ * Checks if an object is empty (has no enumerable properties).
111
+ *
112
+ * @param obj - Object to check
113
+ * @returns True if object is empty or null/undefined
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * isEmptyObject({}) // true
118
+ * isEmptyObject({ a: 1 }) // false
119
+ * isEmptyObject(null) // true
120
+ * ```
121
+ */
122
+ export function isEmptyObject(obj: Record<string, unknown>) {
123
+ if (!obj) return true
124
+ return Object.keys(obj).length === 0
125
+ }
126
+
127
+ /**
128
+ * Checks if an array is empty or if the input is not an array.
129
+ *
130
+ * @param arr - Array or value to check
131
+ * @returns True if empty array or not an array
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * isEmptyArray([]) // true
136
+ * isEmptyArray([1, 2]) // false
137
+ * isEmptyArray('test') // true (not an array)
138
+ * isEmptyArray(null) // true
139
+ * ```
140
+ */
141
+ export function isEmptyArray(arr: string | unknown[]) {
142
+ if (!arr) return true
143
+ return Array.isArray(arr) && arr.length === 0
144
+ }
145
+
146
+ export function arraytoObject(array: Record<string, unknown>[]) {
147
+ return array.reduce((acc: Record<string, unknown>, currentObj) => {
148
+ const key = Object.keys(currentObj)[0]
149
+ if (key !== undefined) {
150
+ acc[key] = currentObj[key]
151
+ }
152
+ return acc
153
+ }, {})
154
+ }
155
+
156
+ export function shortenObjectKeys(
157
+ obj: Record<string, unknown>,
158
+ keyword: string
159
+ ) {
160
+ const regex = new RegExp(`[^]+${keyword}(.*)`)
161
+
162
+ for (const key in obj) {
163
+ const match = key.match(regex)
164
+
165
+ if (match?.[1]) {
166
+ const newKey = convertToCamelCase(match[1])
167
+ obj[newKey] = obj[key]
168
+ delete obj[key]
169
+ }
170
+ }
171
+
172
+ return obj
173
+ }
174
+
175
+ export function filterObjectKeys(
176
+ obj: { [x: string]: unknown },
177
+ keyword: string
178
+ ) {
179
+ const newObj: { [x: string]: unknown } = {}
180
+
181
+ for (const key in obj) {
182
+ const match = key.includes(keyword)
183
+
184
+ if (match) {
185
+ newObj[key] = obj[key]
186
+ }
187
+ }
188
+
189
+ return newObj
190
+ }
191
+
192
+ export function iterableObject(
193
+ obj: { [s: string]: unknown } | ArrayLike<unknown>
194
+ ) {
195
+ return Object.entries(obj).map(([, value]) => {
196
+ return value
197
+ })
198
+ }
199
+
200
+ // =============================================================================
201
+ // REF UTILITIES
202
+ // =============================================================================
203
+
204
+ /**
205
+ * Merges multiple React refs into a single ref callback.
206
+ *
207
+ * Useful when you need to assign multiple refs to the same element,
208
+ * such as combining a local ref with a forwarded ref.
209
+ *
210
+ * @param refs - Refs to merge (functions and ref objects supported)
211
+ * @returns A ref callback that updates all provided refs
212
+ *
213
+ * @example
214
+ * ```tsx
215
+ * // Component that needs both local and forwarded ref
216
+ * function MyComponent({ ref }: { ref: React.Ref<HTMLDivElement> }) {
217
+ * const localRef = useRef<HTMLDivElement>(null)
218
+ * const mergedRef = mergeRefs(localRef, ref)
219
+ *
220
+ * return <div ref={mergedRef}>Content</div>
221
+ * }
222
+ *
223
+ * // Forward ref pattern
224
+ * const ForwardedComponent = forwardRef(MyComponent)
225
+ * ```
226
+ */
227
+ export function mergeRefs<T>(...refs: (Ref<T> | undefined)[]): Ref<T> {
228
+ return (value) => {
229
+ const cleanups = refs.reduce<VoidFunction[]>((accumulator, ref) => {
230
+ if (typeof ref === "function") {
231
+ const cleanup = ref(value)
232
+ if (typeof cleanup === "function") {
233
+ accumulator.push(cleanup)
234
+ }
235
+ } else if (ref) {
236
+ ref.current = value
237
+ }
238
+
239
+ return accumulator
240
+ }, [])
241
+
242
+ return () => {
243
+ for (const cleanup of cleanups) cleanup()
244
+ }
245
+ }
246
+ }
@@ -0,0 +1,15 @@
1
+ // Type augmentations and module declarations
2
+
3
+ // React CSS custom properties support
4
+ import "react"
5
+
6
+ declare module "react" {
7
+ interface CSSProperties {
8
+ [key: `--${string}`]: string | number
9
+ }
10
+ }
11
+
12
+ // Global window extensions
13
+ declare global {
14
+ interface Window {}
15
+ }
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Unit tests for viewport utilities
3
+ *
4
+ * Run with: bun test lib/utils/viewport.test.ts
5
+ */
6
+
7
+ import { describe, expect, it } from "bun:test"
8
+ import { toem, torem, tovw } from "./viewport"
9
+
10
+ // Helper to check vw values with floating point tolerance
11
+ function expectVw(actual: string, expectedVw: number) {
12
+ const match = actual.match(/^([\d.]+)vw$/)
13
+ if (!match?.[1]) {
14
+ throw new Error(`Expected vw format, got: ${actual}`)
15
+ }
16
+ const actualVw = Number.parseFloat(match[1])
17
+ expect(actualVw).toBeCloseTo(expectedVw, 4)
18
+ }
19
+
20
+ // Helper to check max() values with floating point tolerance
21
+ function expectMaxVw(actual: string, expectedMin: number, expectedVw: number) {
22
+ const match = actual.match(/^max\((\d+)px, ([\d.]+)vw\)$/)
23
+ if (!(match?.[1] && match[2])) {
24
+ throw new Error(`Expected max() format, got: ${actual}`)
25
+ }
26
+ const actualMin = Number.parseFloat(match[1])
27
+ const actualVw = Number.parseFloat(match[2])
28
+ expect(actualMin).toBe(expectedMin)
29
+ expect(actualVw).toBeCloseTo(expectedVw, 4)
30
+ }
31
+
32
+ describe("tovw", () => {
33
+ describe("basic conversion (default desktop context)", () => {
34
+ it("should convert pixels to vw with default desktop context (1440px)", () => {
35
+ expectVw(tovw(100), 6.9444)
36
+ expect(tovw(1440)).toBe("100vw")
37
+ expect(tovw(720)).toBe("50vw")
38
+ })
39
+
40
+ it("should handle zero", () => {
41
+ expect(tovw(0)).toBe("0")
42
+ })
43
+ })
44
+
45
+ describe("with minimum value", () => {
46
+ it("should apply minimum value using max()", () => {
47
+ expectMaxVw(tovw(100, 50), 50, 6.9444)
48
+ expectMaxVw(tovw(200, 100), 100, 13.8889)
49
+ })
50
+
51
+ it("should handle zero target with min", () => {
52
+ expect(tovw(0, 50)).toBe("0")
53
+ })
54
+ })
55
+
56
+ describe("with context identifier", () => {
57
+ it("should use mobile context (375px)", () => {
58
+ expectVw(tovw(16, "mobile"), 4.2667)
59
+ expect(tovw(375, "mobile")).toBe("100vw")
60
+ })
61
+
62
+ it("should use desktop-large context (1920px)", () => {
63
+ expectVw(tovw(100, "desktop-large"), 5.2083)
64
+ expect(tovw(1920, "desktop-large")).toBe("100vw")
65
+ })
66
+
67
+ it("should use tablet-lg context (1024px)", () => {
68
+ expectVw(tovw(100, "tablet-lg"), 9.7656)
69
+ expect(tovw(1024, "tablet-lg")).toBe("100vw")
70
+ })
71
+
72
+ it("should use tablet context (620px)", () => {
73
+ expectVw(tovw(100, "tablet"), 16.129)
74
+ expect(tovw(620, "tablet")).toBe("100vw")
75
+ })
76
+
77
+ it("should use desktop context explicitly", () => {
78
+ expectVw(tovw(100, "desktop"), 6.9444)
79
+ })
80
+ })
81
+
82
+ describe("with custom numeric context", () => {
83
+ it("should use custom pixel context as third parameter", () => {
84
+ // Numeric contexts must be passed as third parameter (with undefined min)
85
+ // Using type assertion to test the implementation behavior
86
+ expectVw(tovw(100, 1920), 5.2083)
87
+ expectVw(tovw(100, 800), 12.5)
88
+ expectVw(tovw(100, 2000), 5)
89
+ })
90
+
91
+ it("should treat numeric second parameter as min, not context", () => {
92
+ // When second param is a number, it's treated as min, not context
93
+ expectMaxVw(tovw(100, 1920), 1920, 6.9444)
94
+ })
95
+ })
96
+
97
+ describe("with both min and context", () => {
98
+ it("should apply min and context together", () => {
99
+ expectMaxVw(tovw(100, 50, "mobile"), 50, 26.6667)
100
+ expectMaxVw(tovw(200, 100, "desktop-large"), 100, 10.4167)
101
+ })
102
+
103
+ it("should use numeric context with min", () => {
104
+ expectMaxVw(tovw(100, 50, 1920), 50, 5.2083)
105
+ })
106
+ })
107
+
108
+ describe("edge cases", () => {
109
+ it("should handle very small values", () => {
110
+ expectVw(tovw(1), 0.0694)
111
+ expectVw(tovw(0.5), 0.0347)
112
+ })
113
+
114
+ it("should handle very large values", () => {
115
+ expectVw(tovw(5000), 347.2222)
116
+ })
117
+
118
+ it("should handle decimal values", () => {
119
+ expectVw(tovw(16.5), 1.1458)
120
+ expectVw(tovw(16.5, "mobile"), 4.4)
121
+ })
122
+ })
123
+ })
124
+
125
+ describe("torem", () => {
126
+ describe("basic conversion (default 16px context)", () => {
127
+ it("should convert pixels to rem with default context", () => {
128
+ expect(torem(16)).toBe("1rem")
129
+ expect(torem(24)).toBe("1.5rem")
130
+ expect(torem(32)).toBe("2rem")
131
+ expect(torem(8)).toBe("0.5rem")
132
+ })
133
+
134
+ it("should handle zero", () => {
135
+ expect(torem(0)).toBe("0")
136
+ })
137
+ })
138
+
139
+ describe("with custom context", () => {
140
+ it("should use custom base font size", () => {
141
+ // Using toBeCloseTo for floating point comparison
142
+ const result1 = torem(18, 14)
143
+ const match1 = result1.match(/^([\d.]+)rem$/)
144
+ expect(Number.parseFloat(match1?.[1] || "0")).toBeCloseTo(
145
+ 1.2857142857142858,
146
+ 4
147
+ )
148
+
149
+ expect(torem(20, 10)).toBe("2rem")
150
+ expect(torem(24, 12)).toBe("2rem")
151
+ })
152
+
153
+ it("should handle zero with custom context", () => {
154
+ expect(torem(0, 14)).toBe("0")
155
+ })
156
+ })
157
+
158
+ describe("edge cases", () => {
159
+ it("should handle decimal values", () => {
160
+ const result1 = torem(16.5)
161
+ const match1 = result1.match(/^([\d.]+)rem$/)
162
+ // 16.5 / 16 = 1.03125, rounded to 4 decimals = 1.0313
163
+ expect(Number.parseFloat(match1?.[1] || "0")).toBeCloseTo(1.0313, 4)
164
+
165
+ const result2 = torem(18.5, 14)
166
+ const match2 = result2.match(/^([\d.]+)rem$/)
167
+ // 18.5 / 14 = 1.3214285714285714, rounded to 4 decimals = 1.3214
168
+ expect(Number.parseFloat(match2?.[1] || "0")).toBeCloseTo(1.3214, 4)
169
+ })
170
+
171
+ it("should handle very small values", () => {
172
+ const result1 = torem(1)
173
+ const match1 = result1.match(/^([\d.]+)rem$/)
174
+ expect(Number.parseFloat(match1?.[1] || "0")).toBeCloseTo(0.0625, 4)
175
+
176
+ const result2 = torem(0.5)
177
+ const match2 = result2.match(/^([\d.]+)rem$/)
178
+ // 0.5 / 16 = 0.03125, rounded to 4 decimals = 0.0313
179
+ expect(Number.parseFloat(match2?.[1] || "0")).toBeCloseTo(0.0313, 4)
180
+ })
181
+
182
+ it("should handle very large values", () => {
183
+ expect(torem(100)).toBe("6.25rem")
184
+ expect(torem(200)).toBe("12.5rem")
185
+ })
186
+ })
187
+ })
188
+
189
+ describe("toem", () => {
190
+ describe("basic conversion", () => {
191
+ it("should convert pixels to em", () => {
192
+ expect(toem(24, 16)).toBe("1.5em")
193
+ expect(toem(16, 16)).toBe("1em")
194
+ expect(toem(32, 16)).toBe("2em")
195
+ expect(toem(8, 16)).toBe("0.5em")
196
+ })
197
+
198
+ it("should handle zero", () => {
199
+ expect(toem(0, 16)).toBe("0")
200
+ })
201
+ })
202
+
203
+ describe("with different contexts", () => {
204
+ it("should use custom context size", () => {
205
+ const result1 = toem(18, 14)
206
+ const match1 = result1.match(/^([\d.]+)em$/)
207
+ expect(Number.parseFloat(match1?.[1] || "0")).toBeCloseTo(
208
+ 1.2857142857142858,
209
+ 4
210
+ )
211
+
212
+ expect(toem(20, 10)).toBe("2em")
213
+ expect(toem(24, 12)).toBe("2em")
214
+ })
215
+
216
+ it("should handle zero with different contexts", () => {
217
+ expect(toem(0, 14)).toBe("0")
218
+ expect(toem(0, 20)).toBe("0")
219
+ })
220
+ })
221
+
222
+ describe("edge cases", () => {
223
+ it("should handle decimal values", () => {
224
+ const result1 = toem(16.5, 16)
225
+ const match1 = result1.match(/^([\d.]+)em$/)
226
+ // 16.5 / 16 = 1.03125, rounded to 4 decimals = 1.0313
227
+ expect(Number.parseFloat(match1?.[1] || "0")).toBeCloseTo(1.0313, 4)
228
+
229
+ const result2 = toem(18.5, 14)
230
+ const match2 = result2.match(/^([\d.]+)em$/)
231
+ // 18.5 / 14 = 1.3214285714285714, rounded to 4 decimals = 1.3214
232
+ expect(Number.parseFloat(match2?.[1] || "0")).toBeCloseTo(1.3214, 4)
233
+ })
234
+
235
+ it("should handle very small values", () => {
236
+ const result1 = toem(1, 16)
237
+ const match1 = result1.match(/^([\d.]+)em$/)
238
+ expect(Number.parseFloat(match1?.[1] || "0")).toBeCloseTo(0.0625, 4)
239
+
240
+ const result2 = toem(0.5, 16)
241
+ const match2 = result2.match(/^([\d.]+)em$/)
242
+ // 0.5 / 16 = 0.03125, rounded to 4 decimals = 0.0313
243
+ expect(Number.parseFloat(match2?.[1] || "0")).toBeCloseTo(0.0313, 4)
244
+ })
245
+
246
+ it("should handle very large values", () => {
247
+ expect(toem(100, 16)).toBe("6.25em")
248
+ expect(toem(200, 16)).toBe("12.5em")
249
+ })
250
+
251
+ it("should handle context larger than target", () => {
252
+ expect(toem(8, 16)).toBe("0.5em")
253
+ expect(toem(4, 16)).toBe("0.25em")
254
+ })
255
+ })
256
+ })
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Viewport Utilities
3
+ *
4
+ * Convert pixel values to viewport-relative sizes and relative units.
5
+ * Mirrors the SASS functions for consistent responsive scaling.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { tovw, torem, toem } from '@/utils/viewport'
10
+ *
11
+ * // Convert to viewport width units
12
+ * const paddingX = tovw(16, 24) // "1.5625vw" with a minimum of 24px
13
+ * const fontSize = tovw(16, 'mobile') // "4.27vw" (no undefined needed!)
14
+ * const width = tovw(100, 50, 'desktop') // "max(50px, 6.94vw)"
15
+ *
16
+ * // Convert to rem/em
17
+ * const spacing = torem(24) // "1.5rem"
18
+ * const padding = toem(16, 14) // "1.14em"
19
+ * ```
20
+ *
21
+ * ## When to Use
22
+ *
23
+ * - **CSS**: Prefer the PostCSS functions `tovw()`, `torem()`, and `toem()`
24
+ * - **JavaScript/TypeScript**: Use these for dynamic calculations or inline styles
25
+ * - **Runtime calculations**: Use for canvas, WebGL, or dynamic sizing
26
+ */
27
+
28
+ import { breakpoints } from "@/lib/styles/config"
29
+
30
+ type ContextSize = keyof typeof breakpoints | number
31
+
32
+ /**
33
+ * Resolves a context size to a pixel value.
34
+ *
35
+ * @param context - Context identifier or pixel value
36
+ * @returns Pixel value
37
+ */
38
+ function resolveContext(context: ContextSize): number {
39
+ if (typeof context === "number") {
40
+ return context
41
+ }
42
+ return breakpoints[context] ?? breakpoints.desktop
43
+ }
44
+
45
+ /**
46
+ * Checks if a value is a context identifier (breakpoint key).
47
+ *
48
+ * @param value - Value to check
49
+ * @returns True if value is a breakpoint key
50
+ */
51
+ function isContextIdentifier(
52
+ value: unknown
53
+ ): value is keyof typeof breakpoints {
54
+ return typeof value === "string" && value in breakpoints
55
+ }
56
+
57
+ /**
58
+ * Rounds a number to a maximum of 4 decimal places, removing trailing zeros.
59
+ *
60
+ * @param value - Number to round
61
+ * @returns Rounded number as string without trailing zeros
62
+ */
63
+ function roundToMaxDecimals(value: number): string {
64
+ // Round to 4 decimal places
65
+ const rounded = Math.round(value * 10000) / 10000
66
+ // Convert to string and remove trailing zeros only after decimal point
67
+ return rounded
68
+ .toString()
69
+ .replace(/\.0+$/, "")
70
+ .replace(/(\.\d*?)0+$/, "$1")
71
+ }
72
+
73
+ // Function overloads for tovw
74
+ export function tovw(target: number): string
75
+ export function tovw(target: number, min: number): string
76
+ export function tovw(target: number, context: ContextSize): string
77
+ export function tovw(target: number, min: number, context: ContextSize): string
78
+ /**
79
+ * Converts a pixel value to viewport width units (vw).
80
+ * Optionally applies a minimum value using CSS max().
81
+ *
82
+ * Supports flexible parameter patterns:
83
+ * - `tovw(target)` - Uses default desktop context
84
+ * - `tovw(target, min)` - Sets minimum value
85
+ * - `tovw(target, context)` - Sets context (auto-detected if string identifier)
86
+ * - `tovw(target, min, context)` - Sets both min and context
87
+ *
88
+ * @param target - Target pixel value to convert
89
+ * @param minOrContext - Optional minimum pixel value OR context identifier
90
+ * @param context - Context size identifier (if min was provided)
91
+ * @returns CSS string with vw units (e.g., "6.94vw" or "max(50px, 6.94vw)")
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * // Basic conversion
96
+ * tovw(100) // "6.94vw"
97
+ * tovw(16, 'mobile') // "4.27vw" (no undefined needed!)
98
+ *
99
+ * // With minimum value
100
+ * tovw(100, 50) // "max(50px, 6.94vw)"
101
+ * tovw(100, 50, 'mobile') // "max(50px, 6.94vw)" with mobile context
102
+ *
103
+ * // Custom context
104
+ * tovw(100, 1920) // "5.21vw"
105
+ *
106
+ * // Handles zero
107
+ * tovw(0) // "0"
108
+ * ```
109
+ */
110
+ export function tovw(
111
+ target: number,
112
+ minOrContext?: number | ContextSize,
113
+ context?: ContextSize
114
+ ): string {
115
+ if (target === 0) {
116
+ return "0"
117
+ }
118
+
119
+ let min: number | undefined
120
+ let resolvedContext: ContextSize = "desktop"
121
+
122
+ if (minOrContext !== undefined) {
123
+ if (isContextIdentifier(minOrContext)) {
124
+ // Second param is context
125
+ resolvedContext = minOrContext
126
+ if (context !== undefined) {
127
+ resolvedContext = context
128
+ }
129
+ } else if (typeof minOrContext === "number") {
130
+ // Second param is min value
131
+ min = minOrContext
132
+ if (context !== undefined) {
133
+ resolvedContext = context
134
+ }
135
+ }
136
+ } else if (context !== undefined) {
137
+ // Only context provided as third param (legacy support)
138
+ resolvedContext = context
139
+ }
140
+
141
+ const contextSize = resolveContext(resolvedContext)
142
+ const vwValue = (target / contextSize) * 100
143
+
144
+ if (min !== undefined) {
145
+ return `max(${min}px, ${roundToMaxDecimals(vwValue)}vw)`
146
+ }
147
+
148
+ return `${roundToMaxDecimals(vwValue)}vw`
149
+ }
150
+
151
+ /**
152
+ * Converts a pixel value to rem units.
153
+ *
154
+ * @param target - Target pixel value to convert
155
+ * @param context - Base font size in pixels (default: 16)
156
+ * @returns CSS string with rem units (e.g., "1.5rem")
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * torem(24) // "1.5rem" (24 / 16)
161
+ * torem(18, 14) // "1.29rem" (18 / 14)
162
+ * torem(0) // "0"
163
+ * ```
164
+ */
165
+ export function torem(target: number, context = 16): string {
166
+ if (target === 0) {
167
+ return "0"
168
+ }
169
+
170
+ return `${roundToMaxDecimals(target / context)}rem`
171
+ }
172
+
173
+ /**
174
+ * Converts a pixel value to em units.
175
+ *
176
+ * @param target - Target pixel value to convert
177
+ * @param context - Context size in pixels (required)
178
+ * @returns CSS string with em units (e.g., "1.5em")
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * toem(24, 16) // "1.5em" (24 / 16)
183
+ * toem(18, 14) // "1.29em" (18 / 14)
184
+ * toem(0, 16) // "0"
185
+ * ```
186
+ */
187
+ export function toem(target: number, context: number): string {
188
+ if (target === 0) {
189
+ return "0"
190
+ }
191
+
192
+ return `${roundToMaxDecimals(target / context)}em`
193
+ }