bsmnt 0.0.2 → 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 (346) hide show
  1. package/.github/workflows/release.yml +2 -0
  2. package/CHANGELOG.md +31 -0
  3. package/CLAUDE.md +42 -24
  4. package/README.md +33 -12
  5. package/biome.json +1 -0
  6. package/bun.lock +2 -1
  7. package/docs/architecture.drawio +250 -0
  8. package/docs/architecture.mermaid +85 -0
  9. package/package.json +42 -42
  10. package/{bin → packages/cli/bin}/index.js +28 -31
  11. package/packages/cli/package.json +16 -0
  12. package/{src → packages/cli/src}/commands/add-integration.js +22 -37
  13. package/{src → packages/cli/src}/commands/create.js +125 -131
  14. package/packages/create-basement-app/integrations/sanity/config.js +46 -0
  15. package/{src → packages/create-basement-app/integrations/sanity}/mergers/check-integration-merger.js +1 -1
  16. package/{src → packages/create-basement-app/integrations/sanity}/mergers/layout-merger.js +1 -1
  17. package/{src → packages/create-basement-app/integrations/sanity}/mergers/sitemap-merger.js +1 -1
  18. package/packages/create-basement-app/package.json +10 -0
  19. package/packages/create-basement-app/src/configs/animations.js +28 -0
  20. package/packages/create-basement-app/src/index.js +15 -0
  21. package/packages/create-basement-app/src/mergers/check-integration-merger.js +105 -0
  22. package/{src → packages/create-basement-app/src}/mergers/config.js +5 -33
  23. package/{src → packages/create-basement-app/src}/mergers/index.js +89 -98
  24. package/packages/create-basement-app/src/mergers/layout-merger.js +223 -0
  25. package/packages/create-basement-app/src/mergers/sitemap-merger.js +121 -0
  26. package/packages/create-basement-app/template-hooks/config.js +38 -0
  27. package/packages/create-basement-app/templates/default/.biome/plugins/README.md +21 -0
  28. package/packages/create-basement-app/templates/default/.biome/plugins/no-anchor-element.grit +12 -0
  29. package/packages/create-basement-app/templates/default/.biome/plugins/no-relative-parent-imports.grit +10 -0
  30. package/packages/create-basement-app/templates/default/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  31. package/packages/create-basement-app/templates/default/.cursor/rules/README.md +184 -0
  32. package/packages/create-basement-app/templates/default/.cursor/rules/architecture.mdc +437 -0
  33. package/packages/create-basement-app/templates/default/.cursor/rules/components.mdc +436 -0
  34. package/packages/create-basement-app/templates/default/.cursor/rules/integrations.mdc +447 -0
  35. package/packages/create-basement-app/templates/default/.cursor/rules/main.mdc +278 -0
  36. package/packages/create-basement-app/templates/default/.cursor/rules/styling.mdc +433 -0
  37. package/packages/create-basement-app/templates/default/.editorconfig +40 -0
  38. package/packages/create-basement-app/templates/default/.env.example +81 -0
  39. package/packages/create-basement-app/templates/default/.gitattributes +19 -0
  40. package/packages/create-basement-app/templates/default/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  41. package/packages/create-basement-app/templates/default/.github/workflows/lighthouse-to-slack.yml +136 -0
  42. package/packages/create-basement-app/templates/default/.vscode/extensions.json +20 -0
  43. package/packages/create-basement-app/templates/default/.vscode/settings.json +105 -0
  44. package/packages/create-basement-app/templates/default/README.md +221 -0
  45. package/packages/create-basement-app/templates/default/_gitignore +67 -0
  46. package/packages/create-basement-app/templates/default/app/favicon.ico +0 -0
  47. package/packages/create-basement-app/templates/default/app/layout.tsx +104 -0
  48. package/packages/create-basement-app/templates/default/app/page.tsx +275 -0
  49. package/packages/create-basement-app/templates/default/app/robots.ts +15 -0
  50. package/packages/create-basement-app/templates/default/app/sitemap.ts +16 -0
  51. package/packages/create-basement-app/templates/default/biome.json +250 -0
  52. package/packages/create-basement-app/templates/default/components/basement.svg +1 -0
  53. package/packages/create-basement-app/templates/default/components/layout/footer/index.tsx +27 -0
  54. package/packages/create-basement-app/templates/default/components/layout/header/index.tsx +11 -0
  55. package/packages/create-basement-app/templates/default/components/layout/theme/index.tsx +66 -0
  56. package/packages/create-basement-app/templates/default/components/layout/wrapper/index.tsx +65 -0
  57. package/packages/create-basement-app/templates/default/components/ui/README.md +77 -0
  58. package/packages/create-basement-app/templates/default/components/ui/image/README.md +37 -0
  59. package/packages/create-basement-app/templates/default/components/ui/image/index.tsx +224 -0
  60. package/packages/create-basement-app/templates/default/components/ui/link/index.tsx +146 -0
  61. package/packages/create-basement-app/templates/default/lib/README.md +33 -0
  62. package/packages/create-basement-app/templates/default/lib/hooks/index.ts +12 -0
  63. package/packages/create-basement-app/templates/default/lib/hooks/use-device-detection.ts +81 -0
  64. package/packages/create-basement-app/templates/default/lib/hooks/use-media-breakpoint.ts +15 -0
  65. package/packages/create-basement-app/templates/default/lib/hooks/use-prefetch.ts +74 -0
  66. package/packages/create-basement-app/templates/default/lib/scripts/dev.ts +52 -0
  67. package/packages/create-basement-app/templates/default/lib/scripts/generate-component.ts +322 -0
  68. package/packages/create-basement-app/templates/default/lib/scripts/generate-page.ts +193 -0
  69. package/packages/create-basement-app/templates/default/lib/scripts/generate.ts +79 -0
  70. package/packages/create-basement-app/templates/default/lib/scripts/utils.ts +246 -0
  71. package/packages/create-basement-app/templates/default/lib/store/app.ts +11 -0
  72. package/packages/create-basement-app/templates/default/lib/store/index.ts +11 -0
  73. package/packages/create-basement-app/templates/default/lib/styles/README.md +64 -0
  74. package/packages/create-basement-app/templates/default/lib/styles/cn.ts +7 -0
  75. package/packages/create-basement-app/templates/default/lib/styles/colors.ts +63 -0
  76. package/packages/create-basement-app/templates/default/lib/styles/config.ts +34 -0
  77. package/packages/create-basement-app/templates/default/lib/styles/css/global.css +85 -0
  78. package/packages/create-basement-app/templates/default/lib/styles/css/index.css +6 -0
  79. package/packages/create-basement-app/templates/default/lib/styles/css/reset.css +166 -0
  80. package/packages/create-basement-app/templates/default/lib/styles/css/root.css +68 -0
  81. package/packages/create-basement-app/templates/default/lib/styles/css/tailwind.css +132 -0
  82. package/packages/create-basement-app/templates/default/lib/styles/easings.ts +21 -0
  83. package/packages/create-basement-app/templates/default/lib/styles/fonts.ts +28 -0
  84. package/packages/create-basement-app/templates/default/lib/styles/index.ts +12 -0
  85. package/packages/create-basement-app/templates/default/lib/styles/layout.mjs +27 -0
  86. package/packages/create-basement-app/templates/default/lib/styles/scripts/README.md +29 -0
  87. package/packages/create-basement-app/templates/default/lib/styles/scripts/generate-root.ts +57 -0
  88. package/packages/create-basement-app/templates/default/lib/styles/scripts/generate-tailwind.ts +162 -0
  89. package/packages/create-basement-app/templates/default/lib/styles/scripts/postcss-functions.mjs +168 -0
  90. package/packages/create-basement-app/templates/default/lib/styles/scripts/setup-styles.ts +24 -0
  91. package/packages/create-basement-app/templates/default/lib/styles/scripts/utils.ts +20 -0
  92. package/packages/create-basement-app/templates/default/lib/styles/typography.ts +36 -0
  93. package/packages/create-basement-app/templates/default/lib/utils/README.md +40 -0
  94. package/packages/create-basement-app/templates/default/lib/utils/css.d.ts +21 -0
  95. package/packages/create-basement-app/templates/default/lib/utils/easings.ts +240 -0
  96. package/packages/create-basement-app/templates/default/lib/utils/fetch.ts +84 -0
  97. package/packages/create-basement-app/templates/default/lib/utils/math.test.ts +221 -0
  98. package/packages/create-basement-app/templates/default/lib/utils/math.ts +236 -0
  99. package/packages/create-basement-app/templates/default/lib/utils/metadata.ts +126 -0
  100. package/packages/create-basement-app/templates/default/lib/utils/strings.test.ts +166 -0
  101. package/packages/create-basement-app/templates/default/lib/utils/strings.ts +246 -0
  102. package/packages/create-basement-app/templates/default/lib/utils/types.d.ts +15 -0
  103. package/packages/create-basement-app/templates/default/lib/utils/viewport.test.ts +256 -0
  104. package/packages/create-basement-app/templates/default/lib/utils/viewport.ts +193 -0
  105. package/packages/create-basement-app/templates/default/next.config.ts +142 -0
  106. package/packages/create-basement-app/templates/default/package.json +62 -0
  107. package/packages/create-basement-app/templates/default/postcss.config.mjs +42 -0
  108. package/packages/create-basement-app/templates/default/public/fonts/geist/Geist-Mono.woff2 +0 -0
  109. package/packages/create-basement-app/templates/default/tsconfig.json +43 -0
  110. package/packages/create-basement-app/templates/experiment/.biome/plugins/README.md +21 -0
  111. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-anchor-element.grit +12 -0
  112. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-relative-parent-imports.grit +10 -0
  113. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  114. package/packages/create-basement-app/templates/experiment/.cursor/rules/README.md +184 -0
  115. package/packages/create-basement-app/templates/experiment/.cursor/rules/architecture.mdc +437 -0
  116. package/packages/create-basement-app/templates/experiment/.cursor/rules/components.mdc +436 -0
  117. package/packages/create-basement-app/templates/experiment/.cursor/rules/integrations.mdc +447 -0
  118. package/packages/create-basement-app/templates/experiment/.cursor/rules/main.mdc +278 -0
  119. package/packages/create-basement-app/templates/experiment/.cursor/rules/styling.mdc +433 -0
  120. package/packages/create-basement-app/templates/experiment/.editorconfig +40 -0
  121. package/packages/create-basement-app/templates/experiment/.env.example +81 -0
  122. package/packages/create-basement-app/templates/experiment/.gitattributes +19 -0
  123. package/packages/create-basement-app/templates/experiment/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  124. package/packages/create-basement-app/templates/experiment/.github/workflows/lighthouse-to-slack.yml +136 -0
  125. package/packages/create-basement-app/templates/experiment/.vscode/extensions.json +20 -0
  126. package/packages/create-basement-app/templates/experiment/.vscode/settings.json +105 -0
  127. package/packages/create-basement-app/templates/experiment/README.md +221 -0
  128. package/packages/create-basement-app/templates/experiment/_gitignore +67 -0
  129. package/packages/create-basement-app/templates/experiment/app/favicon.ico +0 -0
  130. package/packages/create-basement-app/templates/experiment/app/layout.tsx +104 -0
  131. package/packages/create-basement-app/templates/experiment/app/page.tsx +275 -0
  132. package/packages/create-basement-app/templates/experiment/app/robots.ts +15 -0
  133. package/packages/create-basement-app/templates/experiment/app/sitemap.ts +16 -0
  134. package/packages/create-basement-app/templates/experiment/biome.json +250 -0
  135. package/packages/create-basement-app/templates/experiment/components/basement.svg +1 -0
  136. package/packages/create-basement-app/templates/experiment/components/layout/footer/index.tsx +27 -0
  137. package/packages/create-basement-app/templates/experiment/components/layout/header/index.tsx +58 -0
  138. package/packages/create-basement-app/templates/experiment/components/layout/navigation-menu.tsx +127 -0
  139. package/packages/create-basement-app/templates/experiment/components/layout/theme/index.tsx +66 -0
  140. package/packages/create-basement-app/templates/experiment/components/layout/wrapper/index.tsx +65 -0
  141. package/packages/create-basement-app/templates/experiment/components/ui/README.md +77 -0
  142. package/packages/create-basement-app/templates/experiment/components/ui/image/README.md +37 -0
  143. package/packages/create-basement-app/templates/experiment/components/ui/image/index.tsx +224 -0
  144. package/packages/create-basement-app/templates/experiment/components/ui/link/index.tsx +146 -0
  145. package/packages/create-basement-app/templates/experiment/lib/README.md +33 -0
  146. package/packages/create-basement-app/templates/experiment/lib/constants.ts +12 -0
  147. package/packages/create-basement-app/templates/experiment/lib/hooks/index.ts +12 -0
  148. package/packages/create-basement-app/templates/experiment/lib/hooks/use-device-detection.ts +81 -0
  149. package/packages/create-basement-app/templates/experiment/lib/hooks/use-media-breakpoint.ts +15 -0
  150. package/packages/create-basement-app/templates/experiment/lib/hooks/use-prefetch.ts +74 -0
  151. package/packages/create-basement-app/templates/experiment/lib/integrations/.gitkeep +0 -0
  152. package/packages/create-basement-app/templates/experiment/lib/scripts/dev.ts +52 -0
  153. package/packages/create-basement-app/templates/experiment/lib/scripts/generate-component.ts +322 -0
  154. package/packages/create-basement-app/templates/experiment/lib/scripts/generate-page.ts +193 -0
  155. package/packages/create-basement-app/templates/experiment/lib/scripts/generate.ts +79 -0
  156. package/packages/create-basement-app/templates/experiment/lib/scripts/utils.ts +246 -0
  157. package/packages/create-basement-app/templates/experiment/lib/store/app.ts +11 -0
  158. package/packages/create-basement-app/templates/experiment/lib/store/index.ts +11 -0
  159. package/packages/create-basement-app/templates/experiment/lib/styles/README.md +64 -0
  160. package/packages/create-basement-app/templates/experiment/lib/styles/cn.ts +7 -0
  161. package/packages/create-basement-app/templates/experiment/lib/styles/colors.ts +63 -0
  162. package/packages/create-basement-app/templates/experiment/lib/styles/config.ts +34 -0
  163. package/packages/create-basement-app/templates/experiment/lib/styles/css/global.css +85 -0
  164. package/packages/create-basement-app/templates/experiment/lib/styles/css/index.css +6 -0
  165. package/packages/create-basement-app/templates/experiment/lib/styles/css/reset.css +166 -0
  166. package/packages/create-basement-app/templates/experiment/lib/styles/css/root.css +68 -0
  167. package/packages/create-basement-app/templates/experiment/lib/styles/css/tailwind.css +132 -0
  168. package/packages/create-basement-app/templates/experiment/lib/styles/easings.ts +21 -0
  169. package/packages/create-basement-app/templates/experiment/lib/styles/fonts.ts +28 -0
  170. package/packages/create-basement-app/templates/experiment/lib/styles/index.ts +12 -0
  171. package/packages/create-basement-app/templates/experiment/lib/styles/layout.mjs +27 -0
  172. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/README.md +29 -0
  173. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/generate-root.ts +57 -0
  174. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/generate-tailwind.ts +162 -0
  175. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/postcss-functions.mjs +168 -0
  176. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/setup-styles.ts +24 -0
  177. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/utils.ts +20 -0
  178. package/packages/create-basement-app/templates/experiment/lib/styles/typography.ts +36 -0
  179. package/packages/create-basement-app/templates/experiment/lib/utils/README.md +40 -0
  180. package/packages/create-basement-app/templates/experiment/lib/utils/css.d.ts +21 -0
  181. package/packages/create-basement-app/templates/experiment/lib/utils/easings.ts +240 -0
  182. package/packages/create-basement-app/templates/experiment/lib/utils/fetch.ts +84 -0
  183. package/packages/create-basement-app/templates/experiment/lib/utils/math.test.ts +221 -0
  184. package/packages/create-basement-app/templates/experiment/lib/utils/math.ts +236 -0
  185. package/packages/create-basement-app/templates/experiment/lib/utils/metadata.ts +126 -0
  186. package/packages/create-basement-app/templates/experiment/lib/utils/strings.test.ts +166 -0
  187. package/packages/create-basement-app/templates/experiment/lib/utils/strings.ts +246 -0
  188. package/packages/create-basement-app/templates/experiment/lib/utils/types.d.ts +15 -0
  189. package/packages/create-basement-app/templates/experiment/lib/utils/viewport.test.ts +256 -0
  190. package/packages/create-basement-app/templates/experiment/lib/utils/viewport.ts +193 -0
  191. package/packages/create-basement-app/templates/experiment/next.config.ts +142 -0
  192. package/packages/create-basement-app/templates/experiment/package.json +69 -0
  193. package/packages/create-basement-app/templates/experiment/postcss.config.mjs +42 -0
  194. package/packages/create-basement-app/templates/experiment/public/fonts/geist/Geist-Mono.woff2 +0 -0
  195. package/packages/create-basement-app/templates/experiment/tsconfig.json +43 -0
  196. package/packages/create-basement-app/templates/webgl/.biome/plugins/README.md +21 -0
  197. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-anchor-element.grit +12 -0
  198. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-relative-parent-imports.grit +10 -0
  199. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  200. package/packages/create-basement-app/templates/webgl/.cursor/rules/README.md +184 -0
  201. package/packages/create-basement-app/templates/webgl/.cursor/rules/architecture.mdc +437 -0
  202. package/packages/create-basement-app/templates/webgl/.cursor/rules/components.mdc +436 -0
  203. package/packages/create-basement-app/templates/webgl/.cursor/rules/integrations.mdc +447 -0
  204. package/packages/create-basement-app/templates/webgl/.cursor/rules/main.mdc +278 -0
  205. package/packages/create-basement-app/templates/webgl/.cursor/rules/styling.mdc +433 -0
  206. package/packages/create-basement-app/templates/webgl/.editorconfig +40 -0
  207. package/packages/create-basement-app/templates/webgl/.env.example +81 -0
  208. package/packages/create-basement-app/templates/webgl/.gitattributes +19 -0
  209. package/packages/create-basement-app/templates/webgl/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  210. package/packages/create-basement-app/templates/webgl/.github/workflows/lighthouse-to-slack.yml +136 -0
  211. package/packages/create-basement-app/templates/webgl/.vscode/extensions.json +20 -0
  212. package/packages/create-basement-app/templates/webgl/.vscode/settings.json +105 -0
  213. package/packages/create-basement-app/templates/webgl/README.md +221 -0
  214. package/packages/create-basement-app/templates/webgl/_gitignore +67 -0
  215. package/packages/create-basement-app/templates/webgl/app/favicon.ico +0 -0
  216. package/packages/create-basement-app/templates/webgl/app/layout.tsx +104 -0
  217. package/packages/create-basement-app/templates/webgl/app/page.tsx +10 -0
  218. package/packages/create-basement-app/templates/webgl/app/robots.ts +15 -0
  219. package/packages/create-basement-app/templates/webgl/app/sitemap.ts +16 -0
  220. package/packages/create-basement-app/templates/webgl/biome.json +250 -0
  221. package/packages/create-basement-app/templates/webgl/components/basement.svg +1 -0
  222. package/packages/create-basement-app/templates/webgl/components/layout/footer/index.tsx +27 -0
  223. package/packages/create-basement-app/templates/webgl/components/layout/header/index.tsx +11 -0
  224. package/packages/create-basement-app/templates/webgl/components/layout/theme/index.tsx +66 -0
  225. package/packages/create-basement-app/templates/webgl/components/layout/wrapper/index.tsx +65 -0
  226. package/packages/create-basement-app/templates/webgl/components/ui/README.md +77 -0
  227. package/packages/create-basement-app/templates/webgl/components/ui/image/README.md +37 -0
  228. package/packages/create-basement-app/templates/webgl/components/ui/image/index.tsx +224 -0
  229. package/packages/create-basement-app/templates/webgl/components/ui/link/index.tsx +146 -0
  230. package/packages/create-basement-app/templates/webgl/components/webgl/canvas/dynamic.tsx +34 -0
  231. package/packages/create-basement-app/templates/webgl/components/webgl/canvas/index.tsx +38 -0
  232. package/packages/create-basement-app/templates/webgl/components/webgl/components/scene/index.tsx +29 -0
  233. package/packages/create-basement-app/templates/webgl/lib/README.md +33 -0
  234. package/packages/create-basement-app/templates/webgl/lib/hooks/index.ts +12 -0
  235. package/packages/create-basement-app/templates/webgl/lib/hooks/use-device-detection.ts +81 -0
  236. package/packages/create-basement-app/templates/webgl/lib/hooks/use-media-breakpoint.ts +15 -0
  237. package/packages/create-basement-app/templates/webgl/lib/hooks/use-prefetch.ts +74 -0
  238. package/packages/create-basement-app/templates/webgl/lib/integrations/.gitkeep +0 -0
  239. package/packages/create-basement-app/templates/webgl/lib/renderer.ts +7 -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/tasks/.last-branch +1 -0
  285. package/tasks/CLAUDE.md +104 -0
  286. package/tasks/archive/2026-02-09-next-starter-dynamic-layers/prd.json +153 -0
  287. package/tasks/archive/2026-02-09-next-starter-dynamic-layers/progress.txt +115 -0
  288. package/tasks/prd-project-restructure.md +375 -0
  289. package/tasks/prd.json +227 -91
  290. package/tasks/progress.txt +281 -87
  291. package/tasks/ralph.sh +113 -0
  292. package/integrations/basehub/README.md +0 -3
  293. package/layers/experiment/components/layout/header/index.tsx +0 -58
  294. package/layers/experiment/components/layout/navigation-menu.tsx +0 -127
  295. package/layers/experiment/lib/constants.ts +0 -12
  296. package/layers/webgl/app/page.tsx +0 -10
  297. package/layers/webgl/components/webgl/canvas/dynamic.tsx +0 -34
  298. package/layers/webgl/components/webgl/canvas/index.tsx +0 -43
  299. package/layers/webgl/components/webgl/components/scene/index.tsx +0 -21
  300. package/src/mergers/next-config-merger.js +0 -63
  301. /package/{src → packages/cli/src}/commands/setup-sanity.js +0 -0
  302. /package/{src → packages/cli/src}/commands/worktree.js +0 -0
  303. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/draft-mode/disable/route.ts +0 -0
  304. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/draft-mode/enable/route.ts +0 -0
  305. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/revalidate/route.ts +0 -0
  306. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/layout.tsx +0 -0
  307. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/sitemap.ts +0 -0
  308. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/studio/[[...tool]]/page.tsx +0 -0
  309. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/studio/layout.tsx +0 -0
  310. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/components/ui/sanity-image/index.tsx +0 -0
  311. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/README.md +0 -0
  312. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/check-integration.ts +0 -0
  313. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/README.md +0 -0
  314. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/client.ts +0 -0
  315. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/components/disable-draft-mode.tsx +0 -0
  316. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/components/rich-text.tsx +0 -0
  317. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/env.ts +0 -0
  318. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/live/index.tsx +0 -0
  319. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/queries.ts +0 -0
  320. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.cli.ts +0 -0
  321. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.config.ts +0 -0
  322. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.types.ts +0 -0
  323. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schema.json +0 -0
  324. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/article.ts +0 -0
  325. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/example.ts +0 -0
  326. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/index.ts +0 -0
  327. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/link.ts +0 -0
  328. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/metadata.ts +0 -0
  329. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/navigation.ts +0 -0
  330. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/page.ts +0 -0
  331. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/richText.ts +0 -0
  332. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/structure.ts +0 -0
  333. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/utils/image.ts +0 -0
  334. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/utils/link.ts +0 -0
  335. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/scripts/copy-sanity-mcp.ts +0 -0
  336. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/scripts/generate-page.ts +0 -0
  337. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/utils/metadata.ts +0 -0
  338. /package/{plugins → packages/create-basement-app/plugins}/README.md +0 -0
  339. /package/{plugins → packages/create-basement-app/plugins}/no-anchor-element.grit +0 -0
  340. /package/{plugins → packages/create-basement-app/plugins}/no-relative-parent-imports.grit +0 -0
  341. /package/{plugins → packages/create-basement-app/plugins}/no-unnecessary-forwardref.grit +0 -0
  342. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-battery.ts +0 -0
  343. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-device-perf.ts +0 -0
  344. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-intersection-observer.ts +0 -0
  345. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-media.ts +0 -0
  346. /package/{layers/webgpu → packages/create-basement-app/templates/default/lib/integrations}/.gitkeep +0 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Unit tests for math utilities
3
+ *
4
+ * Run with: bun test lib/utils/math.test.ts
5
+ */
6
+
7
+ import { describe, expect, it } from "bun:test"
8
+ import {
9
+ clamp,
10
+ degToRad,
11
+ distance,
12
+ lerp,
13
+ mapRange,
14
+ modulo,
15
+ normalize,
16
+ radToDeg,
17
+ roundTo,
18
+ truncate,
19
+ } from "./math"
20
+
21
+ describe("clamp", () => {
22
+ it("should return input when within bounds", () => {
23
+ expect(clamp(0, 50, 100)).toBe(50)
24
+ expect(clamp(-10, 0, 10)).toBe(0)
25
+ })
26
+
27
+ it("should return min when input is below min", () => {
28
+ expect(clamp(0, -5, 100)).toBe(0)
29
+ expect(clamp(10, 5, 20)).toBe(10)
30
+ })
31
+
32
+ it("should return max when input is above max", () => {
33
+ expect(clamp(0, 150, 100)).toBe(100)
34
+ expect(clamp(10, 25, 20)).toBe(20)
35
+ })
36
+
37
+ it("should handle edge cases", () => {
38
+ expect(clamp(0, 0, 100)).toBe(0)
39
+ expect(clamp(0, 100, 100)).toBe(100)
40
+ })
41
+ })
42
+
43
+ describe("lerp", () => {
44
+ it("should return start when amount is 0", () => {
45
+ expect(lerp(0, 100, 0)).toBe(0)
46
+ expect(lerp(-50, 50, 0)).toBe(-50)
47
+ })
48
+
49
+ it("should return end when amount is 1", () => {
50
+ expect(lerp(0, 100, 1)).toBe(100)
51
+ expect(lerp(-50, 50, 1)).toBe(50)
52
+ })
53
+
54
+ it("should return midpoint when amount is 0.5", () => {
55
+ expect(lerp(0, 100, 0.5)).toBe(50)
56
+ expect(lerp(-100, 100, 0.5)).toBe(0)
57
+ })
58
+
59
+ it("should handle intermediate values", () => {
60
+ expect(lerp(0, 100, 0.25)).toBe(25)
61
+ expect(lerp(0, 100, 0.75)).toBe(75)
62
+ })
63
+
64
+ it("should extrapolate beyond 0-1 range", () => {
65
+ expect(lerp(0, 100, 1.5)).toBe(150)
66
+ expect(lerp(0, 100, -0.5)).toBe(-50)
67
+ })
68
+ })
69
+
70
+ describe("mapRange", () => {
71
+ it("should map value from one range to another", () => {
72
+ expect(mapRange(0, 100, 50, 0, 1)).toBe(0.5)
73
+ expect(mapRange(0, 1000, 500, 0, 1)).toBe(0.5)
74
+ })
75
+
76
+ it("should handle inverted output ranges", () => {
77
+ expect(mapRange(0, 100, 50, 1, 0)).toBe(0.5)
78
+ expect(mapRange(0, 100, 0, 1, 0)).toBe(1)
79
+ expect(mapRange(0, 100, 100, 1, 0)).toBe(0)
80
+ })
81
+
82
+ it("should not clamp by default", () => {
83
+ expect(mapRange(0, 100, 150, 0, 1)).toBe(1.5)
84
+ expect(mapRange(0, 100, -50, 0, 1)).toBe(-0.5)
85
+ })
86
+
87
+ it("should clamp when shouldClamp is true", () => {
88
+ expect(mapRange(0, 100, 150, 0, 1, true)).toBe(1)
89
+ expect(mapRange(0, 100, -50, 0, 1, true)).toBe(0)
90
+ })
91
+
92
+ it("should clamp inverted ranges correctly", () => {
93
+ expect(mapRange(0, 100, 150, 1, 0, true)).toBe(0)
94
+ expect(mapRange(0, 100, -50, 1, 0, true)).toBe(1)
95
+ })
96
+ })
97
+
98
+ describe("truncate", () => {
99
+ it("should truncate to specified decimal places", () => {
100
+ expect(truncate(1.23456789, 2)).toBe(1.23)
101
+ expect(truncate(1.23456789, 3)).toBe(1.235)
102
+ expect(truncate(1.23456789, 4)).toBe(1.2346)
103
+ })
104
+
105
+ it("should handle 0 decimal places", () => {
106
+ expect(truncate(1.23456789, 0)).toBe(1)
107
+ expect(truncate(3.9, 0)).toBe(4)
108
+ })
109
+
110
+ it("should handle already truncated numbers", () => {
111
+ expect(truncate(3.14, 2)).toBe(3.14)
112
+ expect(truncate(5, 2)).toBe(5)
113
+ })
114
+ })
115
+
116
+ describe("modulo", () => {
117
+ it("should work like JavaScript % for positive numbers", () => {
118
+ expect(modulo(5, 3)).toBe(2)
119
+ expect(modulo(10, 4)).toBe(2)
120
+ })
121
+
122
+ it("should handle negative dividends correctly (true modulo)", () => {
123
+ expect(modulo(-1, 3)).toBe(2)
124
+ expect(modulo(-5, 3)).toBe(1)
125
+ expect(modulo(-10, 4)).toBe(2)
126
+ })
127
+
128
+ it("should return input when divisor is 0", () => {
129
+ expect(modulo(5, 0)).toBe(5)
130
+ expect(modulo(-5, 0)).toBe(-5)
131
+ })
132
+
133
+ it("should return NaN for negative divisors", () => {
134
+ expect(modulo(5, -3)).toBeNaN()
135
+ })
136
+
137
+ it("should wrap array indices correctly", () => {
138
+ const length = 5
139
+ expect(modulo(-1, length)).toBe(4)
140
+ expect(modulo(5, length)).toBe(0)
141
+ expect(modulo(7, length)).toBe(2)
142
+ })
143
+ })
144
+
145
+ describe("roundTo", () => {
146
+ it("should round to the nearest multiple", () => {
147
+ expect(roundTo(23, 10)).toBe(20)
148
+ expect(roundTo(27, 10)).toBe(30)
149
+ expect(roundTo(25, 10)).toBe(30)
150
+ })
151
+
152
+ it("should handle decimal multiples", () => {
153
+ expect(roundTo(0.23, 0.1)).toBeCloseTo(0.2, 10)
154
+ expect(roundTo(0.27, 0.1)).toBeCloseTo(0.3, 10)
155
+ })
156
+
157
+ it("should handle exact multiples", () => {
158
+ expect(roundTo(20, 10)).toBe(20)
159
+ expect(roundTo(0.5, 0.5)).toBe(0.5)
160
+ })
161
+ })
162
+
163
+ describe("degToRad", () => {
164
+ it("should convert degrees to radians", () => {
165
+ expect(degToRad(0)).toBe(0)
166
+ expect(degToRad(180)).toBeCloseTo(Math.PI, 10)
167
+ expect(degToRad(90)).toBeCloseTo(Math.PI / 2, 10)
168
+ expect(degToRad(360)).toBeCloseTo(Math.PI * 2, 10)
169
+ })
170
+
171
+ it("should handle negative degrees", () => {
172
+ expect(degToRad(-90)).toBeCloseTo(-Math.PI / 2, 10)
173
+ })
174
+ })
175
+
176
+ describe("radToDeg", () => {
177
+ it("should convert radians to degrees", () => {
178
+ expect(radToDeg(0)).toBe(0)
179
+ expect(radToDeg(Math.PI)).toBeCloseTo(180, 10)
180
+ expect(radToDeg(Math.PI / 2)).toBeCloseTo(90, 10)
181
+ expect(radToDeg(Math.PI * 2)).toBeCloseTo(360, 10)
182
+ })
183
+
184
+ it("should handle negative radians", () => {
185
+ expect(radToDeg(-Math.PI / 2)).toBeCloseTo(-90, 10)
186
+ })
187
+ })
188
+
189
+ describe("distance", () => {
190
+ it("should calculate distance between two points", () => {
191
+ expect(distance(0, 0, 3, 4)).toBe(5)
192
+ expect(distance(0, 0, 0, 0)).toBe(0)
193
+ })
194
+
195
+ it("should handle negative coordinates", () => {
196
+ expect(distance(-3, -4, 0, 0)).toBe(5)
197
+ expect(distance(-1, -1, 1, 1)).toBeCloseTo(Math.sqrt(8), 10)
198
+ })
199
+
200
+ it("should be symmetric", () => {
201
+ expect(distance(0, 0, 3, 4)).toBe(distance(3, 4, 0, 0))
202
+ })
203
+ })
204
+
205
+ describe("normalize", () => {
206
+ it("should normalize value to 0-1 range", () => {
207
+ expect(normalize(0, 100, 50)).toBe(0.5)
208
+ expect(normalize(0, 100, 0)).toBe(0)
209
+ expect(normalize(0, 100, 100)).toBe(1)
210
+ })
211
+
212
+ it("should handle non-zero min values", () => {
213
+ expect(normalize(50, 100, 75)).toBe(0.5)
214
+ expect(normalize(-100, 100, 0)).toBe(0.5)
215
+ })
216
+
217
+ it("should extrapolate outside range", () => {
218
+ expect(normalize(0, 100, 150)).toBe(1.5)
219
+ expect(normalize(0, 100, -50)).toBe(-0.5)
220
+ })
221
+ })
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Math Utilities
3
+ *
4
+ * Pure mathematical functions with no side effects.
5
+ * These are the building blocks for animations, layouts, and data transformations.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { clamp, lerp, mapRange } from '@/utils/math'
10
+ *
11
+ * // Constrain a value
12
+ * const bounded = clamp(0, value, 100)
13
+ *
14
+ * // Interpolate between values
15
+ * const mid = lerp(0, 100, 0.5) // 50
16
+ *
17
+ * // Map scroll position to opacity
18
+ * const opacity = mapRange(0, 500, scrollY, 0, 1, true)
19
+ * ```
20
+ */
21
+
22
+ /**
23
+ * Constrains a value between a minimum and maximum.
24
+ *
25
+ * @param min - Lower bound
26
+ * @param input - Value to constrain
27
+ * @param max - Upper bound
28
+ * @returns The clamped value
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * clamp(0, -5, 100) // 0
33
+ * clamp(0, 50, 100) // 50
34
+ * clamp(0, 150, 100) // 100
35
+ * ```
36
+ */
37
+ export function clamp(min: number, input: number, max: number): number {
38
+ return Math.max(min, Math.min(input, max))
39
+ }
40
+
41
+ /**
42
+ * Linear interpolation between two values.
43
+ *
44
+ * @param start - Starting value
45
+ * @param end - Ending value
46
+ * @param amount - Interpolation factor (0 = start, 1 = end)
47
+ * @returns The interpolated value
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * lerp(0, 100, 0) // 0
52
+ * lerp(0, 100, 0.5) // 50
53
+ * lerp(0, 100, 1) // 100
54
+ * lerp(0, 100, 0.25) // 25
55
+ * ```
56
+ */
57
+ export function lerp(start: number, end: number, amount: number): number {
58
+ return (1 - amount) * start + amount * end
59
+ }
60
+
61
+ /**
62
+ * Maps a value from one range to another.
63
+ *
64
+ * @param inMin - Input range minimum
65
+ * @param inMax - Input range maximum
66
+ * @param input - Value to map
67
+ * @param outMin - Output range minimum
68
+ * @param outMax - Output range maximum
69
+ * @param shouldClamp - Whether to clamp output to range (default: false)
70
+ * @returns The mapped value
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * // Map scroll (0-1000) to opacity (0-1)
75
+ * mapRange(0, 1000, 500, 0, 1) // 0.5
76
+ *
77
+ * // Map with clamping (won't exceed bounds)
78
+ * mapRange(0, 100, 150, 0, 1, true) // 1
79
+ *
80
+ * // Inverted ranges work too
81
+ * mapRange(0, 100, 50, 1, 0) // 0.5
82
+ * ```
83
+ */
84
+ export function mapRange(
85
+ inMin: number,
86
+ inMax: number,
87
+ input: number,
88
+ outMin: number,
89
+ outMax: number,
90
+ shouldClamp = false
91
+ ): number {
92
+ const result =
93
+ ((input - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin
94
+
95
+ if (!shouldClamp) return result
96
+
97
+ const isInverted = outMin > outMax
98
+ return isInverted
99
+ ? clamp(outMax, result, outMin)
100
+ : clamp(outMin, result, outMax)
101
+ }
102
+
103
+ /**
104
+ * Truncates a number to a specified number of decimal places.
105
+ *
106
+ * @param value - Number to truncate
107
+ * @param decimals - Number of decimal places
108
+ * @returns The truncated number
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * truncate(3.14159, 2) // 3.14
113
+ * truncate(3.14159, 0) // 3
114
+ * ```
115
+ */
116
+ export function truncate(value: number, decimals: number): number {
117
+ return Number.parseFloat(value.toFixed(decimals))
118
+ }
119
+
120
+ /**
121
+ * True modulo operation (handles negative numbers correctly).
122
+ *
123
+ * JavaScript's % operator is remainder, not modulo.
124
+ * This function returns a value in [0, d) for positive divisors.
125
+ *
126
+ * @param n - Dividend
127
+ * @param d - Divisor (must be positive)
128
+ * @returns The modulo result
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * // JavaScript remainder vs true modulo
133
+ * -1 % 3 // -1 (remainder)
134
+ * modulo(-1, 3) // 2 (modulo)
135
+ *
136
+ * // Useful for wrapping array indices
137
+ * modulo(index - 1, array.length)
138
+ * ```
139
+ */
140
+ export function modulo(n: number, d: number): number {
141
+ if (d === 0) return n
142
+ if (d < 0) return Number.NaN
143
+ return ((n % d) + d) % d
144
+ }
145
+
146
+ /**
147
+ * Rounds a number to the nearest multiple.
148
+ *
149
+ * @param value - Number to round
150
+ * @param multiple - Multiple to round to
151
+ * @returns The rounded value
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * roundTo(23, 10) // 20
156
+ * roundTo(27, 10) // 30
157
+ * roundTo(0.23, 0.1) // 0.2
158
+ * ```
159
+ */
160
+ export function roundTo(value: number, multiple: number): number {
161
+ return Math.round(value / multiple) * multiple
162
+ }
163
+
164
+ /**
165
+ * Converts degrees to radians.
166
+ *
167
+ * @param degrees - Angle in degrees
168
+ * @returns Angle in radians
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * degToRad(180) // Math.PI
173
+ * degToRad(90) // Math.PI / 2
174
+ * ```
175
+ */
176
+ export function degToRad(degrees: number): number {
177
+ return (degrees * Math.PI) / 180
178
+ }
179
+
180
+ /**
181
+ * Converts radians to degrees.
182
+ *
183
+ * @param radians - Angle in radians
184
+ * @returns Angle in degrees
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * radToDeg(Math.PI) // 180
189
+ * radToDeg(Math.PI / 2) // 90
190
+ * ```
191
+ */
192
+ export function radToDeg(radians: number): number {
193
+ return (radians * 180) / Math.PI
194
+ }
195
+
196
+ /**
197
+ * Calculates the distance between two 2D points.
198
+ *
199
+ * @param x1 - First point X
200
+ * @param y1 - First point Y
201
+ * @param x2 - Second point X
202
+ * @param y2 - Second point Y
203
+ * @returns The distance
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * distance(0, 0, 3, 4) // 5
208
+ * ```
209
+ */
210
+ export function distance(
211
+ x1: number,
212
+ y1: number,
213
+ x2: number,
214
+ y2: number
215
+ ): number {
216
+ return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
217
+ }
218
+
219
+ /**
220
+ * Normalizes a value to a 0-1 range.
221
+ *
222
+ * @param min - Range minimum
223
+ * @param max - Range maximum
224
+ * @param value - Value to normalize
225
+ * @returns Normalized value (0-1)
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * normalize(0, 100, 50) // 0.5
230
+ * normalize(0, 100, 0) // 0
231
+ * normalize(0, 100, 100) // 1
232
+ * ```
233
+ */
234
+ export function normalize(min: number, max: number, value: number): number {
235
+ return (value - min) / (max - min)
236
+ }
@@ -0,0 +1,126 @@
1
+ import type { Metadata } from "next"
2
+
3
+ /**
4
+ * Metadata Generation Utilities
5
+ *
6
+ * Helpers to generate consistent metadata across pages,
7
+ * reducing duplication and ensuring SEO best practices.
8
+ */
9
+
10
+ interface GenerateMetadataOptions {
11
+ title?: string
12
+ description?: string
13
+ keywords?: string[]
14
+ image?: {
15
+ url?: string
16
+ width?: number
17
+ height?: number
18
+ alt?: string
19
+ }
20
+ url?: string
21
+ siteName?: string
22
+ noIndex?: boolean
23
+ type?: "website" | "article"
24
+ publishedTime?: string
25
+ modifiedTime?: string
26
+ authors?: string[]
27
+ }
28
+
29
+ const APP_BASE_URL =
30
+ process.env.NEXT_PUBLIC_BASE_URL ?? "https://localhost:3000"
31
+
32
+ /**
33
+ * Generate complete metadata object for pages
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * export async function generateMetadata({ params }) {
38
+ * const page = await fetchPage(params.slug)
39
+ *
40
+ * return generatePageMetadata({
41
+ * title: page.metadata?.title || page.title,
42
+ * description: page.metadata?.description,
43
+ * image: { url: page.metadata?.image?.asset?.url },
44
+ * url: `/page/${params.slug}`,
45
+ * noIndex: page.metadata?.noIndex,
46
+ * })
47
+ * }
48
+ * ```
49
+ */
50
+ export function generatePageMetadata(
51
+ options: GenerateMetadataOptions
52
+ ): Metadata {
53
+ const {
54
+ title,
55
+ description,
56
+ keywords,
57
+ image,
58
+ url,
59
+ siteName = "Satūs",
60
+ noIndex = false,
61
+ type = "website",
62
+ publishedTime,
63
+ modifiedTime,
64
+ authors,
65
+ } = options
66
+
67
+ const fullUrl = url ? `${APP_BASE_URL}${url}` : APP_BASE_URL
68
+ const imageUrl = image?.url || "/opengraph-image.jpg"
69
+ const imageWidth = image?.width || 1200
70
+ const imageHeight = image?.height || 630
71
+ const imageAlt = image?.alt || title || siteName
72
+
73
+ const metadata: Metadata = {
74
+ metadataBase: new URL(APP_BASE_URL),
75
+ title,
76
+ description,
77
+ keywords,
78
+ alternates: {
79
+ canonical: url || "/",
80
+ },
81
+ openGraph: {
82
+ title,
83
+ description,
84
+ url: fullUrl,
85
+ siteName,
86
+ locale: "en_US",
87
+ type,
88
+ images: [
89
+ {
90
+ url: imageUrl,
91
+ width: imageWidth,
92
+ height: imageHeight,
93
+ alt: imageAlt,
94
+ },
95
+ ],
96
+ ...(publishedTime && { publishedTime }),
97
+ ...(modifiedTime && { modifiedTime }),
98
+ ...(authors && { authors }),
99
+ },
100
+ twitter: {
101
+ card: "summary_large_image",
102
+ title,
103
+ description,
104
+ images: [
105
+ {
106
+ url: imageUrl,
107
+ width: imageWidth,
108
+ height: imageHeight,
109
+ alt: imageAlt,
110
+ },
111
+ ],
112
+ },
113
+ other: {
114
+ "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
115
+ },
116
+ }
117
+
118
+ if (noIndex) {
119
+ metadata.robots = {
120
+ index: false,
121
+ follow: false,
122
+ }
123
+ }
124
+
125
+ return metadata
126
+ }