@wildo-ai/saas-website 1.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 (383) hide show
  1. package/LICENSE +34 -0
  2. package/dist/esm/.builder.pid +9 -0
  3. package/dist/esm/astro/blog-post-bridge.d.ts +324 -0
  4. package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -0
  5. package/dist/esm/astro/blog-post-bridge.js +57 -0
  6. package/dist/esm/astro/blog-post-bridge.js.map +1 -0
  7. package/dist/esm/astro/blog-post-head.renderer.d.ts +129 -0
  8. package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -0
  9. package/dist/esm/astro/blog-post-head.renderer.js +139 -0
  10. package/dist/esm/astro/blog-post-head.renderer.js.map +1 -0
  11. package/dist/esm/astro/bridge-runtime.d.ts +224 -0
  12. package/dist/esm/astro/bridge-runtime.d.ts.map +1 -0
  13. package/dist/esm/astro/bridge-runtime.js +30 -0
  14. package/dist/esm/astro/bridge-runtime.js.map +1 -0
  15. package/dist/esm/astro/collect-expected-label-keys.d.ts +65 -0
  16. package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -0
  17. package/dist/esm/astro/collect-expected-label-keys.js +101 -0
  18. package/dist/esm/astro/collect-expected-label-keys.js.map +1 -0
  19. package/dist/esm/astro/headers-renderer.d.ts +32 -0
  20. package/dist/esm/astro/headers-renderer.d.ts.map +1 -0
  21. package/dist/esm/astro/headers-renderer.js +64 -0
  22. package/dist/esm/astro/headers-renderer.js.map +1 -0
  23. package/dist/esm/astro/i18n-routing.helper.d.ts +91 -0
  24. package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -0
  25. package/dist/esm/astro/i18n-routing.helper.js +23 -0
  26. package/dist/esm/astro/i18n-routing.helper.js.map +1 -0
  27. package/dist/esm/astro/internal/html-escape.d.ts +31 -0
  28. package/dist/esm/astro/internal/html-escape.d.ts.map +1 -0
  29. package/dist/esm/astro/internal/html-escape.js +40 -0
  30. package/dist/esm/astro/internal/html-escape.js.map +1 -0
  31. package/dist/esm/astro/label-pack-loader.d.ts +134 -0
  32. package/dist/esm/astro/label-pack-loader.d.ts.map +1 -0
  33. package/dist/esm/astro/label-pack-loader.js +298 -0
  34. package/dist/esm/astro/label-pack-loader.js.map +1 -0
  35. package/dist/esm/astro/llms-txt-server.d.ts +28 -0
  36. package/dist/esm/astro/llms-txt-server.d.ts.map +1 -0
  37. package/dist/esm/astro/llms-txt-server.js +33 -0
  38. package/dist/esm/astro/llms-txt-server.js.map +1 -0
  39. package/dist/esm/astro/pick-labels-for-page.d.ts +63 -0
  40. package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -0
  41. package/dist/esm/astro/pick-labels-for-page.js +73 -0
  42. package/dist/esm/astro/pick-labels-for-page.js.map +1 -0
  43. package/dist/esm/astro/robots-renderer.d.ts +85 -0
  44. package/dist/esm/astro/robots-renderer.d.ts.map +1 -0
  45. package/dist/esm/astro/robots-renderer.js +157 -0
  46. package/dist/esm/astro/robots-renderer.js.map +1 -0
  47. package/dist/esm/astro/sitemap-coverage.d.ts +101 -0
  48. package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -0
  49. package/dist/esm/astro/sitemap-coverage.js +67 -0
  50. package/dist/esm/astro/sitemap-coverage.js.map +1 -0
  51. package/dist/esm/astro/structured-data.renderer.d.ts +21 -0
  52. package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -0
  53. package/dist/esm/astro/structured-data.renderer.js +142 -0
  54. package/dist/esm/astro/structured-data.renderer.js.map +1 -0
  55. package/dist/esm/astro/website-page-head.renderer.d.ts +93 -0
  56. package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -0
  57. package/dist/esm/astro/website-page-head.renderer.js +184 -0
  58. package/dist/esm/astro/website-page-head.renderer.js.map +1 -0
  59. package/dist/esm/astro/website-page-runtime.helper.d.ts +112 -0
  60. package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -0
  61. package/dist/esm/astro/website-page-runtime.helper.js +60 -0
  62. package/dist/esm/astro/website-page-runtime.helper.js.map +1 -0
  63. package/dist/esm/astro/website-site-context.d.ts +126 -0
  64. package/dist/esm/astro/website-site-context.d.ts.map +1 -0
  65. package/dist/esm/astro/website-site-context.js +37 -0
  66. package/dist/esm/astro/website-site-context.js.map +1 -0
  67. package/dist/esm/astro-island.d.ts +56 -0
  68. package/dist/esm/astro-island.d.ts.map +1 -0
  69. package/dist/esm/astro-island.js +61 -0
  70. package/dist/esm/astro-island.js.map +1 -0
  71. package/dist/esm/astro.d.ts +151 -0
  72. package/dist/esm/astro.d.ts.map +1 -0
  73. package/dist/esm/astro.js +151 -0
  74. package/dist/esm/astro.js.map +1 -0
  75. package/dist/esm/companion-exports.d.ts +38 -0
  76. package/dist/esm/companion-exports.d.ts.map +1 -0
  77. package/dist/esm/companion-exports.js +38 -0
  78. package/dist/esm/companion-exports.js.map +1 -0
  79. package/dist/esm/components/low-level/WebsiteButton.d.ts +73 -0
  80. package/dist/esm/components/low-level/WebsiteButton.d.ts.map +1 -0
  81. package/dist/esm/components/low-level/WebsiteButton.js +68 -0
  82. package/dist/esm/components/low-level/WebsiteButton.js.map +1 -0
  83. package/dist/esm/components/low-level/WebsiteHeading.d.ts +78 -0
  84. package/dist/esm/components/low-level/WebsiteHeading.d.ts.map +1 -0
  85. package/dist/esm/components/low-level/WebsiteHeading.js +41 -0
  86. package/dist/esm/components/low-level/WebsiteHeading.js.map +1 -0
  87. package/dist/esm/components/low-level/WebsiteImage.d.ts +73 -0
  88. package/dist/esm/components/low-level/WebsiteImage.d.ts.map +1 -0
  89. package/dist/esm/components/low-level/WebsiteImage.js +21 -0
  90. package/dist/esm/components/low-level/WebsiteImage.js.map +1 -0
  91. package/dist/esm/components/low-level/WebsiteInternalButton.d.ts +90 -0
  92. package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -0
  93. package/dist/esm/components/low-level/WebsiteInternalButton.js +47 -0
  94. package/dist/esm/components/low-level/WebsiteInternalButton.js.map +1 -0
  95. package/dist/esm/components/low-level/WebsiteInternalLink.d.ts +99 -0
  96. package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -0
  97. package/dist/esm/components/low-level/WebsiteInternalLink.js +68 -0
  98. package/dist/esm/components/low-level/WebsiteInternalLink.js.map +1 -0
  99. package/dist/esm/components/low-level/WebsiteLink.d.ts +50 -0
  100. package/dist/esm/components/low-level/WebsiteLink.d.ts.map +1 -0
  101. package/dist/esm/components/low-level/WebsiteLink.js +22 -0
  102. package/dist/esm/components/low-level/WebsiteLink.js.map +1 -0
  103. package/dist/esm/components/low-level/WebsiteText.d.ts +57 -0
  104. package/dist/esm/components/low-level/WebsiteText.d.ts.map +1 -0
  105. package/dist/esm/components/low-level/WebsiteText.js +57 -0
  106. package/dist/esm/components/low-level/WebsiteText.js.map +1 -0
  107. package/dist/esm/config/define-website-config.d.ts +83 -0
  108. package/dist/esm/config/define-website-config.d.ts.map +1 -0
  109. package/dist/esm/config/define-website-config.js +126 -0
  110. package/dist/esm/config/define-website-config.js.map +1 -0
  111. package/dist/esm/config/index.d.ts +38 -0
  112. package/dist/esm/config/index.d.ts.map +1 -0
  113. package/dist/esm/config/index.js +38 -0
  114. package/dist/esm/config/index.js.map +1 -0
  115. package/dist/esm/config/load-website-config.d.ts +136 -0
  116. package/dist/esm/config/load-website-config.d.ts.map +1 -0
  117. package/dist/esm/config/load-website-config.js +177 -0
  118. package/dist/esm/config/load-website-config.js.map +1 -0
  119. package/dist/esm/config/wildo-website-config.schemas.d.ts +251 -0
  120. package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -0
  121. package/dist/esm/config/wildo-website-config.schemas.js +302 -0
  122. package/dist/esm/config/wildo-website-config.schemas.js.map +1 -0
  123. package/dist/esm/config-loader.d.ts +35 -0
  124. package/dist/esm/config-loader.d.ts.map +1 -0
  125. package/dist/esm/config-loader.js +35 -0
  126. package/dist/esm/config-loader.js.map +1 -0
  127. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +38 -0
  128. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -0
  129. package/dist/esm/core/anonymous-session/InboundContactForm.js +77 -0
  130. package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -0
  131. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +41 -0
  132. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -0
  133. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +58 -0
  134. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -0
  135. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +81 -0
  136. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -0
  137. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +177 -0
  138. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -0
  139. package/dist/esm/core/contexts/WebsitePageContext.d.ts +40 -0
  140. package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -0
  141. package/dist/esm/core/contexts/WebsitePageContext.js +8 -0
  142. package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -0
  143. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +162 -0
  144. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -0
  145. package/dist/esm/core/contexts/WebsiteRuntimeContext.js +22 -0
  146. package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -0
  147. package/dist/esm/core/contexts/WebsiteSectionContext.d.ts +47 -0
  148. package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -0
  149. package/dist/esm/core/contexts/WebsiteSectionContext.js +8 -0
  150. package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -0
  151. package/dist/esm/core/contexts/useWebsitePage.d.ts +13 -0
  152. package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -0
  153. package/dist/esm/core/contexts/useWebsitePage.js +22 -0
  154. package/dist/esm/core/contexts/useWebsitePage.js.map +1 -0
  155. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +19 -0
  156. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -0
  157. package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -0
  158. package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -0
  159. package/dist/esm/core/contexts/useWebsiteSection.d.ts +12 -0
  160. package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -0
  161. package/dist/esm/core/contexts/useWebsiteSection.js +22 -0
  162. package/dist/esm/core/contexts/useWebsiteSection.js.map +1 -0
  163. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +17 -0
  164. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -0
  165. package/dist/esm/core/external-providers/frontend-provider-registry.website.js +23 -0
  166. package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -0
  167. package/dist/esm/core/factories/define-website-page-manifest.d.ts +70 -0
  168. package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -0
  169. package/dist/esm/core/factories/define-website-page-manifest.js +51 -0
  170. package/dist/esm/core/factories/define-website-page-manifest.js.map +1 -0
  171. package/dist/esm/core/factories/define-website-section.d.ts +101 -0
  172. package/dist/esm/core/factories/define-website-section.d.ts.map +1 -0
  173. package/dist/esm/core/factories/define-website-section.js +71 -0
  174. package/dist/esm/core/factories/define-website-section.js.map +1 -0
  175. package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts +23 -0
  176. package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -0
  177. package/dist/esm/core/hooks/useWebsiteDesignTokens.js +25 -0
  178. package/dist/esm/core/hooks/useWebsiteDesignTokens.js.map +1 -0
  179. package/dist/esm/core/hooks/useWebsiteLabel.d.ts +68 -0
  180. package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -0
  181. package/dist/esm/core/hooks/useWebsiteLabel.js +103 -0
  182. package/dist/esm/core/hooks/useWebsiteLabel.js.map +1 -0
  183. package/dist/esm/core/layouts/WebsitePageLayout.d.ts +87 -0
  184. package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -0
  185. package/dist/esm/core/layouts/WebsitePageLayout.js +170 -0
  186. package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -0
  187. package/dist/esm/core/layouts/WebsiteSection.d.ts +91 -0
  188. package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -0
  189. package/dist/esm/core/layouts/WebsiteSection.js +26 -0
  190. package/dist/esm/core/layouts/WebsiteSection.js.map +1 -0
  191. package/dist/esm/core/routing/internal-routing.utils.d.ts +65 -0
  192. package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -0
  193. package/dist/esm/core/routing/internal-routing.utils.js +14 -0
  194. package/dist/esm/core/routing/internal-routing.utils.js.map +1 -0
  195. package/dist/esm/index.d.ts +129 -0
  196. package/dist/esm/index.d.ts.map +1 -0
  197. package/dist/esm/index.js +129 -0
  198. package/dist/esm/index.js.map +1 -0
  199. package/dist/esm/mdx/BlogPost.d.ts +193 -0
  200. package/dist/esm/mdx/BlogPost.d.ts.map +1 -0
  201. package/dist/esm/mdx/BlogPost.js +161 -0
  202. package/dist/esm/mdx/BlogPost.js.map +1 -0
  203. package/dist/esm/mdx/blog-post-collection.config.d.ts +139 -0
  204. package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -0
  205. package/dist/esm/mdx/blog-post-collection.config.js +27 -0
  206. package/dist/esm/mdx/blog-post-collection.config.js.map +1 -0
  207. package/dist/esm/mdx/blog-post-frontmatter-source.d.ts +12 -0
  208. package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -0
  209. package/dist/esm/mdx/blog-post-frontmatter-source.js +13 -0
  210. package/dist/esm/mdx/blog-post-frontmatter-source.js.map +1 -0
  211. package/dist/esm/mdx/index.d.ts +22 -0
  212. package/dist/esm/mdx/index.d.ts.map +1 -0
  213. package/dist/esm/mdx/index.js +22 -0
  214. package/dist/esm/mdx/index.js.map +1 -0
  215. package/dist/esm/mdx/website-mdx-provider.d.ts +90 -0
  216. package/dist/esm/mdx/website-mdx-provider.d.ts.map +1 -0
  217. package/dist/esm/mdx/website-mdx-provider.js +8 -0
  218. package/dist/esm/mdx/website-mdx-provider.js.map +1 -0
  219. package/dist/esm/mdx.d.ts +38 -0
  220. package/dist/esm/mdx.d.ts.map +1 -0
  221. package/dist/esm/mdx.js +38 -0
  222. package/dist/esm/mdx.js.map +1 -0
  223. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts +75 -0
  224. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -0
  225. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js +85 -0
  226. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js.map +1 -0
  227. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +120 -0
  228. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -0
  229. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +152 -0
  230. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -0
  231. package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts +73 -0
  232. package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -0
  233. package/dist/esm/schemas/label-keys/website-label-key.schemas.js +76 -0
  234. package/dist/esm/schemas/label-keys/website-label-key.schemas.js.map +1 -0
  235. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +209 -0
  236. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -0
  237. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +218 -0
  238. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -0
  239. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +431 -0
  240. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -0
  241. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +438 -0
  242. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -0
  243. package/dist/esm/schemas/refs/page-ref.schemas.d.ts +40 -0
  244. package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -0
  245. package/dist/esm/schemas/refs/page-ref.schemas.js +43 -0
  246. package/dist/esm/schemas/refs/page-ref.schemas.js.map +1 -0
  247. package/dist/esm/schemas/refs/section-ref.schemas.d.ts +41 -0
  248. package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -0
  249. package/dist/esm/schemas/refs/section-ref.schemas.js +44 -0
  250. package/dist/esm/schemas/refs/section-ref.schemas.js.map +1 -0
  251. package/dist/esm/schemas/sections/website-section-category.shared.d.ts +63 -0
  252. package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -0
  253. package/dist/esm/schemas/sections/website-section-category.shared.js +64 -0
  254. package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -0
  255. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +114 -0
  256. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -0
  257. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +69 -0
  258. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -0
  259. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +100 -0
  260. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -0
  261. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +355 -0
  262. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -0
  263. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +58 -0
  264. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -0
  265. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js +2 -0
  266. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -0
  267. package/dist/esm/schemas/validators/label-pack.validator.d.ts +144 -0
  268. package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -0
  269. package/dist/esm/schemas/validators/label-pack.validator.js +100 -0
  270. package/dist/esm/schemas/validators/label-pack.validator.js.map +1 -0
  271. package/dist/tsconfig.build.tsbuildinfo +1 -0
  272. package/package.json +143 -0
  273. package/src/__tests__/bundle-isolation.test.ts +607 -0
  274. package/src/astro/__tests__/blog-post-bridge.test.tsx +288 -0
  275. package/src/astro/__tests__/blog-post-head.renderer.test.ts +249 -0
  276. package/src/astro/__tests__/bridge-runtime.test.tsx +198 -0
  277. package/src/astro/__tests__/collect-expected-label-keys.test.ts +69 -0
  278. package/src/astro/__tests__/headers-renderer.test.ts +143 -0
  279. package/src/astro/__tests__/i18n-routing.helper.test.ts +116 -0
  280. package/src/astro/__tests__/label-pack-loader.test.ts +305 -0
  281. package/src/astro/__tests__/llms-txt-server.test.ts +127 -0
  282. package/src/astro/__tests__/pick-labels-for-page.test.ts +60 -0
  283. package/src/astro/__tests__/robots-renderer.test.ts +228 -0
  284. package/src/astro/__tests__/sitemap-coverage.test.ts +230 -0
  285. package/src/astro/__tests__/structured-data.renderer.test.ts +172 -0
  286. package/src/astro/__tests__/website-page-head.renderer.test.ts +424 -0
  287. package/src/astro/__tests__/website-page-runtime.helper.test.ts +157 -0
  288. package/src/astro/__tests__/website-site-context.test.ts +121 -0
  289. package/src/astro/blog-post-bridge.tsx +404 -0
  290. package/src/astro/blog-post-head.renderer.ts +293 -0
  291. package/src/astro/bridge-runtime.tsx +270 -0
  292. package/src/astro/collect-expected-label-keys.ts +113 -0
  293. package/src/astro/headers-renderer.ts +82 -0
  294. package/src/astro/i18n-routing.helper.ts +116 -0
  295. package/src/astro/internal/html-escape.ts +41 -0
  296. package/src/astro/label-pack-loader.ts +385 -0
  297. package/src/astro/llms-txt-server.ts +56 -0
  298. package/src/astro/pick-labels-for-page.ts +85 -0
  299. package/src/astro/robots-renderer.ts +171 -0
  300. package/src/astro/sitemap-coverage.ts +186 -0
  301. package/src/astro/structured-data.renderer.ts +171 -0
  302. package/src/astro/website-page-head.renderer.ts +279 -0
  303. package/src/astro/website-page-runtime.helper.ts +188 -0
  304. package/src/astro/website-site-context.ts +136 -0
  305. package/src/astro-island.ts +61 -0
  306. package/src/astro.ts +151 -0
  307. package/src/companion-exports.ts +38 -0
  308. package/src/components/low-level/WebsiteButton.tsx +118 -0
  309. package/src/components/low-level/WebsiteHeading.tsx +96 -0
  310. package/src/components/low-level/WebsiteImage.tsx +96 -0
  311. package/src/components/low-level/WebsiteInternalButton.tsx +172 -0
  312. package/src/components/low-level/WebsiteInternalLink.tsx +193 -0
  313. package/src/components/low-level/WebsiteLink.tsx +71 -0
  314. package/src/components/low-level/WebsiteText.tsx +75 -0
  315. package/src/components/low-level/__tests__/WebsiteInternalButton.test.tsx +169 -0
  316. package/src/components/low-level/__tests__/WebsiteInternalLink.test.tsx +202 -0
  317. package/src/components/low-level/__tests__/primitives.test.tsx +141 -0
  318. package/src/config/__tests__/define-website-config.test.ts +231 -0
  319. package/src/config/__tests__/load-website-config.test.ts +215 -0
  320. package/src/config/define-website-config.ts +134 -0
  321. package/src/config/index.ts +38 -0
  322. package/src/config/load-website-config.ts +229 -0
  323. package/src/config/wildo-website-config.schemas.ts +322 -0
  324. package/src/config-loader.ts +35 -0
  325. package/src/core/__tests__/WebsitePageLayout.test.tsx +184 -0
  326. package/src/core/__tests__/WebsiteSection.test.tsx +85 -0
  327. package/src/core/__tests__/contexts.test.tsx +133 -0
  328. package/src/core/__tests__/core-boundary.test.ts +85 -0
  329. package/src/core/__tests__/define-website-page-manifest.test.tsx +111 -0
  330. package/src/core/__tests__/define-website-section.test.tsx +89 -0
  331. package/src/core/__tests__/useWebsiteDesignTokens.test.tsx +41 -0
  332. package/src/core/__tests__/useWebsiteLabel.test.tsx +117 -0
  333. package/src/core/anonymous-session/InboundContactForm.tsx +157 -0
  334. package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +44 -0
  335. package/src/core/anonymous-session/inbound-contact-form.schema.ts +62 -0
  336. package/src/core/anonymous-session/website-anonymous-session-client.ts +194 -0
  337. package/src/core/contexts/WebsitePageContext.tsx +52 -0
  338. package/src/core/contexts/WebsiteRuntimeContext.tsx +175 -0
  339. package/src/core/contexts/WebsiteSectionContext.tsx +59 -0
  340. package/src/core/contexts/useWebsitePage.ts +28 -0
  341. package/src/core/contexts/useWebsiteRuntime.ts +34 -0
  342. package/src/core/contexts/useWebsiteSection.ts +28 -0
  343. package/src/core/external-providers/frontend-provider-registry.website.ts +62 -0
  344. package/src/core/factories/define-website-page-manifest.ts +102 -0
  345. package/src/core/factories/define-website-section.ts +128 -0
  346. package/src/core/hooks/useWebsiteDesignTokens.ts +26 -0
  347. package/src/core/hooks/useWebsiteLabel.ts +121 -0
  348. package/src/core/layouts/WebsitePageLayout.tsx +311 -0
  349. package/src/core/layouts/WebsiteSection.tsx +139 -0
  350. package/src/core/routing/__tests__/internal-routing.utils.test.ts +134 -0
  351. package/src/core/routing/internal-routing.utils.ts +77 -0
  352. package/src/index.ts +132 -0
  353. package/src/mdx/BlogPost.tsx +296 -0
  354. package/src/mdx/__tests__/BlogPost.test.tsx +218 -0
  355. package/src/mdx/__tests__/blog-post-collection.config.test.ts +127 -0
  356. package/src/mdx/__tests__/website-mdx-provider.test.tsx +82 -0
  357. package/src/mdx/blog-post-collection.config.ts +177 -0
  358. package/src/mdx/blog-post-frontmatter-source.ts +31 -0
  359. package/src/mdx/index.ts +39 -0
  360. package/src/mdx/website-mdx-provider.tsx +95 -0
  361. package/src/mdx.ts +38 -0
  362. package/src/schemas/__tests__/blog-frontmatter.test.ts +50 -0
  363. package/src/schemas/__tests__/companion-exports-react-free.test.ts +51 -0
  364. package/src/schemas/__tests__/design-tokens.test.ts +163 -0
  365. package/src/schemas/__tests__/label-key.test.ts +46 -0
  366. package/src/schemas/__tests__/label-pack-validator.test.ts +146 -0
  367. package/src/schemas/__tests__/page-manifest.test.ts +66 -0
  368. package/src/schemas/__tests__/refs.test.ts +37 -0
  369. package/src/schemas/__tests__/root-config.test.ts +292 -0
  370. package/src/schemas/__tests__/section-definition.test.ts +47 -0
  371. package/src/schemas/__tests__/structured-data-reconciliation.test.ts +543 -0
  372. package/src/schemas/blog/blog-post-frontmatter.shared.schemas.ts +111 -0
  373. package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +166 -0
  374. package/src/schemas/label-keys/website-label-key.schemas.ts +88 -0
  375. package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +266 -0
  376. package/src/schemas/manifests/website-root-config.shared.schemas.ts +504 -0
  377. package/src/schemas/refs/page-ref.schemas.ts +49 -0
  378. package/src/schemas/refs/section-ref.schemas.ts +50 -0
  379. package/src/schemas/sections/website-section-category.shared.ts +65 -0
  380. package/src/schemas/sections/website-section-definition.shared.schemas.ts +120 -0
  381. package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +579 -0
  382. package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +62 -0
  383. package/src/schemas/validators/label-pack.validator.ts +201 -0
package/src/astro.ts ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @wildo-package @wildo-ai/saas-website (astro adapter SSR/frontmatter entry)
3
+ *
4
+ * Astro **frontmatter / SSR** sub-entry. Re-exports the TypeScript
5
+ * helpers a consumer's per-page `.astro` files consume INSIDE
6
+ * frontmatter to render a marketing-site page (locale resolution,
7
+ * label-pack loading, `<head>` HTML rendering, design-token CSS
8
+ * rendering, i18n routing config, expected-label-key collection).
9
+ *
10
+ * **The `createWebsitePageBridgeIsland` factory lives in a SEPARATE
11
+ * subpath — `@wildo-ai/saas-website/astro-island`** — and is NOT
12
+ * re-exported here. The split exists because the consumer's per-page
13
+ * `*.bridge.tsx` module is bundled into BOTH the SSR graph (via the
14
+ * `.astro` page that statically imports it) AND the client graph
15
+ * (Astro's hydration pipeline). Re-exporting the bridge factory
16
+ * alongside the Node-using helpers here would force Vite to resolve
17
+ * `node:fs` / `node:path` for the client bundle even though
18
+ * tree-shaking would later drop them — resolution happens BEFORE
19
+ * tree-shaking, so the build hard-fails with
20
+ * `"resolve" is not exported by "__vite-browser-external"`. The
21
+ * dedicated `astro-island` subpath keeps the client import graph
22
+ * 100% bundle-clean.
23
+ *
24
+ * **No `.astro` shell shipped.** The Phase-5 redesign deliberately
25
+ * removed the previously-planned `WebsitePageBridge.astro` shell
26
+ * because:
27
+ * 1. A `.astro` file cannot be re-exported from a TS subpath barrel
28
+ * and `tsc` does not copy non-TS files to `dist/`, so the shell
29
+ * was unreachable from `@wildo-ai/saas-website/astro`.
30
+ * 2. The shell would have had to pass `pageComponent` /
31
+ * `headerComponent` / `footerComponent` (`ComponentType` refs)
32
+ * as React-island props — Astro's `devalue`-style island prop
33
+ * serialization cannot transport function values, so hydration
34
+ * would have mismatched on first interaction.
35
+ * Both failure modes are sidestepped by giving consumers two
36
+ * primitive-string-only TS helpers (`renderWebsitePageHeadHtml`,
37
+ * `renderWebsiteDesignTokensCss`) for the Astro-side `<head>` work
38
+ * and a closure-based island factory (`createWebsitePageBridgeIsland`,
39
+ * exported from `@wildo-ai/saas-website/astro-island`) for the
40
+ * React-island side. The consumer authors a thin per-page `.astro`
41
+ * file that statically imports the per-page bridge island — Astro's
42
+ * compiler then resolves the `client:*` directive against a static
43
+ * module export, which is the only pattern that makes Astro's
44
+ * hydration bundling work correctly. See `bridge-runtime.tsx`'s
45
+ * file-level JSDoc for the full rationale.
46
+ *
47
+ * @wildo-boundary
48
+ * This entry is on the SSR / Astro-build-time side of the bundle
49
+ * boundary (D12 second loosening). It MAY import:
50
+ * - `node:fs`, `node:path` (label-pack-loader, runtime-helper)
51
+ * It MUST NOT import:
52
+ * - the bridge-runtime module — that module ships into the
53
+ * client island bundle and is exposed via the
54
+ * `@wildo-ai/saas-website/astro-island` subpath instead.
55
+ * - the Astro runtime (`astro` package, `astro:*` virtual modules)
56
+ * — those live exclusively inside `.astro` files. Adding
57
+ * `astro` here would make the engine package unimportable
58
+ * from any context that does not boot the Astro compiler
59
+ * (Vitest tests, the `wildo website audit` CLI, companion
60
+ * subprocesses).
61
+ *
62
+ * Enforced by `engine/saas-website/src/__tests__/bundle-isolation.test.ts`
63
+ * which walks `astro.ts` as one of `ENTRY_FILES` AND runs a targeted
64
+ * negative test that `node:fs` / `node:path` are reachable from
65
+ * `astro.ts` ONLY — never from `index.ts`, `companion-exports.ts`,
66
+ * or `astro-island.ts`.
67
+ */
68
+
69
+ export * from './astro/website-site-context';
70
+ export * from './astro/i18n-routing.helper';
71
+ export * from './astro/collect-expected-label-keys';
72
+ export * from './astro/label-pack-loader';
73
+ /**
74
+ * `pick-labels-for-page` is INTENTIONALLY NOT re-exported here.
75
+ * It is an internal helper consumed only by `loadWebsitePageRuntime`
76
+ * (which IS exported via the runtime helper below). Exposing it as a
77
+ * public sub-entry surface would (a) invite consumers to call it
78
+ * directly from `.astro` frontmatter — which they should never do
79
+ * (the runtime helper already wires the slicing transparently), and
80
+ * (b) lock the engine into an extra public API the audit step would
81
+ * have to keep stable. Tests import the helper via its relative
82
+ * path, which keeps the behavior covered without leaking the surface.
83
+ * (Phase 6 audit fix M-B.)
84
+ */
85
+ export * from './astro/website-page-runtime.helper';
86
+ export * from './astro/website-page-head.renderer';
87
+ export * from './astro/structured-data.renderer';
88
+ /**
89
+ * Phase 7 — sibling head renderer for blog posts. Reads from a
90
+ * validated `BlogPostFrontmatter` instead of the page manifest's
91
+ * `metaLabelKeys`; emits `og:type=article` plus the
92
+ * `article:published_time` / `article:author` / `article:tag` lines
93
+ * the OG "article" vertical requires. See the renderer file's
94
+ * preamble for why this is a sibling rather than a flag on
95
+ * `renderWebsitePageHeadHtml`.
96
+ */
97
+ export * from './astro/blog-post-head.renderer';
98
+ /**
99
+ * Phase 8 close-out — `renderRobotsTxt(siteContext)` pure string
100
+ * builder for the marketing site's `robots.txt` body. Sibling of
101
+ * `renderWebsitePageHeadHtml` (same Astro-frontmatter consumption
102
+ * shape: pure TypeScript, no Astro runtime dependency, vitest-
103
+ * friendly). See `engine/saas-website/src/astro/robots-renderer.ts`
104
+ * for the field-by-field rationale and
105
+ * `.cursor/plans/saas-website-seo-sitemap.md` for the slice context.
106
+ */
107
+ export * from './astro/robots-renderer';
108
+ /**
109
+ * Phase 8 close-out — `enumerateMarketingPageSitemapUrls(siteContext)`
110
+ * pure helper that returns every (page-manifest × locale) absolute
111
+ * URL the marketing site's sitemap MUST advertise, paired with its
112
+ * hreflang alternates. Two consumption surfaces:
113
+ *
114
+ * 1. Vitest enforcement — a regression that drops a manifest-
115
+ * registered route fails fast in unit tests, well before
116
+ * `astro build` would surface it.
117
+ * 2. `@astrojs/sitemap` `customPages` integration — when the
118
+ * consumer needs to surface routes Astro's file-based router
119
+ * cannot auto-discover (data-driven landing variants, etc.),
120
+ * this helper computes the canonical URL list to spread into
121
+ * the plugin config.
122
+ *
123
+ * See `engine/saas-website/src/astro/sitemap-coverage.ts` and
124
+ * `.cursor/plans/saas-website-seo-sitemap.md` for the full
125
+ * rationale + scope boundary.
126
+ */
127
+ export * from './astro/sitemap-coverage';
128
+ /**
129
+ * AEO — `renderPageStructuredData(opts)` pure string builder that
130
+ * collects every section's `getStructuredData()` (when present),
131
+ * dedupes by `@id`, and emits one
132
+ * `<script type="application/ld+json">` block per surviving payload.
133
+ * Sibling of `renderWebsitePageHeadHtml` (same pure-TS, vitest-
134
+ * friendly contract). See
135
+ * `engine/saas-website/src/astro/structured-data.renderer.ts`
136
+ * for the safety + dedupe semantics and
137
+ * `.cursor/plans/saas-website-aeo-geo.md` for the slice context.
138
+ */
139
+ export * from './astro/structured-data.renderer';
140
+ export * from './astro/headers-renderer';
141
+ export * from './astro/llms-txt-server';
142
+ /**
143
+ * AEO — runtime payload type (`WebsiteStructuredDataPayload`) and
144
+ * convenience return-shape union (`WebsiteStructuredDataReturn`)
145
+ * the section author imports to type their `getStructuredData()`.
146
+ * Defined locally in `saas-website` (not in `saas-specifications`)
147
+ * because the runtime engine cannot depend on the spec layer; the
148
+ * companion catalog at `_catalogs/schema-org/` describes the same
149
+ * vocabulary at the COMPANION layer.
150
+ */
151
+ export * from './schemas/structured-data/website-structured-data.shared.schemas';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @wildo-package @wildo-ai/saas-website (companion entry)
3
+ *
4
+ * Narrow, React-free entry point for non-React consumers:
5
+ * - the `wildo-cli`'s page/sitemap/structured-data audit commands
6
+ * - companion subprocesses (e.g. introspection of authored pages)
7
+ * - CI scripts that validate label-pack coverage
8
+ * - anything Astro-build-time that cannot pull React into the
9
+ * evaluation graph
10
+ *
11
+ * Per `saas-website-slice.md` §"Note on `companion-exports.ts` scope
12
+ * expansion": the SEO builder library lives here too (it's pure
13
+ * string + JSON-LD generation, React-free by design).
14
+ *
15
+ * @wildo-boundary
16
+ * This file MUST re-export ONLY from `schemas/**` and `seo/**`.
17
+ * It MUST NOT import or re-export anything from:
18
+ * - `core/**` (React runtime contexts/hooks)
19
+ * - `astro/**` (Astro-only adapter)
20
+ * - `mdx/**` (MDX-only adapter)
21
+ * - `integration/**` (React-bearing integration code)
22
+ * It MUST NOT import React, Astro, or `@mdx-js/*`.
23
+ *
24
+ * Enforced by review until Phase 8 boundary lint design (see plan).
25
+ *
26
+ * Phase 1 status: schemas exposed; SEO builders land in Phase 2.5.
27
+ */
28
+
29
+ export * from './schemas/refs/page-ref.schemas';
30
+ export * from './schemas/refs/section-ref.schemas';
31
+ export * from './schemas/label-keys/website-label-key.schemas';
32
+ export * from './schemas/sections/website-section-category.shared';
33
+ export * from './schemas/sections/website-section-definition.shared.schemas';
34
+ export * from './schemas/design-tokens/website-design-tokens.shared.schemas';
35
+ export * from './schemas/manifests/website-page-manifest.shared.schemas';
36
+ export * from './schemas/manifests/website-root-config.shared.schemas';
37
+ export * from './schemas/blog/blog-post-frontmatter.shared.schemas';
38
+ export * from './schemas/validators/label-pack.validator';
@@ -0,0 +1,118 @@
1
+ import React, { type CSSProperties, type ReactNode } from 'react';
2
+
3
+ import { WebsiteLink } from './WebsiteLink';
4
+
5
+ /**
6
+ * @wildo_source:part:start saas.website.primitives.button facet:layer:components facet:family:website
7
+ *
8
+ * `WebsiteButton` — L1 marketing-site CTA / button primitive.
9
+ *
10
+ * **Two render modes** (selected by the presence of `href`):
11
+ * - `href` supplied => renders as a `<WebsiteLink>` (anchor)
12
+ * with button styling. This is the dominant marketing-site
13
+ * case (CTAs link to `/signup`, `/contact`, `#pricing`).
14
+ * - `href` omitted => renders a real `<button type="…">` for
15
+ * interactive sections (eg. a "Subscribe" form button on the
16
+ * contact page that opens an inbound-contact dialog).
17
+ *
18
+ * **Why a single primitive** (vs. separate `<WebsiteCta>` +
19
+ * `<WebsiteButton>`): the visual identity is the same, and the
20
+ * rendered tag is a low-level concern decided per call site. A
21
+ * single primitive with two modes keeps the section-author API
22
+ * narrow (one import, one prop set) while preserving correct
23
+ * semantic HTML at each call site.
24
+ *
25
+ * **Variant vocabulary**: kept minimal at the placeholder level
26
+ * (`primary` / `secondary`). The override implementation typically
27
+ * extends with `tertiary`, `ghost`, `destructive`, etc. — adding
28
+ * variants here would lock the placeholder vocabulary against the
29
+ * override's richer set.
30
+ */
31
+ export enum WebsiteButtonVariant {
32
+ PRIMARY = 'primary',
33
+ SECONDARY = 'secondary',
34
+ }
35
+
36
+ export enum WebsiteButtonSize {
37
+ SM = 'sm',
38
+ MD = 'md',
39
+ LG = 'lg',
40
+ }
41
+
42
+ interface WebsiteButtonCommonProps {
43
+ variant?: WebsiteButtonVariant;
44
+ size?: WebsiteButtonSize;
45
+ className?: string;
46
+ style?: CSSProperties;
47
+ id?: string;
48
+ ariaLabel?: string;
49
+ children: ReactNode;
50
+ }
51
+
52
+ interface WebsiteButtonAsLinkProps extends WebsiteButtonCommonProps {
53
+ href: string;
54
+ external?: boolean;
55
+ type?: never;
56
+ onClick?: never;
57
+ disabled?: never;
58
+ }
59
+
60
+ interface WebsiteButtonAsButtonProps extends WebsiteButtonCommonProps {
61
+ href?: never;
62
+ external?: never;
63
+ type?: 'button' | 'submit' | 'reset';
64
+ onClick?: () => void;
65
+ disabled?: boolean;
66
+ }
67
+
68
+ /**
69
+ * Discriminated union of "anchor-shaped" vs. "button-shaped" props.
70
+ * Either supply `href` (renders an anchor with link semantics) OR
71
+ * supply `type` / `onClick` / `disabled` (renders a real button) —
72
+ * mixing the two is rejected at compile time.
73
+ */
74
+ export type WebsiteButtonProps = WebsiteButtonAsLinkProps | WebsiteButtonAsButtonProps;
75
+
76
+ export function WebsiteButton(props: WebsiteButtonProps): ReactNode {
77
+ const variant = props.variant ?? WebsiteButtonVariant.PRIMARY;
78
+ const size = props.size ?? WebsiteButtonSize.MD;
79
+
80
+ if ('href' in props && props.href !== undefined) {
81
+ return (
82
+ <WebsiteLink
83
+ href={props.href}
84
+ external={props.external}
85
+ className={props.className}
86
+ style={props.style}
87
+ id={props.id}
88
+ ariaLabel={props.ariaLabel}
89
+ >
90
+ <span
91
+ data-website-primitive="button"
92
+ data-website-button-variant={variant}
93
+ data-website-button-size={size}
94
+ data-website-button-mode="link"
95
+ >
96
+ {props.children}
97
+ </span>
98
+ </WebsiteLink>
99
+ );
100
+ }
101
+
102
+ const attrs: Record<string, unknown> = {
103
+ 'data-website-primitive': 'button',
104
+ 'data-website-button-variant': variant,
105
+ 'data-website-button-size': size,
106
+ 'data-website-button-mode': 'button',
107
+ type: props.type ?? 'button',
108
+ };
109
+ if (props.className !== undefined) attrs.className = props.className;
110
+ if (props.style !== undefined) attrs.style = props.style;
111
+ if (props.id !== undefined) attrs.id = props.id;
112
+ if (props.ariaLabel !== undefined) attrs['aria-label'] = props.ariaLabel;
113
+ if (props.disabled !== undefined) attrs.disabled = props.disabled;
114
+ if (props.onClick !== undefined) attrs.onClick = props.onClick;
115
+ return <button {...attrs}>{props.children}</button>;
116
+ }
117
+ WebsiteButton.displayName = 'WebsiteButton';
118
+ /** @wildo_source:part:end saas.website.primitives.button */
@@ -0,0 +1,96 @@
1
+ import React, { type CSSProperties, type ReactNode } from 'react';
2
+
3
+ /**
4
+ * @wildo_source:part:start saas.website.primitives.heading facet:layer:components facet:family:website
5
+ *
6
+ * `WebsiteHeading` — L1 marketing-site heading primitive.
7
+ *
8
+ * **Status (Phase 2 / D18 — option D)**: minimal, self-contained
9
+ * placeholder. The framework defaults are intentionally bare so
10
+ * consumers MUST override at the L1 boundary by barrel-re-exporting
11
+ * a styled variant from their app's
12
+ * `app/website/primitives/WebsiteHeading.tsx`. The Phase 6
13
+ * dogfood will validate the override workflow and (deferred to
14
+ * Phase 6) decide whether to promote these to the
15
+ * `ComponentRegistryService` registry shared with `saas-frontend-lib`.
16
+ *
17
+ * **Why these primitives ship at all** (vs. asking consumers to
18
+ * author every primitive themselves): consistency of the section
19
+ * authoring surface. Section authors call `<WebsiteHeading
20
+ * level={1}>` from `defineWebsiteSection`'d components without
21
+ * having to know which app they're rendering in — the override
22
+ * happens at the consumer barrel level transparently.
23
+ *
24
+ * **Why `level` is a discrete prop (1–6)**: heading hierarchy is
25
+ * critical for SEO and accessibility, and a free-string `as` prop
26
+ * makes it trivial for a copy-paste section to render `<h2>`s where
27
+ * the page outline expects `<h1>`. Constraining `level` to a numeric
28
+ * union keeps the section author honest about the document outline.
29
+ *
30
+ * **Why `variant` (display vs. headline) is shipped here**:
31
+ * marketing-site headings need a visual scale that's distinct from
32
+ * the in-app heading scale (display sizes for hero, headline sizes
33
+ * for section titles). Even as a placeholder, the variant slot
34
+ * exists so section authors don't have to invent the prop ad-hoc;
35
+ * the override implementation can deliver the actual visual scale.
36
+ */
37
+ export type WebsiteHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
38
+
39
+ export enum WebsiteHeadingVariant {
40
+ /**
41
+ * Hero / above-the-fold sizes. Maps to the largest type scale in
42
+ * the consumer's design system. Use for one-per-page copy.
43
+ */
44
+ DISPLAY = 'display',
45
+ /**
46
+ * Section / sub-section titles. The default — most marketing-site
47
+ * headings are headline-scale, not display-scale.
48
+ */
49
+ HEADLINE = 'headline',
50
+ /**
51
+ * In-section group titles (eg. pricing tier names, FAQ question
52
+ * cards). Smaller than headline, structurally a heading.
53
+ */
54
+ TITLE = 'title',
55
+ }
56
+
57
+ export interface WebsiteHeadingProps {
58
+ /** Required heading level (1–6). Drives the rendered tag. */
59
+ level: WebsiteHeadingLevel;
60
+ /** Visual variant. Defaults to `HEADLINE` (the most common case). */
61
+ variant?: WebsiteHeadingVariant;
62
+ /** Forwarded to the heading element. */
63
+ className?: string;
64
+ /** Forwarded to the heading element for one-off inline tweaks. */
65
+ style?: CSSProperties;
66
+ /** Forwarded id (for anchor links to specific headings). */
67
+ id?: string;
68
+ children: ReactNode;
69
+ }
70
+
71
+ /**
72
+ * Minimal placeholder. Renders the correct semantic tag with no
73
+ * visual styling beyond a `data-website-primitive="heading"`
74
+ * attribute that the consumer's tailwind config / global CSS / the
75
+ * override implementation can hook into.
76
+ */
77
+ export function WebsiteHeading({
78
+ level,
79
+ variant = WebsiteHeadingVariant.HEADLINE,
80
+ className,
81
+ style,
82
+ id,
83
+ children,
84
+ }: WebsiteHeadingProps): ReactNode {
85
+ const Tag = `h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
86
+ const attrs: Record<string, unknown> = {
87
+ 'data-website-primitive': 'heading',
88
+ 'data-website-heading-variant': variant,
89
+ };
90
+ if (className !== undefined) attrs.className = className;
91
+ if (style !== undefined) attrs.style = style;
92
+ if (id !== undefined) attrs.id = id;
93
+ return <Tag {...attrs}>{children}</Tag>;
94
+ }
95
+ WebsiteHeading.displayName = 'WebsiteHeading';
96
+ /** @wildo_source:part:end saas.website.primitives.heading */
@@ -0,0 +1,96 @@
1
+ import React, { type CSSProperties, type ReactNode } from 'react';
2
+
3
+ /**
4
+ * @wildo_source:part:start saas.website.primitives.image facet:layer:components facet:family:website
5
+ *
6
+ * `WebsiteImage` — L1 marketing-site image primitive.
7
+ *
8
+ * **Status (Phase 2 / D18)**: minimal placeholder around a plain
9
+ * `<img>` with sensible accessibility / performance defaults
10
+ * (`alt` is REQUIRED, `loading` defaults to `'lazy'`, `decoding`
11
+ * defaults to `'async'`). Production sites override at the L1
12
+ * boundary to wire in Astro's `<Image>` component (or a Next/CDN
13
+ * variant) for automatic format / width / DPR handling.
14
+ *
15
+ * **Why `alt` is REQUIRED at the type level**: marketing imagery
16
+ * is a public-facing surface where missing alt text directly hurts
17
+ * SEO scoring AND blocks screen-reader access. Forcing the section
18
+ * author to author an `alt` (even an empty `''` for purely
19
+ * decorative images) makes the accessibility decision explicit at
20
+ * the call site.
21
+ *
22
+ * **Why `width` / `height` are REQUIRED**: cumulative-layout-shift
23
+ * is a Core Web Vital that marketing pages are routinely scored
24
+ * against. Without intrinsic dimensions, every image on the page
25
+ * triggers layout shift on load. Forcing the dimensions at the call
26
+ * site keeps CLS near zero by default — the override implementation
27
+ * can swap to Astro's `<Image>` (which encodes the same dimensions
28
+ * differently) without changing the section-author API.
29
+ *
30
+ * **Why no `priority` / `fetchpriority` prop here**: Astro's
31
+ * `<Image>` exposes its own `loading="eager"` / `priority` props
32
+ * with implementation-specific semantics, and a placeholder-level
33
+ * `priority` would be either a lie (does nothing without the Astro
34
+ * adapter) or a footgun (forces the override to honor a contract
35
+ * it can't always meet). The override implementation is the right
36
+ * place to expose this.
37
+ */
38
+ export interface WebsiteImageProps {
39
+ src: string;
40
+ /**
41
+ * REQUIRED. Use `alt=""` (empty string) for purely decorative
42
+ * images — `null` / `undefined` is rejected at compile time so
43
+ * the accessibility decision is always explicit.
44
+ */
45
+ alt: string;
46
+ /**
47
+ * REQUIRED. Intrinsic image width in CSS pixels (eg. `1200`).
48
+ */
49
+ width: number;
50
+ /**
51
+ * REQUIRED. Intrinsic image height in CSS pixels (eg. `630`).
52
+ */
53
+ height: number;
54
+ /**
55
+ * `'lazy'` (default) defers loading until the image is near the
56
+ * viewport. Set to `'eager'` for above-the-fold hero images.
57
+ */
58
+ loading?: 'lazy' | 'eager';
59
+ /**
60
+ * `'async'` (default) lets the browser decode the image off the
61
+ * main thread. Set to `'sync'` only for above-the-fold hero
62
+ * images that need to paint immediately.
63
+ */
64
+ decoding?: 'async' | 'sync' | 'auto';
65
+ className?: string;
66
+ style?: CSSProperties;
67
+ id?: string;
68
+ }
69
+
70
+ export function WebsiteImage({
71
+ src,
72
+ alt,
73
+ width,
74
+ height,
75
+ loading = 'lazy',
76
+ decoding = 'async',
77
+ className,
78
+ style,
79
+ id,
80
+ }: WebsiteImageProps): ReactNode {
81
+ const attrs: Record<string, unknown> = {
82
+ 'data-website-primitive': 'image',
83
+ src,
84
+ alt,
85
+ width,
86
+ height,
87
+ loading,
88
+ decoding,
89
+ };
90
+ if (className !== undefined) attrs.className = className;
91
+ if (style !== undefined) attrs.style = style;
92
+ if (id !== undefined) attrs.id = id;
93
+ return <img {...attrs} />;
94
+ }
95
+ WebsiteImage.displayName = 'WebsiteImage';
96
+ /** @wildo_source:part:end saas.website.primitives.image */
@@ -0,0 +1,172 @@
1
+ import React, { type CSSProperties, type ReactNode } from 'react';
2
+
3
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
4
+
5
+ import { useWebsiteRuntime } from '../../core/contexts/useWebsiteRuntime';
6
+ import { buildLocalizedRoutePath } from '../../core/routing/internal-routing.utils';
7
+
8
+ import {
9
+ WebsiteButton,
10
+ type WebsiteButtonSize,
11
+ type WebsiteButtonVariant,
12
+ } from './WebsiteButton';
13
+
14
+ /**
15
+ * Pre-computed set of every known locale code; mirrors the guard in
16
+ * `WebsiteInternalLink` (kept duplicated rather than shared so each
17
+ * primitive's audit surface stays self-contained — the set is tiny).
18
+ */
19
+ const ALL_LOCALE_CODES: ReadonlySet<string> = new Set(
20
+ Object.values(AvailableLanguage) as readonly string[],
21
+ );
22
+
23
+ function detectAccidentalLocalePrefix(routePath: string): string | null {
24
+ const slashIdx = routePath.indexOf('/', 1);
25
+ const firstSegment =
26
+ slashIdx === -1 ? routePath.slice(1) : routePath.slice(1, slashIdx);
27
+ if (firstSegment === '') return null;
28
+ return ALL_LOCALE_CODES.has(firstSegment) ? firstSegment : null;
29
+ }
30
+
31
+ /**
32
+ * @wildo_source:part:start saas.website.primitives.internal-button facet:layer:components facet:family:website
33
+ *
34
+ * `WebsiteInternalButton` — L1 marketing-site CTA primitive for
35
+ * INTERNAL anchor-mode buttons (CTAs that navigate to another route
36
+ * served by the same Astro build, e.g. "Get in touch" → `/contact`).
37
+ *
38
+ * **The architectural problem this solves** (Phase 6 fourth audit fix
39
+ * H-Φ): the H-β fix shipped `WebsiteInternalLink` for header/footer
40
+ * navigation links but `WebsiteButton` consumers in section bodies
41
+ * (hero / features / pricing / contact) continued to write
42
+ * `<WebsiteButton href="/contact">` with raw locale-neutral paths. On
43
+ * `/fr/…` pages those CTAs navigated to the EN-default `/contact`
44
+ * URL — exactly the cross-locale leak H-β was meant to kill, just on
45
+ * the page body instead of the chrome. Authors had to know the
46
+ * routing rule and call `buildLocalizedRoutePath` manually on every
47
+ * CTA they shipped, and the audit confirmed they did not.
48
+ *
49
+ * **Why a parallel primitive** (mirrors the H-β reasoning, NOT a
50
+ * `routePath` flag on `WebsiteButton`):
51
+ * 1. The two primitives have different authoring contracts —
52
+ * `WebsiteButton`'s `href` is a fully-qualified URL the author
53
+ * already commits to (or an anchor `#section`, or external);
54
+ * this primitive's `routePath` is a raw locale-neutral path that
55
+ * the framework prefixes. Mixing both shapes behind one prop
56
+ * forces the author to think about a routing rule on every CTA
57
+ * they place; mixing them without a type-system signal invites
58
+ * the wrong shape on the wrong branch.
59
+ * 2. Internal CTAs REQUIRE the runtime context; external/anchor
60
+ * CTAs do not. Forcing every external-link `WebsiteButton`
61
+ * consumer to wrap in `<WebsiteRuntimeContextProvider>` is wrong.
62
+ * Two primitives keep the dependency surface honest.
63
+ * 3. Symmetric with `WebsiteInternalLink` — section authors learn
64
+ * one rule ("use the `Internal` variant for app-served paths,
65
+ * the bare variant for external/scheme/anchor URLs") and apply
66
+ * it identically to both link and button shapes.
67
+ *
68
+ * **Button-only render mode**: this primitive is anchor-mode-only by
69
+ * construction (every internal target is a route → an anchor element
70
+ * with appropriate href). Consumers who need a real `<button>` (no
71
+ * navigation, just an `onClick` handler) keep using `<WebsiteButton>`
72
+ * directly — there is no `routePath`-based real-button concept.
73
+ *
74
+ * **Anchor-only links** (`routePath="#features"`) and URL schemes
75
+ * (`mailto:`/`tel:`) are rejected at runtime: those targets do NOT
76
+ * benefit from locale prefixing, and surfacing them through this
77
+ * primitive would invite the SAME confusion that motivated splitting
78
+ * `WebsiteInternalLink` from `WebsiteLink` in the first place. Use
79
+ * `<WebsiteButton href="..." external={true}>` for those cases.
80
+ *
81
+ * **Boundary**: this component MUST stay in the React-island bundle
82
+ * tier (`components/low-level/`). It depends on `useWebsiteRuntime`
83
+ * (which throws when called outside a `<WebsiteRuntimeContextProvider>`)
84
+ * — the throw is the contract that prevents body CTAs from being
85
+ * rendered outside a bridged page accidentally.
86
+ *
87
+ * @example
88
+ * ```tsx
89
+ * // Hero CTA — auto-prefixes the active locale
90
+ * // (`/contact` on EN pages, `/fr/contact` on FR pages):
91
+ * <WebsiteInternalButton routePath="/contact" variant={WebsiteButtonVariant.PRIMARY}>
92
+ * {useWebsiteLabel('cta.primary')}
93
+ * </WebsiteInternalButton>
94
+ * ```
95
+ */
96
+ export interface WebsiteInternalButtonProps {
97
+ /**
98
+ * The locale-NEUTRAL route path. Must match a value the consumer's
99
+ * Astro build serves (typically a `WebsitePageManifest.routePath`).
100
+ * MUST start with `/` — anchor-only `#section` and URL schemes
101
+ * (`mailto:` / `tel:`) are rejected (use `<WebsiteButton href={...}>`
102
+ * for those cases).
103
+ */
104
+ routePath: string;
105
+ variant?: WebsiteButtonVariant;
106
+ size?: WebsiteButtonSize;
107
+ className?: string;
108
+ style?: CSSProperties;
109
+ id?: string;
110
+ ariaLabel?: string;
111
+ children: ReactNode;
112
+ }
113
+
114
+ export function WebsiteInternalButton({
115
+ routePath,
116
+ variant,
117
+ size,
118
+ className,
119
+ style,
120
+ id,
121
+ ariaLabel,
122
+ children,
123
+ }: WebsiteInternalButtonProps): ReactNode {
124
+ const runtime = useWebsiteRuntime();
125
+
126
+ if (!routePath.startsWith('/')) {
127
+ throw new Error(
128
+ `[WebsiteInternalButton] routePath="${routePath}" must start with "/" ` +
129
+ `(framework-relative path). Anchors ("#..."), URL schemes ` +
130
+ `("mailto:" / "tel:"), and fully-qualified external URLs are ` +
131
+ `NOT supported by this primitive — use <WebsiteButton href={...} external={true}> ` +
132
+ `(or omit \`external\` for in-app anchors and scheme links). ` +
133
+ `For NAVIGATION links (not styled as a CTA), see <WebsiteInternalLink routePath={...}>.`,
134
+ );
135
+ }
136
+
137
+ const accidentalPrefix = detectAccidentalLocalePrefix(routePath);
138
+ if (accidentalPrefix !== null) {
139
+ throw new Error(
140
+ `[WebsiteInternalButton] routePath="${routePath}" begins with the ` +
141
+ `locale segment "/${accidentalPrefix}" — the framework prefixes ` +
142
+ `the active locale automatically, so passing a pre-prefixed path ` +
143
+ `would render as "/${accidentalPrefix}${routePath}" on non-default ` +
144
+ `locale pages (double-prefix) or silently break locale routing on ` +
145
+ `default-locale pages. Pass the locale-NEUTRAL path instead ` +
146
+ `(eg. "/pricing", not "/${accidentalPrefix}/pricing").`,
147
+ );
148
+ }
149
+
150
+ const href = buildLocalizedRoutePath({
151
+ locale: runtime.locale,
152
+ defaultLocale: runtime.defaultLocale,
153
+ routePath,
154
+ });
155
+
156
+ return (
157
+ <WebsiteButton
158
+ href={href}
159
+ external={false}
160
+ variant={variant}
161
+ size={size}
162
+ className={className}
163
+ style={style}
164
+ id={id}
165
+ ariaLabel={ariaLabel}
166
+ >
167
+ {children}
168
+ </WebsiteButton>
169
+ );
170
+ }
171
+ WebsiteInternalButton.displayName = 'WebsiteInternalButton';
172
+ /** @wildo_source:part:end saas.website.primitives.internal-button */