blume 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
  12. package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
  13. package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
  14. package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
  15. package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
  16. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  17. package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
  18. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
  19. package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
  20. package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
  21. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  22. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  23. package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
  24. package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
  25. package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
  26. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
  27. package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
  28. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  29. package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
  30. package/dist/cli/chunk-cm89scdv.js +443 -0
  31. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  32. package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
  33. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  34. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  35. package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
  36. package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
  37. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  38. package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
  39. package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
  40. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  41. package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
  42. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
  43. package/dist/cli/chunk-kwrg63tm.js +31 -0
  44. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  45. package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
  46. package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
  47. package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
  48. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
  49. package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
  50. package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
  51. package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
  52. package/dist/cli/chunk-q3wxxq39.js +126 -0
  53. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  54. package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
  55. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  56. package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
  57. package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
  58. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  59. package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
  60. package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
  61. package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
  62. package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
  63. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
  64. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  65. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  66. package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
  67. package/dist/cli/chunk-zf748avv.js.map +37 -0
  68. package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
  69. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
  70. package/dist/cli/index.js +26 -20
  71. package/dist/cli/index.js.map +5 -5
  72. package/dist/types/ai/agent-surface.d.ts +8 -7
  73. package/dist/types/ai/ai-catalog.d.ts +7 -0
  74. package/dist/types/ai/ask-context.d.ts +22 -0
  75. package/dist/types/ai/ask.d.ts +271 -58
  76. package/dist/types/ai/index.d.ts +2 -2
  77. package/dist/types/ai/site-skill.d.ts +26 -0
  78. package/dist/types/ai/skills.d.ts +16 -0
  79. package/dist/types/astro/integration.d.ts +8 -0
  80. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  81. package/dist/types/captcha/index.d.ts +28 -0
  82. package/dist/types/captcha/schema.d.ts +29 -0
  83. package/dist/types/captcha/turnstile.d.ts +28 -0
  84. package/dist/types/components/content/api-field.d.ts +59 -0
  85. package/dist/types/components/content/api-page.d.ts +95 -0
  86. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  87. package/dist/types/components/openapi/helpers.d.ts +137 -0
  88. package/dist/types/components/openapi/security.d.ts +85 -0
  89. package/dist/types/consent/ethyca.d.ts +48 -0
  90. package/dist/types/consent/index.d.ts +37 -0
  91. package/dist/types/consent/native.d.ts +28 -0
  92. package/dist/types/consent/osano.d.ts +42 -0
  93. package/dist/types/consent/schema.d.ts +110 -0
  94. package/dist/types/core/config-input.d.ts +187 -20
  95. package/dist/types/core/data.d.ts +49 -4
  96. package/dist/types/core/footer.d.ts +8 -0
  97. package/dist/types/core/i18n-ui.d.ts +86 -0
  98. package/dist/types/core/includes.d.ts +8 -2
  99. package/dist/types/core/localizable.d.ts +7 -0
  100. package/dist/types/core/page-modes.d.ts +29 -0
  101. package/dist/types/core/redirect-patterns.d.ts +144 -0
  102. package/dist/types/core/related.d.ts +40 -0
  103. package/dist/types/core/schema.d.ts +551 -56
  104. package/dist/types/core/sources/read.d.ts +5 -0
  105. package/dist/types/core/types.d.ts +1 -1
  106. package/dist/types/core/variables.d.ts +42 -0
  107. package/dist/types/deploy/node-headers.d.ts +13 -2
  108. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  109. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  110. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  111. package/dist/types/deploy/redirects.d.ts +39 -11
  112. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  113. package/dist/types/markdown/code-title.d.ts +17 -0
  114. package/dist/types/markdown/features.d.ts +3 -2
  115. package/dist/types/narration/build.d.ts +70 -0
  116. package/dist/types/narration/provider.d.ts +26 -0
  117. package/dist/types/narration/script.d.ts +154 -0
  118. package/dist/types/openapi/model.d.ts +12 -6
  119. package/dist/types/openapi/overlay.d.ts +54 -0
  120. package/dist/types/openapi/parse.d.ts +13 -0
  121. package/dist/types/openapi/references.d.ts +14 -4
  122. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  123. package/dist/types/ratelimit/index.d.ts +27 -0
  124. package/dist/types/ratelimit/memory.d.ts +36 -0
  125. package/dist/types/ratelimit/schema.d.ts +111 -0
  126. package/dist/types/ratelimit/upstash.d.ts +21 -0
  127. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  128. package/dist/types/reference/asyncapi.d.ts +10 -10
  129. package/dist/types/reference/graphql.d.ts +13 -10
  130. package/dist/types/reference/openapi.d.ts +33 -12
  131. package/dist/types/reference/options.d.ts +45 -5
  132. package/dist/types/reference/scalar.d.ts +13 -0
  133. package/dist/types/reference/schema.d.ts +60 -36
  134. package/dist/types/search/documents.d.ts +12 -1
  135. package/dist/types/search/orama-index.d.ts +4 -0
  136. package/dist/types/seo/metatags.d.ts +20 -0
  137. package/docs/02-deployment.mdx +41 -5
  138. package/docs/03-upgrading.mdx +4 -4
  139. package/docs/04-migrating.mdx +1 -1
  140. package/docs/advanced/changelog.mdx +2 -2
  141. package/docs/advanced/skills.mdx +14 -0
  142. package/docs/cli/audit.mdx +21 -1
  143. package/docs/cli/index.mdx +1 -0
  144. package/docs/configuration/analytics.mdx +3 -1
  145. package/docs/configuration/assistant.mdx +188 -36
  146. package/docs/configuration/consent.mdx +100 -0
  147. package/docs/configuration/customization.mdx +4 -4
  148. package/docs/configuration/index.mdx +63 -5
  149. package/docs/configuration/meta.ts +3 -0
  150. package/docs/configuration/narration.mdx +119 -0
  151. package/docs/configuration/rate-limiting.mdx +69 -0
  152. package/docs/configuration/search.mdx +29 -1
  153. package/docs/configuration/theming.mdx +3 -1
  154. package/docs/content/components.mdx +106 -0
  155. package/docs/content/frontmatter.mdx +76 -0
  156. package/docs/content/i18n.mdx +38 -3
  157. package/docs/content/includes.mdx +16 -0
  158. package/docs/content/meta.mdx +1 -0
  159. package/docs/content/meta.ts +1 -0
  160. package/docs/content/navigation.mdx +28 -6
  161. package/docs/content/sources/contentful.mdx +32 -0
  162. package/docs/content/sources/custom.mdx +39 -0
  163. package/docs/content/sources/github-releases.mdx +31 -0
  164. package/docs/content/sources/index.mdx +91 -0
  165. package/docs/content/sources/meta.ts +16 -0
  166. package/docs/content/sources/notion.mdx +32 -0
  167. package/docs/content/sources/obsidian.mdx +37 -0
  168. package/docs/content/sources/payload.mdx +30 -0
  169. package/docs/content/sources/remote-mdx.mdx +40 -0
  170. package/docs/content/sources/sanity.mdx +29 -0
  171. package/docs/content/sources/strapi.mdx +31 -0
  172. package/docs/content/syntax.mdx +67 -2
  173. package/docs/content/variables.mdx +41 -0
  174. package/docs/discoverability/agent-discovery.mdx +29 -2
  175. package/docs/discoverability/index.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/metadata.mdx +17 -0
  178. package/docs/references/api-pages.mdx +74 -0
  179. package/docs/references/asyncapi.mdx +1 -1
  180. package/docs/references/graphql.mdx +2 -2
  181. package/docs/references/meta.ts +1 -1
  182. package/docs/references/openapi.mdx +102 -3
  183. package/docs/references/scalar.mdx +3 -1
  184. package/package.json +36 -1
  185. package/skills/blume/SKILL.md +3 -0
  186. package/skills/blume-migrate/SKILL.md +5 -5
  187. package/skills/blume-migrate/references/docusaurus.md +3 -3
  188. package/skills/blume-migrate/references/mintlify.md +37 -19
  189. package/skills/blume-migrate/references/nextra.md +2 -2
  190. package/skills/blume-migrate/references/starlight.md +2 -1
  191. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  192. package/skills/blume-write-skill/SKILL.md +82 -0
  193. package/src/ai/agent-readability.ts +2 -1
  194. package/src/ai/agent-surface.ts +9 -7
  195. package/src/ai/ai-catalog.ts +6 -3
  196. package/src/ai/ask-context.ts +73 -20
  197. package/src/ai/ask-data.ts +6 -0
  198. package/src/ai/ask-tools.ts +142 -0
  199. package/src/ai/ask.ts +354 -76
  200. package/src/ai/component-markdown.ts +75 -0
  201. package/src/ai/cors.ts +2 -0
  202. package/src/ai/index.ts +12 -0
  203. package/src/ai/llms.ts +6 -2
  204. package/src/ai/markdown.ts +2 -1
  205. package/src/ai/mcp/data.ts +6 -0
  206. package/src/ai/openapi-components.ts +9 -3
  207. package/src/ai/site-skill.ts +274 -0
  208. package/src/ai/skills.ts +35 -2
  209. package/src/ai/write-skill.ts +97 -0
  210. package/src/astro/generate.ts +132 -18
  211. package/src/astro/index.ts +1 -0
  212. package/src/astro/integration.ts +35 -0
  213. package/src/astro/module-types.ts +6 -0
  214. package/src/astro/templates.ts +283 -30
  215. package/src/astro/variables.ts +31 -0
  216. package/src/audit/catalog.ts +19 -0
  217. package/src/audit/checks/contrast.ts +183 -0
  218. package/src/audit/checks/i18n.ts +2 -3
  219. package/src/audit/checks/indexability.ts +2 -3
  220. package/src/audit/checks/links.ts +5 -12
  221. package/src/audit/checks/sitemap.ts +2 -3
  222. package/src/audit/redirects.ts +46 -15
  223. package/src/audit/run.ts +2 -0
  224. package/src/audit/types.ts +2 -1
  225. package/src/blume-modules.d.ts +7 -0
  226. package/src/captcha/client.ts +152 -0
  227. package/src/captcha/hcaptcha.ts +36 -0
  228. package/src/captcha/index.ts +28 -0
  229. package/src/captcha/schema.ts +32 -0
  230. package/src/captcha/turnstile.ts +38 -0
  231. package/src/captcha/verify.ts +71 -0
  232. package/src/cli/command-meta.ts +5 -0
  233. package/src/cli/commands/doctor.ts +22 -16
  234. package/src/cli/commands/skill.ts +96 -0
  235. package/src/cli/index.ts +5 -0
  236. package/src/cli/required-secrets.ts +71 -29
  237. package/src/components/BlumePage.astro +2 -0
  238. package/src/components/content/ApiEndpoint.astro +18 -0
  239. package/src/components/content/ApiField.astro +79 -0
  240. package/src/components/content/ApiPlayground.astro +62 -0
  241. package/src/components/content/ApiRail.astro +19 -0
  242. package/src/components/content/Color.astro +1 -0
  243. package/src/components/content/FileTree.astro +1 -0
  244. package/src/components/content/GithubInfo.astro +1 -0
  245. package/src/components/content/Panel.astro +1 -0
  246. package/src/components/content/ParamField.astro +25 -0
  247. package/src/components/content/RequestExample.astro +17 -0
  248. package/src/components/content/ResponseExample.astro +17 -0
  249. package/src/components/content/ResponseField.astro +32 -0
  250. package/src/components/content/Step.astro +1 -0
  251. package/src/components/content/Tile.astro +1 -0
  252. package/src/components/content/Tooltip.astro +1 -0
  253. package/src/components/content/Tree.astro +1 -0
  254. package/src/components/content/TypeTable.astro +4 -1
  255. package/src/components/content/View.astro +21 -0
  256. package/src/components/content/ViewSwitcher.astro +168 -0
  257. package/src/components/content/api-field.ts +100 -0
  258. package/src/components/content/api-page.ts +224 -0
  259. package/src/components/content/view-switcher.ts +71 -0
  260. package/src/components/index.ts +2 -0
  261. package/src/components/islands/Assistant.astro +10 -1
  262. package/src/components/islands/assistant.tsx +104 -14
  263. package/src/components/islands/code-question.ts +62 -0
  264. package/src/components/islands/hooks.ts +110 -21
  265. package/src/components/islands/support-link.ts +68 -0
  266. package/src/components/layout/Analytics.astro +11 -2
  267. package/src/components/layout/Banner.astro +14 -10
  268. package/src/components/layout/Breadcrumbs.astro +6 -2
  269. package/src/components/layout/ConsentBanner.astro +62 -0
  270. package/src/components/layout/ConsentHead.astro +38 -0
  271. package/src/components/layout/Directory.astro +94 -0
  272. package/src/components/layout/DrawerSwitchers.astro +51 -0
  273. package/src/components/layout/Empty.astro +2 -2
  274. package/src/components/layout/Header.astro +122 -80
  275. package/src/components/layout/LanguageSwitcher.astro +48 -15
  276. package/src/components/layout/NarrationPlayer.astro +1085 -0
  277. package/src/components/layout/NavSelector.astro +33 -7
  278. package/src/components/layout/PageFeedback.astro +54 -49
  279. package/src/components/layout/PageLayout.astro +71 -3
  280. package/src/components/layout/ReferenceLayout.astro +17 -6
  281. package/src/components/layout/RelatedPages.astro +42 -0
  282. package/src/components/layout/RootLayout.astro +308 -45
  283. package/src/components/layout/Search.astro +31 -17
  284. package/src/components/layout/SiteFooter.astro +124 -0
  285. package/src/components/layout/SiteMetatags.astro +16 -0
  286. package/src/components/layout/consent/native.ts +79 -0
  287. package/src/components/layout/consent/types.ts +37 -0
  288. package/src/components/layout/footer-links.ts +53 -0
  289. package/src/components/layout/footer-socials.ts +87 -0
  290. package/src/components/layout/locale-redirect.ts +107 -0
  291. package/src/components/layout/nav-utils.ts +83 -0
  292. package/src/components/layout/page-feedback.ts +96 -0
  293. package/src/components/layout/search/analytics.ts +98 -0
  294. package/src/components/layout/search/flexsearch.ts +17 -5
  295. package/src/components/layout/search/orama-cloud.ts +26 -14
  296. package/src/components/layout/search/types.ts +20 -0
  297. package/src/components/layout/search/typesense.ts +5 -1
  298. package/src/components/openapi/Operation.astro +161 -13
  299. package/src/components/openapi/RequestBody.astro +6 -4
  300. package/src/components/openapi/RequestPanel.astro +59 -7
  301. package/src/components/openapi/Responses.astro +6 -4
  302. package/src/components/openapi/async-snippets.ts +4 -4
  303. package/src/components/openapi/code-samples.ts +97 -0
  304. package/src/components/openapi/security.ts +6 -3
  305. package/src/components/openapi/snippets.ts +869 -8
  306. package/src/components/props.ts +6 -0
  307. package/src/consent/client.ts +95 -0
  308. package/src/consent/clients.ts +19 -0
  309. package/src/consent/ethyca.ts +71 -0
  310. package/src/consent/head.ts +24 -0
  311. package/src/consent/index.ts +37 -0
  312. package/src/consent/init.ts +29 -0
  313. package/src/consent/native.ts +36 -0
  314. package/src/consent/osano.ts +59 -0
  315. package/src/consent/schema.ts +30 -0
  316. package/src/core/builtin-tags.ts +5 -0
  317. package/src/core/client-address.ts +19 -0
  318. package/src/core/config-input.ts +194 -20
  319. package/src/core/data.ts +44 -4
  320. package/src/core/diagnostics.ts +3 -2
  321. package/src/core/footer.ts +26 -0
  322. package/src/core/graph.ts +39 -36
  323. package/src/core/i18n-ui.ts +82 -2
  324. package/src/core/includes.ts +20 -3
  325. package/src/core/links.ts +16 -2
  326. package/src/core/localizable.ts +26 -0
  327. package/src/core/navigation.ts +43 -12
  328. package/src/core/page-modes.ts +56 -0
  329. package/src/core/project-graph.ts +111 -5
  330. package/src/core/redirect-patterns.ts +491 -0
  331. package/src/core/related.ts +131 -0
  332. package/src/core/schema.ts +318 -64
  333. package/src/core/sources/normalize.ts +2 -1
  334. package/src/core/sources/read.ts +8 -3
  335. package/src/core/types.ts +1 -1
  336. package/src/core/ui-packs/ar.ts +43 -0
  337. package/src/core/ui-packs/bg.ts +45 -0
  338. package/src/core/ui-packs/bn.ts +43 -0
  339. package/src/core/ui-packs/ca.ts +46 -0
  340. package/src/core/ui-packs/cs.ts +44 -0
  341. package/src/core/ui-packs/da.ts +45 -0
  342. package/src/core/ui-packs/de.ts +46 -0
  343. package/src/core/ui-packs/el.ts +45 -0
  344. package/src/core/ui-packs/es.ts +46 -0
  345. package/src/core/ui-packs/fa.ts +45 -0
  346. package/src/core/ui-packs/fi.ts +45 -0
  347. package/src/core/ui-packs/fr.ts +46 -0
  348. package/src/core/ui-packs/he.ts +43 -0
  349. package/src/core/ui-packs/hi.ts +44 -0
  350. package/src/core/ui-packs/hr.ts +45 -0
  351. package/src/core/ui-packs/hu.ts +44 -0
  352. package/src/core/ui-packs/id.ts +45 -0
  353. package/src/core/ui-packs/it.ts +45 -0
  354. package/src/core/ui-packs/ja.ts +45 -0
  355. package/src/core/ui-packs/ko.ts +44 -0
  356. package/src/core/ui-packs/nl.ts +46 -0
  357. package/src/core/ui-packs/no.ts +44 -0
  358. package/src/core/ui-packs/pl.ts +45 -0
  359. package/src/core/ui-packs/pt-br.ts +46 -0
  360. package/src/core/ui-packs/pt.ts +45 -0
  361. package/src/core/ui-packs/ro.ts +45 -0
  362. package/src/core/ui-packs/ru.ts +45 -0
  363. package/src/core/ui-packs/sk.ts +44 -0
  364. package/src/core/ui-packs/sr.ts +45 -0
  365. package/src/core/ui-packs/sv.ts +44 -0
  366. package/src/core/ui-packs/th.ts +43 -0
  367. package/src/core/ui-packs/tr.ts +45 -0
  368. package/src/core/ui-packs/uk.ts +45 -0
  369. package/src/core/ui-packs/vi.ts +45 -0
  370. package/src/core/ui-packs/zh-tw.ts +43 -0
  371. package/src/core/ui-packs/zh.ts +43 -0
  372. package/src/core/variables.ts +103 -0
  373. package/src/deploy/artifacts.ts +77 -8
  374. package/src/deploy/cloudflare-negotiation.ts +35 -5
  375. package/src/deploy/headers.ts +2 -1
  376. package/src/deploy/node-headers.ts +63 -9
  377. package/src/deploy/platforms/cloudflare.ts +32 -0
  378. package/src/deploy/platforms/netlify.ts +33 -1
  379. package/src/deploy/platforms/vercel.ts +37 -2
  380. package/src/deploy/redirects.ts +91 -24
  381. package/src/deploy/vercel-negotiation.ts +36 -0
  382. package/src/markdown/api-rail.ts +259 -0
  383. package/src/markdown/code-title.ts +35 -9
  384. package/src/markdown/features.ts +3 -2
  385. package/src/markdown/fence-meta.ts +2 -0
  386. package/src/markdown/include.ts +11 -1
  387. package/src/markdown/index.ts +28 -5
  388. package/src/markdown/math.ts +4 -4
  389. package/src/markdown/variables.ts +56 -0
  390. package/src/markdown/views.ts +82 -0
  391. package/src/narration/build.ts +412 -0
  392. package/src/narration/player.ts +249 -0
  393. package/src/narration/provider.ts +29 -0
  394. package/src/narration/script.ts +592 -0
  395. package/src/og/card.ts +7 -6
  396. package/src/openapi/model.ts +57 -24
  397. package/src/openapi/overlay.ts +256 -0
  398. package/src/openapi/parse.ts +55 -2
  399. package/src/openapi/references.ts +21 -5
  400. package/src/openapi/render-mdx.ts +5 -1
  401. package/src/openapi/scalar.ts +40 -6
  402. package/src/openapi/source.ts +1 -1
  403. package/src/ratelimit/cloudflare.ts +54 -0
  404. package/src/ratelimit/index.ts +30 -0
  405. package/src/ratelimit/memory.ts +45 -0
  406. package/src/ratelimit/runtime.ts +229 -0
  407. package/src/ratelimit/schema.ts +28 -0
  408. package/src/ratelimit/upstash.ts +30 -0
  409. package/src/ratelimit/wrangler.ts +70 -0
  410. package/src/reference/asyncapi.ts +2 -2
  411. package/src/reference/graphql.ts +5 -2
  412. package/src/reference/openapi.ts +21 -7
  413. package/src/reference/options.ts +51 -7
  414. package/src/reference/scalar.ts +33 -8
  415. package/src/registry/eject.ts +20 -4
  416. package/src/registry/registry.ts +7 -0
  417. package/src/search/documents.ts +26 -2
  418. package/src/search/orama-index.ts +28 -3
  419. package/src/search/pagefind-ranking.ts +39 -0
  420. package/src/search/sync/algolia.ts +15 -4
  421. package/src/search/sync/orama-cloud.ts +3 -0
  422. package/src/search/sync/typesense.ts +5 -0
  423. package/src/seo/metatags.ts +67 -0
  424. package/src/theme/chrome-icons.ts +1 -0
  425. package/src/theme/color.ts +500 -0
  426. package/src/theme/entry.ts +56 -3
  427. package/src/theme/palette.ts +121 -45
  428. package/src/translate/prompts.ts +1 -0
  429. package/src/translate/validate.ts +8 -2
  430. package/src/upgrade/upgrade.ts +4 -171
  431. package/dist/cli/chunk-364znk6q.js +0 -75
  432. package/dist/cli/chunk-364znk6q.js.map +0 -11
  433. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  434. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  435. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  436. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  437. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  438. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  439. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  440. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  441. package/docs/content/sources.mdx +0 -334
  442. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
  443. /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
  444. /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
  445. /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
  446. /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
  447. /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
  448. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
  449. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
  450. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
  451. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
  452. /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
  453. /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
  454. /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
  455. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
@@ -19,9 +19,15 @@ interface Props {
19
19
  * overflow the viewport — pass `end` there.
20
20
  */
21
21
  align?: "start" | "end";
22
+ /**
23
+ * `bar` (the default) for the header; `drawer` for the top of the nav
24
+ * drawer, where the trigger spans the drawer, always shows its label, and
25
+ * opens a panel as wide as itself, so nothing reaches past the drawer edge.
26
+ */
27
+ variant?: "bar" | "drawer";
22
28
  }
23
29
 
24
- const { selector, route, align = "start" } = Astro.props;
30
+ const { selector, route, align = "start", variant = "bar" } = Astro.props;
25
31
 
26
32
  // The active item is the deepest path the current route sits under (on a path
27
33
  // boundary, so `/api` never claims `/api-reference` routes), falling back to
@@ -37,25 +43,37 @@ const active =
37
43
  // version like "v2" or "Latest"): on a phone a bare chevron doesn't say what
38
44
  // it switches. Longer product names still collapse to the icon there.
39
45
  const label = active?.label ?? selector.label;
40
- const labelClass = label.length <= 6 ? undefined : "max-sm:hidden";
46
+ let labelClass: string | undefined;
47
+ if (variant === "drawer") {
48
+ labelClass = "flex-1 text-start";
49
+ } else if (label.length > 6) {
50
+ labelClass = "max-sm:hidden";
51
+ }
41
52
  // The accessible name carries the visible active label too (WCAG 2.5.3,
42
53
  // label in name), so a voice-control user can say "v2" to open it — the
43
54
  // language switcher's pattern.
44
55
  const summaryLabel =
45
56
  label === selector.label ? selector.label : `${selector.label}: ${label}`;
46
57
 
47
- const iconButton =
48
- "inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground";
58
+ // Borderless in the header, like the icon buttons and the language switcher;
59
+ // bordered in the drawer, where the trigger spans it.
60
+ const triggerClass =
61
+ variant === "bar"
62
+ ? "inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full px-3 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground group-open:bg-muted group-open:text-foreground"
63
+ : "inline-flex h-9 w-full cursor-pointer items-center gap-1.5 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground";
49
64
  const menuRowClass =
50
65
  "flex w-full items-start gap-2.5 rounded-md px-2 py-1.5 text-start text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=true]:text-foreground";
51
66
  ---
52
67
 
53
68
  {
54
69
  selector.items.length > 0 && (
55
- <details class="group relative" data-blume-dropdown>
70
+ <details
71
+ class:list={["group relative", variant === "drawer" && "w-full"]}
72
+ data-blume-dropdown
73
+ >
56
74
  <summary
57
75
  aria-label={summaryLabel}
58
- class={`${iconButton} list-none [&::-webkit-details-marker]:hidden`}
76
+ class:list={[triggerClass, "list-none [&::-webkit-details-marker]:hidden"]}
59
77
  >
60
78
  {active?.icon && <Icon name={active.icon} size={16} />}
61
79
  <span class={labelClass}>{label}</span>
@@ -66,7 +84,15 @@ const menuRowClass =
66
84
  />
67
85
  </summary>
68
86
  <div
69
- class={`absolute z-50 mt-2 min-w-56 max-w-[calc(100vw-1rem)] rounded-blume border border-border bg-background p-1 shadow-xl ${align === "end" ? "end-0" : "start-0"}`}
87
+ class:list={[
88
+ "absolute z-50 mt-2 rounded-blume border border-border bg-background p-1 shadow-xl",
89
+ variant === "drawer"
90
+ ? "inset-x-0"
91
+ : [
92
+ "min-w-56 max-w-[calc(100vw-1rem)]",
93
+ align === "end" ? "end-0" : "start-0",
94
+ ],
95
+ ]}
70
96
  data-blume-dropdown-panel
71
97
  >
72
98
  {selector.items.map((item) => (
@@ -1,14 +1,22 @@
1
1
  ---
2
+ import data from "blume:data";
3
+
2
4
  import { EN_UI } from "../../core/i18n-ui.ts";
3
5
  import type { UIStrings } from "../../core/i18n-ui.ts";
4
6
  import Icon from "../Icon.astro";
7
+ import { COMMENT_MAX_LENGTH } from "./page-feedback.ts";
5
8
 
6
9
  interface Props {
10
+ /** Ask for a written comment after the rating (`feedback.comments`). */
11
+ comments?: boolean;
7
12
  /** Localized feedback labels. */
8
13
  strings?: UIStrings["feedback"];
9
14
  }
10
15
 
11
- const { strings } = Astro.props;
16
+ const { comments = false, strings } = Astro.props;
17
+ // Answers go through analytics, so under a consent layer the rating waits,
18
+ // hidden, until the reader allows analytics (`page-feedback.ts` shows it).
19
+ const waitsForConsent = data.config.consent !== null;
12
20
  // Merge over the English baseline per key (rather than `strings ?? …`) so a
13
21
  // partial — or empty `{}` — strings object still resolves every label to a
14
22
  // default, matching the pattern PageActions and Search use.
@@ -20,61 +28,58 @@ const buttonClass =
20
28
 
21
29
  <section
22
30
  aria-label={f.question}
23
- class="mx-auto mt-12 flex max-w-content items-center justify-between gap-4 border-border border-t pt-6"
31
+ class="mx-auto mt-12 max-w-content border-border border-t pt-6"
24
32
  data-blume-page-feedback
33
+ hidden={waitsForConsent}
25
34
  >
26
- <p class="text-muted-foreground text-sm">{f.question}</p>
27
- <div class="flex items-center gap-2" data-feedback-actions>
28
- <button class={buttonClass} data-feedback-value="yes" type="button">
29
- <Icon name="thumbs-up" size={16} />
30
- {f.yes}
31
- </button>
32
- <button class={buttonClass} data-feedback-value="no" type="button">
33
- <Icon name="thumbs-down" size={16} />
34
- {f.no}
35
- </button>
35
+ <div class="flex items-center justify-between gap-4">
36
+ <p class="text-muted-foreground text-sm">{f.question}</p>
37
+ <div class="flex items-center gap-2" data-feedback-actions>
38
+ <button class={buttonClass} data-feedback-value="yes" type="button">
39
+ <Icon name="thumbs-up" size={16} />
40
+ {f.yes}
41
+ </button>
42
+ <button class={buttonClass} data-feedback-value="no" type="button">
43
+ <Icon name="thumbs-down" size={16} />
44
+ {f.no}
45
+ </button>
46
+ </div>
47
+ <p
48
+ class="hidden text-muted-foreground text-sm focus:outline-none"
49
+ data-feedback-thanks
50
+ role="status"
51
+ tabindex="-1"
52
+ >
53
+ {f.thanks}
54
+ </p>
36
55
  </div>
37
- <p
38
- class="hidden text-muted-foreground text-sm focus:outline-none"
39
- data-feedback-thanks
40
- role="status"
41
- tabindex="-1"
42
- >
43
- {f.thanks}
44
- </p>
56
+ {
57
+ comments && (
58
+ <form class="mt-4 grid gap-2" data-feedback-form hidden>
59
+ <label class="text-muted-foreground text-sm" for="blume-feedback-comment">
60
+ {f.comment}
61
+ </label>
62
+ <textarea
63
+ class="w-full resize-y rounded-blume border border-border bg-background px-3 py-2 text-foreground text-sm outline-none transition-colors focus:border-foreground"
64
+ id="blume-feedback-comment"
65
+ maxlength={COMMENT_MAX_LENGTH}
66
+ name="comment"
67
+ rows="3"
68
+ ></textarea>
69
+ <div>
70
+ <button class={buttonClass} type="submit">
71
+ {f.send}
72
+ </button>
73
+ </div>
74
+ </form>
75
+ )
76
+ }
45
77
  </section>
46
78
 
47
79
  <script>
48
- import { track } from "./analytics-client.ts";
49
-
50
- // Run on the initial load and again after every client-router swap, which
51
- // rebuilds the rating widget from server-rendered markup — the handlers
52
- // below always bind to the freshly swapped-in elements.
53
- const initFeedback = () => {
54
- const root = document.querySelector("[data-blume-page-feedback]");
55
- if (!root) {
56
- return;
57
- }
58
- const actions = root.querySelector("[data-feedback-actions]");
59
- const thanks = root.querySelector("[data-feedback-thanks]");
60
- for (const button of root.querySelectorAll("[data-feedback-value]")) {
61
- button.addEventListener("click", () => {
62
- track("feedback", {
63
- helpful: button.getAttribute("data-feedback-value") ?? "",
64
- path: location.pathname,
65
- title: document.title,
66
- });
67
- actions?.classList.add("hidden");
68
- thanks?.classList.remove("hidden");
69
- // The clicked button just disappeared under focus; hand focus to the
70
- // (live) thanks line so the reader isn't dropped at the top of the page.
71
- if (thanks instanceof HTMLElement) {
72
- thanks.focus();
73
- }
74
- });
75
- }
76
- };
80
+ import { initFeedback, syncFeedbackVisibility } from "./page-feedback.ts";
77
81
 
78
82
  initFeedback();
79
83
  document.addEventListener("astro:after-swap", initFeedback);
84
+ addEventListener("blume:consent", syncFeedbackVisibility);
80
85
  </script>
@@ -5,8 +5,8 @@ import { createIconSprite } from "../icon-sprite.ts";
5
5
  // page that wants Blume's chrome (document shell, header, theme, fonts) without
6
6
  // the docs sidebar + prose + TOC grid that RootLayout hard-codes. It renders the
7
7
  // `<head>`, theme/banner pre-paint scripts, banner, and header, then a single
8
- // full-width `<slot />` for the body (plus an optional `footer` slot rendered
9
- // after `<main>`). Import it into a custom page and pull props from `blume:data`:
8
+ // full-width `<slot />` for the body, then an optional `footer` slot, or the
9
+ // site footer `footer` configures when the page fills no slot. Import it into a custom page and pull props from `blume:data`:
10
10
  //
11
11
  // import PageLayout from "blume/components/layout/PageLayout.astro";
12
12
  // import data from "blume:data";
@@ -40,11 +40,18 @@ import { buildStructuredData } from "../../seo/jsonld.ts";
40
40
  import { normalizeXHandle } from "../../seo/x-handle.ts";
41
41
  import Icon from "../Icon.astro";
42
42
  import { stripBase, withBase, withMountedBase } from "../islands/base-path.ts";
43
+ import { routeSetFor } from "../../core/locale-links.ts";
44
+ import {
45
+ browserLanguageTargets,
46
+ LOCALE_REDIRECT_SCRIPT,
47
+ } from "./locale-redirect.ts";
43
48
  import { pageDirection, pageLocale } from "./page-locale.ts";
44
49
  import { searchLocaleFor } from "./search-locale.ts";
45
50
  import "blume:theme";
46
51
  import { ClientRouter } from "astro:transitions";
47
52
  import Analytics from "./Analytics.astro";
53
+ import ConsentBanner from "./ConsentBanner.astro";
54
+ import ConsentHead from "./ConsentHead.astro";
48
55
  import Banner from "./Banner.astro";
49
56
  import DiscoveryLinks from "./DiscoveryLinks.astro";
50
57
  import Favicon from "./Favicon.astro";
@@ -57,6 +64,9 @@ import {
57
64
  import Header from "./Header.astro";
58
65
  import { currentTabForRoute } from "./nav-utils.ts";
59
66
  import NavTabMenu from "./NavTabMenu.astro";
67
+ import DrawerSwitchers from "./DrawerSwitchers.astro";
68
+ import SiteFooter from "./SiteFooter.astro";
69
+ import SiteMetatags from "./SiteMetatags.astro";
60
70
  import WebMcp from "./WebMcp.astro";
61
71
 
62
72
  interface Props {
@@ -195,6 +205,8 @@ const clientDataJson = clientData
195
205
  : null;
196
206
 
197
207
  const strings = ui ?? EN_UI;
208
+ // The consent adapter (`consent`), site-wide: analytics waits for the reader.
209
+ const consent = data.config.consent;
198
210
  // Merge over the English defaults so a label missing from a translation (or
199
211
  // from a not-yet-regenerated snapshot) still renders instead of coming out
200
212
  // blank — the PageActions pattern.
@@ -291,6 +303,25 @@ const bannerKey = banner?.dismissible ? banner.key : null;
291
303
  // Start the page's icon sprite before anything renders an icon (see
292
304
  // icon-sprite.ts); <IconSprite /> at the end of the body emits it.
293
305
  createIconSprite(Astro.locals);
306
+
307
+ // A visitor landing on the default language's home page goes to their
308
+ // browser's language (`i18n.routeByBrowserLanguage`, see locale-redirect.ts).
309
+ const localeTargets = browserLanguageTargets({
310
+ basePath: data.config.basePath,
311
+ i18n: data.config.i18n,
312
+ route,
313
+ routes: routeSetFor(data.routes),
314
+ });
315
+ const localeRedirect = localeTargets
316
+ ? JSON.stringify(
317
+ Object.fromEntries(
318
+ Object.entries(localeTargets).map(([code, path]) => [
319
+ code,
320
+ withMountedBase(path),
321
+ ])
322
+ )
323
+ )
324
+ : null;
294
325
  ---
295
326
 
296
327
  <!doctype html>
@@ -298,6 +329,16 @@ createIconSprite(Astro.locals);
298
329
  <head>
299
330
  <meta charset="utf-8" />
300
331
  <meta name="viewport" content="width=device-width, initial-scale=1" />
332
+ {
333
+ localeRedirect && (
334
+ <script
335
+ data-default={data.config.i18n?.defaultLocale}
336
+ data-targets={localeRedirect}
337
+ is:inline
338
+ set:html={LOCALE_REDIRECT_SCRIPT}
339
+ />
340
+ )
341
+ }
301
342
  {/* Client-side navigation, matching RootLayout: same-origin link clicks
302
343
  swap the DOM in place instead of tearing the document down, so no
303
344
  browser paints a blank frame between pages. */}
@@ -363,6 +404,7 @@ createIconSprite(Astro.locals);
363
404
  {description && <meta content={description} name="twitter:description" />}
364
405
  {xSite && <meta content={xSite} name="twitter:site" />}
365
406
  {xCreator && <meta content={xCreator} name="twitter:creator" />}
407
+ <SiteMetatags tags={data.config.metatags} />
366
408
  {/* Agent discovery on every page, so a custom page built on PageLayout is
367
409
  covered without passing a prop, the same way the docs pages are. */}
368
410
  <DiscoveryLinks discovery={discovery} markdownMirror={markdownMirror} />
@@ -381,6 +423,7 @@ createIconSprite(Astro.locals);
381
423
  )
382
424
  }
383
425
  <script data-mode={themeMode} is:inline set:html={THEME_INIT_SCRIPT} />
426
+ {consent && <ConsentHead consent={consent} />}
384
427
  <Analytics analytics={analytics} />
385
428
  </head>
386
429
  <body class="bg-background font-sans text-foreground antialiased">
@@ -409,8 +452,27 @@ createIconSprite(Astro.locals);
409
452
  transparent={transparentHeader}
410
453
  />
411
454
  <main id="blume-content"><slot /></main>
412
- <slot name="footer" />
455
+ {
456
+ Astro.slots.has("footer") ? (
457
+ <slot name="footer" />
458
+ ) : (
459
+ <SiteFooter
460
+ footer={data.config.footer}
461
+ locale={locale}
462
+ navigation={navigation}
463
+ ui={strings}
464
+ />
465
+ )
466
+ }
413
467
  <WebMcp />
468
+ {
469
+ consent?.kind === "native" && (
470
+ <ConsentBanner
471
+ policy={consent.options.policy}
472
+ strings={strings.consent}
473
+ />
474
+ )
475
+ }
414
476
  {
415
477
  // Mobile nav for chrome-only pages: the header's inline tab bar is hidden
416
478
  // below `lg` and there's no sidebar drawer, so surface the tabs in a
@@ -425,6 +487,12 @@ createIconSprite(Astro.locals);
425
487
  data-blume-nav-drawer
426
488
  id="blume-nav-drawer"
427
489
  >
490
+ <DrawerSwitchers
491
+ localeSwitch={localeSwitch}
492
+ route={route}
493
+ selectors={navigation.selectors}
494
+ switcherStrings={strings.languageSwitcher}
495
+ />
428
496
  <nav aria-label={navStrings.sections}>
429
497
  <ul class="m-0 list-none p-0">
430
498
  {navigation.tabs.map((tab) => (
@@ -4,6 +4,7 @@ import { createIconSprite } from "../icon-sprite.ts";
4
4
  import "blume:theme";
5
5
  import data from "blume:data";
6
6
  import type {
7
+ BlumeBanner,
7
8
  BlumeDataConfig,
8
9
  BlumeFavicon,
9
10
  DiscoveryHeadLinks,
@@ -14,6 +15,8 @@ import { NEW_TAB_HINT_ID } from "../../core/new-tab.ts";
14
15
  import type { FontHead } from "../../theme/fonts.ts";
15
16
  import type { Navigation } from "../../core/types.ts";
16
17
  import Analytics from "./Analytics.astro";
18
+ import ConsentBanner from "./ConsentBanner.astro";
19
+ import ConsentHead from "./ConsentHead.astro";
17
20
  import WebMcp from "./WebMcp.astro";
18
21
  import Banner from "./Banner.astro";
19
22
  import DiscoveryLinks from "./DiscoveryLinks.astro";
@@ -27,6 +30,7 @@ import {
27
30
  import Header from "./Header.astro";
28
31
  import { pageDirection, pageLocale } from "./page-locale.ts";
29
32
  import { searchLocaleFor } from "./search-locale.ts";
33
+ import SiteMetatags from "./SiteMetatags.astro";
30
34
 
31
35
  // A minimal shell for the Scalar API/AsyncAPI reference: Blume's banner + navbar
32
36
  // on top, then a full-height region the reference mounts into. Unlike RootLayout
@@ -50,12 +54,7 @@ interface Props {
50
54
  } | null;
51
55
  favicon?: BlumeFavicon | null;
52
56
  appleIcon?: BlumeFavicon | null;
53
- banner?: {
54
- content: string;
55
- link?: { text: string; href: string };
56
- dismissible: boolean;
57
- key: string;
58
- } | null;
57
+ banner?: BlumeBanner | null;
59
58
  analytics?: BlumeDataConfig["analytics"] | null;
60
59
  navigation: Navigation;
61
60
  route: string;
@@ -110,6 +109,8 @@ const locale = pageLocale(data.config.i18n, localeProp, Astro.currentLocale);
110
109
  const dir = dirProp ?? pageDirection(data.config.i18n, locale);
111
110
 
112
111
  const strings = ui ?? EN_UI;
112
+ // The consent adapter (`consent`), site-wide: analytics waits for the reader.
113
+ const consent = data.config.consent;
113
114
  const navStrings = { ...EN_UI.nav, ...strings.nav };
114
115
  // Scope search to the reference page's language on a multi-locale site.
115
116
  const searchLocale = searchLocaleFor(data.config.i18n, locale);
@@ -128,6 +129,7 @@ createIconSprite(Astro.locals);
128
129
  <meta name="viewport" content="width=device-width, initial-scale=1" />
129
130
  {noindex && <meta name="robots" content="noindex" />}
130
131
  <title>{pageTitle}</title>
132
+ <SiteMetatags tags={data.config.metatags} />
131
133
  <Favicon favicon={favicon} appleIcon={appleIcon} />
132
134
  <Fonts cssVars={fontCssVars ?? []} />
133
135
  <DiscoveryLinks discovery={discovery} />
@@ -137,6 +139,7 @@ createIconSprite(Astro.locals);
137
139
  )
138
140
  }
139
141
  <script data-mode={themeMode} is:inline set:html={THEME_INIT_SCRIPT} />
142
+ {consent && <ConsentHead consent={consent} />}
140
143
  <Analytics analytics={analytics} />
141
144
  </head>
142
145
  <body class="bg-background font-sans text-foreground antialiased">
@@ -170,6 +173,14 @@ createIconSprite(Astro.locals);
170
173
  <script is:inline set:html={SCALAR_THEME_INIT_SCRIPT} />
171
174
  </div>
172
175
  <WebMcp />
176
+ {
177
+ consent?.kind === "native" && (
178
+ <ConsentBanner
179
+ policy={consent.options.policy}
180
+ strings={strings.consent}
181
+ />
182
+ )
183
+ }
173
184
  <IconSprite />
174
185
  </body>
175
186
  </html>
@@ -0,0 +1,42 @@
1
+ ---
2
+ import type { RelatedLink } from "../../core/related.ts";
3
+ import Card from "../content/Card.astro";
4
+ import CardGroup from "../content/CardGroup.astro";
5
+
6
+ /**
7
+ * A page's `related` links, as cards at its foot: each linked page's title
8
+ * and description, or a URL's host for a link off the site (see
9
+ * `core/related.ts`).
10
+ */
11
+ interface Props {
12
+ links: RelatedLink[];
13
+ /** The heading, from the `page.related` UI string. */
14
+ title: string;
15
+ }
16
+
17
+ const { links, title } = Astro.props;
18
+ ---
19
+
20
+ {
21
+ links.length > 0 && (
22
+ <nav
23
+ aria-labelledby="blume-related-pages"
24
+ class="mx-auto mt-10 max-w-content"
25
+ data-blume-related
26
+ >
27
+ <h2
28
+ class="font-medium text-foreground text-sm"
29
+ id="blume-related-pages"
30
+ >
31
+ {title}
32
+ </h2>
33
+ <CardGroup>
34
+ {links.map((link) => (
35
+ <Card href={link.href} title={link.title}>
36
+ {link.description}
37
+ </Card>
38
+ ))}
39
+ </CardGroup>
40
+ </nav>
41
+ )
42
+ }