@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
@@ -0,0 +1,121 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { useWebsitePage } from '../contexts/useWebsitePage';
4
+ import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';
5
+ import { useWebsiteSection } from '../contexts/useWebsiteSection';
6
+ import {
7
+ WebsiteLabelKeySchema,
8
+ composeWebsiteLabelKey,
9
+ type WebsiteLabelKey,
10
+ } from '../../schemas/label-keys/website-label-key.schemas';
11
+
12
+ /**
13
+ * @wildo_source:part:start saas.website.use-label.hook facet:layer:core facet:family:website
14
+ *
15
+ * `useWebsiteLabel(slotPath)` — leaf-relative label resolution.
16
+ *
17
+ * Composes the slot leaf with the active page+section context to
18
+ * derive the full `WebsiteLabelKey` (`website.{pageRef}.{sectionRef}.{slotPath}`),
19
+ * looks it up in the active locale's label pack, and returns the
20
+ * resolved string.
21
+ *
22
+ * **Author ergonomics**: section authors call
23
+ * `useWebsiteLabel('headline')` from inside their section component
24
+ * without ever typing the page or section ref — the hook reads them
25
+ * from context. This keeps section components portable across pages
26
+ * (the same `<HeroSection />` can be reused on `landing` and on a
27
+ * dedicated `/about` page; only the surrounding `<WebsiteSection
28
+ * sectionRef="hero">` differs, and the label keys re-root accordingly).
29
+ *
30
+ * **Fail-loud diagnostics** for missing keys: returns the composed key
31
+ * itself (visible in the rendered DOM as e.g. `website.landing.hero.headline`)
32
+ * AND emits a `console.warn`. The visible-key fallback is preferred
33
+ * over an empty string or a thrown error because:
34
+ * - empty string silently breaks layout (zero-height heading) and
35
+ * leaks past visual review,
36
+ * - throwing would crash the entire page on a single missing label
37
+ * while the SEO crawler indexes a 500 page,
38
+ * - the rendered key is a clear authoring signal ("ship the missing
39
+ * pack entry") that designers and copywriters can spot without
40
+ * opening DevTools.
41
+ *
42
+ * **Hard guards** (each enforced by the underlying context hooks):
43
+ * - MUST be called inside `<WebsiteRuntimeContextProvider>`,
44
+ * - MUST be called inside `<WebsitePageLayout>`,
45
+ * - MUST be called inside `<WebsiteSection>`.
46
+ *
47
+ * @param slotPath leaf identifier (eg. `'headline'`, `'cta.primary'`,
48
+ * `'tiers[0].name'`). Anything that survives the slot
49
+ * portion of `WebsiteLabelKeySchema`'s regex.
50
+ * @returns the resolved label string, or the composed key itself when
51
+ * the pack lacks an entry (with a `console.warn`).
52
+ */
53
+ export function useWebsiteLabel(slotPath: string): string {
54
+ const { labelPack } = useWebsiteRuntime();
55
+ const { pageRef } = useWebsitePage();
56
+ const { sectionRef } = useWebsiteSection();
57
+
58
+ /**
59
+ * Compose + parse memoized on the three identity inputs. The parse
60
+ * itself is cheap (regex + brand) but executing it on every render
61
+ * of every label-bound primitive in a section adds up across a
62
+ * 12-section landing page.
63
+ */
64
+ const fullKey = useMemo<WebsiteLabelKey>(
65
+ () =>
66
+ composeWebsiteLabelKey({
67
+ pageRef: pageRef as unknown as string,
68
+ sectionRef: sectionRef as unknown as string,
69
+ slotPath,
70
+ }),
71
+ [pageRef, sectionRef, slotPath],
72
+ );
73
+
74
+ const resolved = labelPack[fullKey as unknown as string];
75
+ if (resolved === undefined) {
76
+ console.warn(
77
+ `[useWebsiteLabel] Missing label for key "${fullKey as unknown as string}". ` +
78
+ `Add it to the active locale's pack (or remove the section's claim on this slot).`,
79
+ );
80
+ return fullKey as unknown as string;
81
+ }
82
+ return resolved;
83
+ }
84
+ /** @wildo_source:part:end saas.website.use-label.hook */
85
+
86
+ /**
87
+ * @wildo_source:part:start saas.website.use-label-by-key.hook facet:layer:core facet:family:website
88
+ *
89
+ * `useWebsiteLabelByKey(key)` — absolute key lookup.
90
+ *
91
+ * Use when the consumer already holds a fully-composed label key and
92
+ * does NOT need page+section composition. Two main call sites:
93
+ * - the navigation header/footer rendering page meta titles
94
+ * (`useWebsiteLabelByKey(manifest.metaLabelKeys.titleKey)`),
95
+ * - section components that accept a `*Key` prop from their parent
96
+ * page (`<HeroSection headlineKey="website.landing.hero.headline" />`)
97
+ * instead of using the leaf-relative hook.
98
+ *
99
+ * Same fail-loud diagnostics as `useWebsiteLabel`: returns the key
100
+ * string and emits a `console.warn` on miss.
101
+ *
102
+ * Defensively re-parses the key through `WebsiteLabelKeySchema` so a
103
+ * caller that bypassed the type system (eg. an `as WebsiteLabelKey`
104
+ * cast on a malformed string) gets caught at the hook boundary
105
+ * instead of silently looking up garbage.
106
+ */
107
+ export function useWebsiteLabelByKey(key: WebsiteLabelKey): string {
108
+ const { labelPack } = useWebsiteRuntime();
109
+
110
+ const safeKey = useMemo(() => WebsiteLabelKeySchema.parse(key), [key]);
111
+
112
+ const resolved = labelPack[safeKey as unknown as string];
113
+ if (resolved === undefined) {
114
+ console.warn(
115
+ `[useWebsiteLabelByKey] Missing label for key "${safeKey as unknown as string}".`,
116
+ );
117
+ return safeKey as unknown as string;
118
+ }
119
+ return resolved;
120
+ }
121
+ /** @wildo_source:part:end saas.website.use-label-by-key.hook */
@@ -0,0 +1,311 @@
1
+ import React, { useMemo, type ReactNode } from 'react';
2
+ import { BUILTIN_PROVIDER_CAPABILITY } from '@wildo-ai/saas-models/public-runtime';
3
+
4
+ import { WebsitePageContextProvider } from '../contexts/WebsitePageContext';
5
+ import type { WebsitePageContextValue } from '../contexts/WebsitePageContext';
6
+ import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';
7
+ import { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';
8
+ import {
9
+ WebsitePageRefSchema,
10
+ type WebsitePageRef,
11
+ } from '../../schemas/refs/page-ref.schemas';
12
+
13
+ /**
14
+ * Stable DOM id stamped on the `<main>` element so the framework's
15
+ * skip-link `<a href="#main-content">` can move keyboard focus past
16
+ * the chrome on every page (Phase 6 audit fix M-α). Co-located here
17
+ * (rather than exposed as a prop) because:
18
+ * - The skip-link target is a framework concern — exposing it as a
19
+ * consumer-tunable id would invite drift between the link's href
20
+ * and the main element's id.
21
+ * - Screen readers announce the heading inside `<main id="...">` on
22
+ * focus; a stable, framework-owned id keeps that affordance
23
+ * deterministic across every page on every site.
24
+ */
25
+ const MAIN_CONTENT_DOM_ID = 'main-content';
26
+
27
+ /**
28
+ * Inline style for the skip link's "visually hidden by default,
29
+ * visible on focus" pattern. Inlined (NOT shipped as a CSS class)
30
+ * because:
31
+ * - The framework intentionally avoids shipping any CSS file the
32
+ * consumer must opt in to — the only stylesheet emitted is the
33
+ * `:root { --website-*: … }` design-token block, and the rest of
34
+ * the styling surface lives in consumer code (see Phase 5
35
+ * `renderWebsiteDesignTokensCss`). Inlining keeps the skip link
36
+ * self-sufficient and side-effect-free.
37
+ * - The "skip-link" pattern is a small, well-known a11y idiom; the
38
+ * handful of style declarations here do not benefit from being
39
+ * reorganized into a class with a custom-property hook.
40
+ *
41
+ * The "focused" variant uses BOTH `:focus` AND `:focus-visible`
42
+ * pseudo-classes via a scoped `<style>` tag inside the layout output.
43
+ * We cannot apply pseudo-class styles via a `style` attribute, but
44
+ * emitting a tiny scoped block once at the layout root is safe (one
45
+ * block per page render, ~120 bytes) and avoids importing a CSS-in-JS
46
+ * runtime.
47
+ *
48
+ * **Why both selectors and not just `:focus-visible`** (Phase 6 fourth
49
+ * audit fix M-Η): `:focus-visible` is the modern UA-heuristic-driven
50
+ * selector that hides focus rings on mouse clicks, and it is the
51
+ * RIGHT default for buttons / links. For a skip link though we WANT
52
+ * the affordance to appear on EVERY focus event — the link is
53
+ * invisible by default and a keyboard-only user (the only intended
54
+ * audience) is the only one who can reach it. Older browsers that
55
+ * predate `:focus-visible` (and Safari < 15.4 with the feature
56
+ * disabled) would skip the rule entirely with a `:focus-visible`-only
57
+ * declaration, leaving the keyboard user looking at an apparently
58
+ * empty page when they pressed Tab. Pairing `:focus, :focus-visible`
59
+ * is the documented WCAG 2.1 fallback pattern.
60
+ */
61
+ const SKIP_LINK_HIDDEN_STYLE: React.CSSProperties = {
62
+ position: 'absolute',
63
+ top: 0,
64
+ left: 0,
65
+ width: '1px',
66
+ height: '1px',
67
+ padding: 0,
68
+ margin: '-1px',
69
+ overflow: 'hidden',
70
+ clip: 'rect(0 0 0 0)',
71
+ whiteSpace: 'nowrap',
72
+ border: 0,
73
+ };
74
+
75
+ /**
76
+ * Single scoped style block for the skip link's `:focus-visible`
77
+ * affordance. Targets the `data-website-skip-link` attribute (NOT a
78
+ * class) so consumer Tailwind / utility classes cannot accidentally
79
+ * shadow it. Stays trivially small to keep payload under the
80
+ * "framework rendering" implicit budget.
81
+ */
82
+ const SKIP_LINK_FOCUS_CSS = `
83
+ [data-website-skip-link]:focus,
84
+ [data-website-skip-link]:focus-visible {
85
+ position: fixed !important;
86
+ top: 0.5rem !important;
87
+ left: 0.5rem !important;
88
+ width: auto !important;
89
+ height: auto !important;
90
+ padding: 0.75rem 1rem !important;
91
+ margin: 0 !important;
92
+ overflow: visible !important;
93
+ clip: auto !important;
94
+ white-space: normal !important;
95
+ background: var(--website-colors-background, #fff) !important;
96
+ color: var(--website-colors-foreground, #000) !important;
97
+ border: 2px solid var(--website-colors-primary, #000) !important;
98
+ border-radius: var(--website-radii-md, 0.375rem) !important;
99
+ z-index: 9999 !important;
100
+ text-decoration: none !important;
101
+ }
102
+ `.trim();
103
+
104
+ /**
105
+ * @wildo_source:part:start saas.website.page-layout.component facet:layer:core facet:family:website
106
+ *
107
+ * `WebsitePageLayout` — the per-page structural primitive.
108
+ *
109
+ * **Three responsibilities** (each carrying its own semantic weight):
110
+ *
111
+ * 1. **Provides `WebsitePageContext`** — registers the active page
112
+ * ref so child sections (`<WebsiteSection>`) and label hooks
113
+ * (`useWebsiteLabel`) can derive label keys without re-typing
114
+ * the page ref at every leaf.
115
+ * 2. **Renders the site-wide chrome** — pulls
116
+ * `headerComponent` and `footerComponent` from the runtime
117
+ * navigation config and wraps the page's main content in the
118
+ * site's standard header/main/footer scaffold. Per-page
119
+ * overrides (`header={…}` / `footer={…}` props) take precedence
120
+ * over the runtime defaults so individual pages can opt into a
121
+ * custom chrome (eg. a campaign landing page running with
122
+ * header-only minimal chrome).
123
+ * 3. **Stamps DOM landmarks** — emits `data-website-page-ref` on
124
+ * the layout root for analytics / SEO crawlers / overlay
125
+ * tooling that needs to identify the active page from the DOM
126
+ * without booting React. When the website facet hydrates
127
+ * frontend-provider bootstraps, the same root also carries the
128
+ * active public provider refs for analytics / browser-monitoring /
129
+ * captcha so non-React tooling can detect the configured vendor
130
+ * without reverse-engineering app code.
131
+ *
132
+ * **Why semantic `<header>` / `<main>` / `<footer>` elements**:
133
+ * marketing-site SEO scoring penalizes pages that ship a single
134
+ * unstructured `<div>` tree. Using semantic landmarks costs nothing
135
+ * and improves crawler accessibility AND screen-reader
136
+ * navigability — both are first-class concerns for a public
137
+ * marketing surface.
138
+ *
139
+ * **Why this lives in `core/`** (not `components/`): the layout is
140
+ * structural framework machinery (it owns the page-context
141
+ * provisioning + the navigation slot resolution), not a styled
142
+ * primitive. Designers override `header` / `footer` via runtime
143
+ * config or per-page props, NOT by replacing this component.
144
+ */
145
+ export interface WebsitePageLayoutProps {
146
+ /**
147
+ * The active page's ref. Accepts either an already-branded
148
+ * `WebsitePageRef` or a raw string — the layout parses through
149
+ * `WebsitePageRefSchema` so a malformed ref fails loud at render.
150
+ *
151
+ * Typically sourced from the page's `WebsitePageManifest.ref`
152
+ * (eg. `<WebsitePageLayout pageRef={LANDING_PAGE_MANIFEST.ref}>`).
153
+ */
154
+ pageRef: WebsitePageRef | string;
155
+ /**
156
+ * Per-page header override. When supplied, takes precedence over
157
+ * `WebsiteRuntimeContext.designTokens` navigation config's
158
+ * `headerComponent`. Pass `null` explicitly to suppress the header
159
+ * entirely (campaign landing pages without site-wide nav).
160
+ */
161
+ header?: ReactNode | null;
162
+ /**
163
+ * Per-page footer override. Same semantics as `header`.
164
+ */
165
+ footer?: ReactNode | null;
166
+ /**
167
+ * Optional className applied to the root `<div data-website-page-ref="…">`
168
+ * wrapper. Use for page-level layout tweaks (eg. `min-h-screen` on
169
+ * landing pages).
170
+ */
171
+ className?: string;
172
+ /**
173
+ * Optional className applied to the inner `<main>` element. Use for
174
+ * vertical-rhythm overrides on long-scroll landing pages.
175
+ */
176
+ mainClassName?: string;
177
+ /**
178
+ * The page's section composition. Typically a sequence of
179
+ * `<WebsiteSection>` wrappers around concrete section components.
180
+ */
181
+ children: ReactNode;
182
+ }
183
+
184
+ export function WebsitePageLayout({
185
+ pageRef,
186
+ header,
187
+ footer,
188
+ className,
189
+ mainClassName,
190
+ children,
191
+ }: WebsitePageLayoutProps): ReactNode {
192
+ const runtime = useWebsiteRuntime();
193
+
194
+ /**
195
+ * Defensive parse: callers should pass a `WebsitePageManifest.ref`
196
+ * (already branded), but accepting raw strings + parsing here means
197
+ * a hand-typed call site can't smuggle a malformed ref into the
198
+ * label-key derivation chain.
199
+ */
200
+ const safePageRef = useMemo<WebsitePageRef>(
201
+ () => WebsitePageRefSchema.parse(pageRef),
202
+ [pageRef],
203
+ );
204
+
205
+ const pageContextValue = useMemo<WebsitePageContextValue>(
206
+ () => ({ pageRef: safePageRef }),
207
+ [safePageRef],
208
+ );
209
+
210
+ /**
211
+ * Header resolution rules:
212
+ * - `header === null` => explicit suppression
213
+ * - `header !== undefined` => per-page override (any ReactNode)
214
+ * - `header === undefined` => fall back to the runtime navigation
215
+ * config's `headerComponent`
216
+ *
217
+ * The fallback is the common path: every page renders the same
218
+ * site-wide chrome. The override path is the escape hatch (campaign
219
+ * landing pages with custom header layouts).
220
+ */
221
+ const resolvedHeader = useMemo<ReactNode>(() => {
222
+ if (header === null) return null;
223
+ if (header !== undefined) return header;
224
+ const HeaderComponent = runtime.navigation.headerComponent;
225
+ return <HeaderComponent />;
226
+ }, [header, runtime.navigation.headerComponent]);
227
+
228
+ const resolvedFooter = useMemo<ReactNode>(() => {
229
+ if (footer === null) return null;
230
+ if (footer !== undefined) return footer;
231
+ const FooterComponent = runtime.navigation.footerComponent;
232
+ return <FooterComponent />;
233
+ }, [footer, runtime.navigation.footerComponent]);
234
+
235
+ const providerRootAttributes = useMemo<Record<string, string>>(() => {
236
+ const attrs: Record<string, string> = {};
237
+ const productAnalyticsProviderRef = runtime.frontendProviderRegistry
238
+ ?.findProviderByProviderCapability(
239
+ BUILTIN_PROVIDER_CAPABILITY.FRONTEND_PRODUCT_ANALYTICS,
240
+ )
241
+ ?.metadata.ref;
242
+ const errorMonitoringProviderRef = runtime.frontendProviderRegistry
243
+ ?.findProviderByProviderCapability(
244
+ BUILTIN_PROVIDER_CAPABILITY.FRONTEND_ERROR_MONITORING,
245
+ )
246
+ ?.metadata.ref;
247
+ const captchaProviderRef = runtime.frontendProviderRegistry
248
+ ?.findProviderByProviderCapability(
249
+ BUILTIN_PROVIDER_CAPABILITY.FRONTEND_CAPTCHA,
250
+ )
251
+ ?.metadata.ref;
252
+
253
+ if (productAnalyticsProviderRef) {
254
+ attrs['data-website-product-analytics-provider-ref'] = productAnalyticsProviderRef;
255
+ }
256
+ if (errorMonitoringProviderRef) {
257
+ attrs['data-website-error-monitoring-provider-ref'] = errorMonitoringProviderRef;
258
+ }
259
+ if (captchaProviderRef) {
260
+ attrs['data-website-captcha-provider-ref'] = captchaProviderRef;
261
+ }
262
+
263
+ return attrs;
264
+ }, [runtime.frontendProviderRegistry]);
265
+
266
+ /**
267
+ * Resolved skip-link label. Reads through the runtime label pack
268
+ * via the standard `useWebsiteLabelByKey` hook so a missing
269
+ * translation surfaces the same diagnostic shape as any other
270
+ * missing chrome label. The framework guarantees the key reaches
271
+ * the bridge (see `pickLabelsForPage`) and the consumer's
272
+ * label-pack validator (see `composeEffectiveChromeLabelKeys` in
273
+ * `label-pack-loader.ts`) ensures it is present in every locale
274
+ * pack — there is therefore no silent-fallback path that would
275
+ * ship an English skip link to a non-EN visitor.
276
+ */
277
+ const skipLinkLabel = useWebsiteLabelByKey(runtime.skipToMainContentLabelKey);
278
+
279
+ return (
280
+ <WebsitePageContextProvider value={pageContextValue}>
281
+ <div
282
+ data-website-page-ref={safePageRef as unknown as string}
283
+ className={className}
284
+ {...providerRootAttributes}
285
+ >
286
+ {/*
287
+ * Skip link rendered as the FIRST focusable element on the
288
+ * page so a keyboard / AT user can bypass the chrome on every
289
+ * navigation (WCAG 2.1 §2.4.1 "Bypass Blocks", Level A).
290
+ * Visually hidden by default; revealed on focus via the
291
+ * scoped `<style>` block below.
292
+ */}
293
+ <style>{SKIP_LINK_FOCUS_CSS}</style>
294
+ <a
295
+ href={`#${MAIN_CONTENT_DOM_ID}`}
296
+ data-website-skip-link=""
297
+ style={SKIP_LINK_HIDDEN_STYLE}
298
+ >
299
+ {skipLinkLabel}
300
+ </a>
301
+ {resolvedHeader !== null ? <header>{resolvedHeader}</header> : null}
302
+ <main id={MAIN_CONTENT_DOM_ID} className={mainClassName} tabIndex={-1}>
303
+ {children}
304
+ </main>
305
+ {resolvedFooter !== null ? <footer>{resolvedFooter}</footer> : null}
306
+ </div>
307
+ </WebsitePageContextProvider>
308
+ );
309
+ }
310
+ WebsitePageLayout.displayName = 'WebsitePageLayout';
311
+ /** @wildo_source:part:end saas.website.page-layout.component */
@@ -0,0 +1,139 @@
1
+ import React, { useMemo, type HTMLAttributes, type ReactNode } from 'react';
2
+
3
+ import { WebsiteSectionContextProvider } from '../contexts/WebsiteSectionContext';
4
+ import type { WebsiteSectionContextValue } from '../contexts/WebsiteSectionContext';
5
+ import { WebsiteSectionCategory } from '../../schemas/sections/website-section-category.shared';
6
+ import {
7
+ WebsiteSectionRefSchema,
8
+ type WebsiteSectionRef,
9
+ } from '../../schemas/refs/section-ref.schemas';
10
+
11
+ /**
12
+ * @wildo_source:part:start saas.website.section.component facet:layer:core facet:family:website
13
+ *
14
+ * `WebsiteSection` — the per-section structural primitive.
15
+ *
16
+ * **Three responsibilities** (semantic-weight discipline):
17
+ *
18
+ * 1. **Provides `WebsiteSectionContext`** — registers the active
19
+ * sectionRef + category so leaf primitives (`useWebsiteLabel`,
20
+ * analytics overlays, structured-data emitters) can derive
21
+ * keys / metadata without re-typing the section's identity.
22
+ * 2. **Renders a semantic `<section>` landmark** — emits
23
+ * `data-section-ref` and `data-section-category` attributes so
24
+ * analytics tooling and SEO crawlers can identify and group
25
+ * sections without parsing className conventions. Optional
26
+ * `id` prop wires up anchor-link navigation
27
+ * (`<a href="#pricing">`).
28
+ * 3. **Provides a styling seam** — `className` is forwarded to the
29
+ * `<section>` element so designers can apply utility-class
30
+ * decorations (background, padding, max-width container) without
31
+ * having to compose multiple wrappers.
32
+ *
33
+ * **Why NOT render a `<div>` instead of `<section>`**: marketing
34
+ * pages benefit from semantic landmarks for SEO scoring and
35
+ * accessibility (screen-reader section navigation). A `<section>`
36
+ * with `data-section-category="hero"` is structurally meaningful;
37
+ * a `<div>` with the same data-attribute carries the same metadata
38
+ * but loses the landmark benefit.
39
+ *
40
+ * **Why expose `as` is NOT a prop**: switching the rendered tag
41
+ * (`<article>` for blog post bodies, `<aside>` for sidebar widgets)
42
+ * is legitimate but rare enough to be worth a separate primitive
43
+ * (Phase 4: `<WebsiteAside>`, `<WebsiteArticle>`). Keeping
44
+ * `WebsiteSection` rendered as a literal `<section>` removes a
45
+ * rarely-used prop from the section-author API and keeps the SEO
46
+ * landmark contract guaranteed at the type level.
47
+ */
48
+ export interface WebsiteSectionProps {
49
+ /**
50
+ * The section's ref. Accepts an already-branded `WebsiteSectionRef`
51
+ * or a raw string — the layout parses through
52
+ * `WebsiteSectionRefSchema` so a malformed ref fails loud at
53
+ * render. Typically sourced from the page manifest's
54
+ * `sectionRefs[]` (eg. `<WebsiteSection sectionRef="hero" …>`).
55
+ */
56
+ sectionRef: WebsiteSectionRef | string;
57
+ /**
58
+ * The section's structural intent (see `WebsiteSectionCategory`).
59
+ * Required at the wrapper level (rather than only on the section
60
+ * definition) because the wrapper emits `data-section-category`
61
+ * for analytics, and asking the wrapper to look up the category
62
+ * on a separate manifest registry would re-introduce the runtime
63
+ * coupling the per-section context exists to avoid.
64
+ */
65
+ category: WebsiteSectionCategory;
66
+ /**
67
+ * Optional DOM id. Wires anchor-link navigation
68
+ * (`<a href="#pricing">`). When omitted, the section has no DOM
69
+ * id — by design; not every section is an anchor target.
70
+ */
71
+ id?: string;
72
+ /**
73
+ * Optional className applied to the `<section>` element. Used by
74
+ * designers for layout / spacing utilities. Section components
75
+ * themselves should NOT pass className through to this wrapper —
76
+ * keep the styling responsibility at the page-composition level.
77
+ */
78
+ className?: string;
79
+ /**
80
+ * The section's body. Typically a single section component
81
+ * (`<HeroSection {…heroProps} />`). May be plain JSX for one-off
82
+ * sections that don't justify a `defineWebsiteSection` factory call.
83
+ */
84
+ children: ReactNode;
85
+ /**
86
+ * Pass-through `aria-*` / `role` attributes for sections that need
87
+ * to refine their landmark semantics (eg. `aria-label="Featured
88
+ * customer logos"` on a logo wall).
89
+ */
90
+ ariaLabel?: string;
91
+ ariaLabelledBy?: string;
92
+ }
93
+
94
+ export function WebsiteSection({
95
+ sectionRef,
96
+ category,
97
+ id,
98
+ className,
99
+ children,
100
+ ariaLabel,
101
+ ariaLabelledBy,
102
+ }: WebsiteSectionProps): ReactNode {
103
+ const safeSectionRef = useMemo<WebsiteSectionRef>(
104
+ () => WebsiteSectionRefSchema.parse(sectionRef),
105
+ [sectionRef],
106
+ );
107
+
108
+ const sectionContextValue = useMemo<WebsiteSectionContextValue>(
109
+ () => ({ sectionRef: safeSectionRef, category }),
110
+ [safeSectionRef, category],
111
+ );
112
+
113
+ /**
114
+ * Build the `<section>` attribute set imperatively to keep
115
+ * `aria-*` props off the element when omitted (rendering
116
+ * `aria-label={undefined}` still emits the attribute in some
117
+ * React versions — explicit absence is cleaner).
118
+ */
119
+ const sectionAttrs: HTMLAttributes<HTMLElement> = {
120
+ className,
121
+ };
122
+ if (id !== undefined) sectionAttrs.id = id;
123
+ if (ariaLabel !== undefined) sectionAttrs['aria-label'] = ariaLabel;
124
+ if (ariaLabelledBy !== undefined) sectionAttrs['aria-labelledby'] = ariaLabelledBy;
125
+
126
+ return (
127
+ <WebsiteSectionContextProvider value={sectionContextValue}>
128
+ <section
129
+ data-section-ref={safeSectionRef as unknown as string}
130
+ data-section-category={category}
131
+ {...sectionAttrs}
132
+ >
133
+ {children}
134
+ </section>
135
+ </WebsiteSectionContextProvider>
136
+ );
137
+ }
138
+ WebsiteSection.displayName = 'WebsiteSection';
139
+ /** @wildo_source:part:end saas.website.section.component */