blume 2.0.3 → 2.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 (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
@@ -0,0 +1,592 @@
1
+ /**
2
+ * Narration's reading script: what "Listen to this page" says, in order, and
3
+ * where each spoken sentence sits in the page's text.
4
+ *
5
+ * One walker serves both sides. `blume build` runs it over the built HTML
6
+ * (parsed with node-html-parser) to generate a clip per sentence, and the
7
+ * player runs it over the live `<article>` to highlight the sentence being
8
+ * read. Both see the same markup and apply the same rules, so they agree on
9
+ * every block's text; the sentence split is only ever computed once per
10
+ * engine (by the build for generated audio, by the browser for its own
11
+ * voices), so an ICU difference between Node and the browser can't move a
12
+ * highlight.
13
+ *
14
+ * The rules follow what reads well aloud: the title, description, headings,
15
+ * and prose are read in order; callouts, steps, tabs, and collapsible
16
+ * sections are announced with a short spoken cue; code, tables, media,
17
+ * diagrams, math, API field tables, and anything marked
18
+ * `data-blume-narration="skip"` are left out.
19
+ */
20
+
21
+ /**
22
+ * The slice of a DOM-like tree the walker reads. The browser DOM and
23
+ * node-html-parser both satisfy it through a small adapter, so the walker
24
+ * never touches either API directly.
25
+ */
26
+ export interface NarrationTree<N> {
27
+ /** An element's attribute, or `null` when it is absent. */
28
+ attr: (node: N, name: string) => string | null;
29
+ children: (node: N) => readonly N[];
30
+ /** An element's lowercase tag name; `null` for text and other nodes. */
31
+ tag: (node: N) => string | null;
32
+ /** A text node's decoded text; `null` for elements and other nodes. */
33
+ text: (node: N) => string | null;
34
+ }
35
+
36
+ /**
37
+ * The spoken cues, already localized for the page. `{n}` in `step` is the
38
+ * step's number and `{title}` in `tab` is the tab's label. An empty string
39
+ * silences that cue.
40
+ */
41
+ export interface NarrationCues {
42
+ danger: string;
43
+ info: string;
44
+ note: string;
45
+ section: string;
46
+ step: string;
47
+ success: string;
48
+ tab: string;
49
+ tip: string;
50
+ warning: string;
51
+ }
52
+
53
+ /**
54
+ * A run of text read as one unit: a paragraph, heading, list item, or any
55
+ * other block. `nodes` are the text nodes it was read from; `nodeOf[i]` and
56
+ * `offsetOf[i]` locate character `i` of `text` in them, so a sentence's
57
+ * `[start, end)` maps back to a DOM range for highlighting.
58
+ */
59
+ export interface NarrationBlock<N> {
60
+ nodeOf: number[];
61
+ nodes: N[];
62
+ offsetOf: number[];
63
+ text: string;
64
+ }
65
+
66
+ /** One step of the reading order: a spoken cue, or a block of page text. */
67
+ export type NarrationItem =
68
+ | { block: number; kind: "block" }
69
+ | { kind: "cue"; text: string };
70
+
71
+ /** What {@link extractNarration} reads from a page. */
72
+ export interface NarrationExtract<N> {
73
+ blocks: NarrationBlock<N>[];
74
+ items: NarrationItem[];
75
+ }
76
+
77
+ /**
78
+ * One spoken unit. A sentence carries its block and its `[start, end)` in
79
+ * that block's text; a cue carries `block: null`. `text` is what is spoken.
80
+ */
81
+ export interface NarrationSegment {
82
+ block: number | null;
83
+ end: number;
84
+ start: number;
85
+ text: string;
86
+ }
87
+
88
+ /**
89
+ * The per-page manifest `blume build` writes for generated narration. The
90
+ * player aligns `blocks` (the build's block texts) with the live page by
91
+ * text, then plays one clip per segment. A sentence names its block and range;
92
+ * a cue carries its spoken `text` instead.
93
+ */
94
+ export interface NarrationManifest {
95
+ blocks: string[];
96
+ segments: NarrationManifestSegment[];
97
+ version: 1;
98
+ }
99
+
100
+ /** One clip of a {@link NarrationManifest}: a sentence or a cue. */
101
+ export type NarrationManifestSegment =
102
+ | { audio: string; block: number; end: number; start: number }
103
+ | { audio: string; text: string };
104
+
105
+ /**
106
+ * A page needs at least this many spoken characters (title and description
107
+ * included) to offer narration: roughly 50 English words. Shorter pages, and
108
+ * pages that are mostly code, tables, or API fields, get no player.
109
+ */
110
+ export const NARRATION_MIN_CHARS = 280;
111
+
112
+ /**
113
+ * Sentences longer than this are split at a clause, then a word boundary.
114
+ * Browser speech engines drop an utterance that runs past about 15 seconds,
115
+ * and shorter clips also keep the highlight moving.
116
+ */
117
+ export const NARRATION_MAX_SEGMENT_CHARS = 220;
118
+
119
+ /** The attribute that keeps an element (and everything in it) out of narration. */
120
+ export const NARRATION_SKIP_ATTRIBUTE = "data-blume-narration";
121
+
122
+ // Inline elements continue the current block; every other element starts and
123
+ // ends one, so a paragraph's text never runs into the heading after it. The
124
+ // replaced and form elements are inline too: an icon or a button mid-sentence
125
+ // is skipped without splitting the sentence around it.
126
+ const INLINE_TAGS = new Set([
127
+ "a",
128
+ "abbr",
129
+ "audio",
130
+ "button",
131
+ "canvas",
132
+ "embed",
133
+ "iframe",
134
+ "img",
135
+ "input",
136
+ "math",
137
+ "noscript",
138
+ "object",
139
+ "picture",
140
+ "script",
141
+ "select",
142
+ "style",
143
+ "svg",
144
+ "template",
145
+ "textarea",
146
+ "video",
147
+ "b",
148
+ "bdi",
149
+ "bdo",
150
+ "cite",
151
+ "code",
152
+ "data",
153
+ "del",
154
+ "dfn",
155
+ "em",
156
+ "font",
157
+ "i",
158
+ "ins",
159
+ "kbd",
160
+ "label",
161
+ "mark",
162
+ "q",
163
+ "s",
164
+ "samp",
165
+ "small",
166
+ "span",
167
+ "strong",
168
+ "sub",
169
+ "sup",
170
+ "time",
171
+ "u",
172
+ "var",
173
+ "wbr",
174
+ ]);
175
+
176
+ // Content that doesn't read well aloud, or isn't content at all.
177
+ const SKIPPED_TAGS = new Set([
178
+ "audio",
179
+ "blume-diff",
180
+ "blume-mermaid",
181
+ "button",
182
+ "canvas",
183
+ "dialog",
184
+ "embed",
185
+ "figure",
186
+ "iframe",
187
+ "img",
188
+ "input",
189
+ "math",
190
+ "nav",
191
+ "noscript",
192
+ "object",
193
+ "picture",
194
+ "pre",
195
+ "script",
196
+ "select",
197
+ "style",
198
+ "svg",
199
+ "table",
200
+ "template",
201
+ "textarea",
202
+ "video",
203
+ ]);
204
+
205
+ // Classes whose text is for screen readers or renderers, not for listening:
206
+ // visually hidden hints, and KaTeX's glyph layer (its MathML is `math`).
207
+ const SKIPPED_CLASSES = ["sr-only", "katex"];
208
+
209
+ const CALLOUT_KINDS = new Set([
210
+ "danger",
211
+ "info",
212
+ "note",
213
+ "success",
214
+ "tip",
215
+ "warning",
216
+ ]);
217
+
218
+ const isCalloutKind = (
219
+ kind: string
220
+ ): kind is "danger" | "info" | "note" | "success" | "tip" | "warning" =>
221
+ CALLOUT_KINDS.has(kind);
222
+
223
+ const WHITESPACE = /\s/u;
224
+
225
+ const hasClass = (classes: string | null, name: string): boolean =>
226
+ classes !== null && classes.split(/\s+/u).includes(name);
227
+
228
+ const isSkipped = <N>(
229
+ tree: NarrationTree<N>,
230
+ node: N,
231
+ tag: string
232
+ ): boolean => {
233
+ if (SKIPPED_TAGS.has(tag)) {
234
+ return true;
235
+ }
236
+ if (tree.attr(node, NARRATION_SKIP_ATTRIBUTE) === "skip") {
237
+ return true;
238
+ }
239
+ if (tree.attr(node, "aria-hidden") === "true") {
240
+ return true;
241
+ }
242
+ const classes = tree.attr(node, "class");
243
+ return SKIPPED_CLASSES.some((name) => hasClass(classes, name));
244
+ };
245
+
246
+ /** The spoken cue that introduces an element, or `null` for none. */
247
+ const cueFor = <N>(
248
+ tree: NarrationTree<N>,
249
+ node: N,
250
+ tag: string,
251
+ cues: NarrationCues,
252
+ stepNumber: number
253
+ ): string | null => {
254
+ const callout = tree.attr(node, "data-blume-callout");
255
+ if (callout !== null) {
256
+ return isCalloutKind(callout) ? cues[callout] : cues.note;
257
+ }
258
+ if (tree.attr(node, "data-blume-step") !== null) {
259
+ return cues.step.replaceAll("{n}", String(stepNumber));
260
+ }
261
+ if (tree.attr(node, "data-blume-tab-panel") !== null) {
262
+ const title = tree.attr(node, "data-title");
263
+ return title ? cues.tab.replaceAll("{title}", title) : null;
264
+ }
265
+ return tag === "details" ? cues.section : null;
266
+ };
267
+
268
+ /**
269
+ * Walk `root` and return its reading order: each block of text, with the
270
+ * cues that introduce callouts, steps, tabs, and collapsible sections.
271
+ * Whitespace collapses to single spaces, as it renders.
272
+ */
273
+ export const extractNarration = <N>(
274
+ root: N,
275
+ tree: NarrationTree<N>,
276
+ cues: NarrationCues
277
+ ): NarrationExtract<N> => {
278
+ const blocks: NarrationBlock<N>[] = [];
279
+ const items: NarrationItem[] = [];
280
+
281
+ let text = "";
282
+ let nodes: N[] = [];
283
+ let nodeOf: number[] = [];
284
+ let offsetOf: number[] = [];
285
+ let pendingSpace = false;
286
+ // Cues wait here until their element yields text: a tab of nothing but
287
+ // code, or a step holding only a snippet, isn't announced into silence.
288
+ let pendingCues: string[] = [];
289
+
290
+ const flush = (): void => {
291
+ if (text) {
292
+ for (const cue of pendingCues) {
293
+ items.push({ kind: "cue", text: cue });
294
+ }
295
+ pendingCues = [];
296
+ items.push({ block: blocks.length, kind: "block" });
297
+ blocks.push({ nodeOf, nodes, offsetOf, text });
298
+ }
299
+ text = "";
300
+ nodes = [];
301
+ nodeOf = [];
302
+ offsetOf = [];
303
+ pendingSpace = false;
304
+ };
305
+
306
+ const append = (node: N, value: string): void => {
307
+ let index = -1;
308
+ for (let offset = 0; offset < value.length; offset += 1) {
309
+ const char = value.charAt(offset);
310
+ if (WHITESPACE.test(char)) {
311
+ pendingSpace = text.length > 0;
312
+ continue;
313
+ }
314
+ if (index === -1) {
315
+ index = nodes.length;
316
+ nodes.push(node);
317
+ }
318
+ // A collapsed run of whitespace maps to the character after it, so a
319
+ // range that starts on the space starts on the word instead.
320
+ if (pendingSpace) {
321
+ text += " ";
322
+ nodeOf.push(index);
323
+ offsetOf.push(offset);
324
+ pendingSpace = false;
325
+ }
326
+ text += char;
327
+ nodeOf.push(index);
328
+ offsetOf.push(offset);
329
+ }
330
+ };
331
+
332
+ const visit = (node: N, stepNumber: number): void => {
333
+ const value = tree.text(node);
334
+ if (value !== null) {
335
+ append(node, value);
336
+ return;
337
+ }
338
+ const tag = tree.tag(node);
339
+ if (tag === "br") {
340
+ pendingSpace = text.length > 0;
341
+ return;
342
+ }
343
+ // A node with no tag (a parsed document's root) is transparent.
344
+ const inline = tag === null || INLINE_TAGS.has(tag);
345
+ if (tag !== null && isSkipped(tree, node, tag)) {
346
+ if (!inline) {
347
+ flush();
348
+ }
349
+ return;
350
+ }
351
+ if (!inline) {
352
+ flush();
353
+ }
354
+ const cue = tag === null ? null : cueFor(tree, node, tag, cues, stepNumber);
355
+ const cueSlot = pendingCues.length;
356
+ if (cue) {
357
+ pendingCues.push(cue);
358
+ }
359
+ // Steps are numbered among their siblings, the way the CSS counter that
360
+ // draws their markers counts them.
361
+ let steps = 0;
362
+ for (const child of tree.children(node)) {
363
+ if (tree.attr(child, "data-blume-step") !== null) {
364
+ steps += 1;
365
+ }
366
+ visit(child, steps);
367
+ }
368
+ if (!inline) {
369
+ flush();
370
+ }
371
+ // Still pending: nothing inside was read, so drop the cue.
372
+ pendingCues = pendingCues.slice(0, cueSlot);
373
+ };
374
+
375
+ visit(root, 0);
376
+ flush();
377
+ return { blocks, items };
378
+ };
379
+
380
+ const CLAUSE_BREAK = /[,;:–—]\s/gu;
381
+
382
+ /**
383
+ * Split `[start, end)` of `text` into pieces no longer than `max`: after the
384
+ * last clause mark that leaves a piece at least 40% of `max` long, else at
385
+ * the last space, else hard at `max`.
386
+ */
387
+ const splitLong = (
388
+ text: string,
389
+ start: number,
390
+ end: number,
391
+ max: number
392
+ ): [number, number][] => {
393
+ const pieces: [number, number][] = [];
394
+ let from = start;
395
+ while (end - from > max) {
396
+ const window = text.slice(from, from + max);
397
+ const floor = Math.floor(max * 0.4);
398
+ let cut = -1;
399
+ for (const match of window.matchAll(CLAUSE_BREAK)) {
400
+ if (match.index >= floor) {
401
+ cut = match.index + 1;
402
+ }
403
+ }
404
+ if (cut === -1) {
405
+ const space = window.lastIndexOf(" ");
406
+ cut = space >= floor ? space : max;
407
+ }
408
+ pieces.push([from, from + cut]);
409
+ from += cut;
410
+ while (from < end && text.charAt(from) === " ") {
411
+ from += 1;
412
+ }
413
+ }
414
+ pieces.push([from, end]);
415
+ return pieces;
416
+ };
417
+
418
+ /**
419
+ * The sentence spans of `text`. ICU can end a sentence at an ASCII `.`, `?`,
420
+ * or `!` with no space after it, which splits inline code like
421
+ * `?install=windows` in two; a break that isn't followed by whitespace is
422
+ * merged back into one sentence.
423
+ * Full-width terminators (`。`, `?`) end a sentence without a space and keep
424
+ * their breaks.
425
+ */
426
+ const sentenceSpans = (
427
+ segmenter: Intl.Segmenter,
428
+ text: string
429
+ ): [number, number][] => {
430
+ const spans: [number, number][] = [];
431
+ let merge = false;
432
+ for (const { index, segment } of segmenter.segment(text)) {
433
+ const end = index + segment.length;
434
+ const last = spans.at(-1);
435
+ if (merge && last) {
436
+ last[1] = end;
437
+ } else {
438
+ spans.push([index, end]);
439
+ }
440
+ merge = end < text.length && /[.?!]$/u.test(segment);
441
+ }
442
+ return spans;
443
+ };
444
+
445
+ /**
446
+ * Split an extract into spoken segments: a cue as it is, and each block into
447
+ * its sentences for `lang` (long ones broken further, see
448
+ * {@link NARRATION_MAX_SEGMENT_CHARS}).
449
+ */
450
+ export const segmentNarration = <N>(
451
+ extract: NarrationExtract<N>,
452
+ lang: string,
453
+ max: number = NARRATION_MAX_SEGMENT_CHARS
454
+ ): NarrationSegment[] => {
455
+ const segmenter = new Intl.Segmenter(lang, { granularity: "sentence" });
456
+ const segments: NarrationSegment[] = [];
457
+ for (const item of extract.items) {
458
+ if (item.kind === "cue") {
459
+ segments.push({ block: null, end: 0, start: 0, text: item.text });
460
+ continue;
461
+ }
462
+ const text = extract.blocks[item.block]?.text ?? "";
463
+ // A sentence span carries its trailing space (ICU attaches whitespace to
464
+ // the sentence it follows, and a block never starts with one), so only
465
+ // the end needs trimming.
466
+ for (const [start, spanEnd] of sentenceSpans(segmenter, text)) {
467
+ let end = spanEnd;
468
+ while (end > start && text.charAt(end - 1) === " ") {
469
+ end -= 1;
470
+ }
471
+ for (const [from, to] of splitLong(text, start, end, max)) {
472
+ segments.push({
473
+ block: item.block,
474
+ end: to,
475
+ start: from,
476
+ text: text.slice(from, to),
477
+ });
478
+ }
479
+ }
480
+ }
481
+ return segments;
482
+ };
483
+
484
+ /** How many characters the page's blocks will speak, cues excluded. */
485
+ export const narrationLength = <N>(extract: NarrationExtract<N>): number =>
486
+ extract.blocks.reduce((total, block) => total + block.text.length, 0);
487
+
488
+ /** Whether a page has enough prose to offer narration. */
489
+ export const isNarratable = <N>(extract: NarrationExtract<N>): boolean =>
490
+ narrationLength(extract) >= NARRATION_MIN_CHARS;
491
+
492
+ const DENSE_SCRIPT_LANGS = new Set(["ja", "ko", "zh"]);
493
+
494
+ /**
495
+ * Characters read per second at 1x: about 170 words a minute for
496
+ * space-separated scripts, and a slower character rate where a character is a
497
+ * word or more. Estimates only; neither engine reports durations up front.
498
+ */
499
+ export const narrationCharsPerSecond = (lang: string): number => {
500
+ const [primary = ""] = lang.toLowerCase().replaceAll("_", "-").split("-");
501
+ return DENSE_SCRIPT_LANGS.has(primary) ? 7 : 15;
502
+ };
503
+
504
+ /** The page's estimated listening time at 1x, in whole minutes (at least 1). */
505
+ export const narrationMinutes = (chars: number, lang: string): number =>
506
+ Math.max(1, Math.round(chars / narrationCharsPerSecond(lang) / 60));
507
+
508
+ /**
509
+ * Map each manifest block onto the live page's blocks by exact text, in
510
+ * order, skipping page blocks the build didn't see (a script may have added
511
+ * some). A manifest block with no match maps to `-1`: its sentences still
512
+ * play, without a highlight.
513
+ */
514
+ export const alignBlocks = (
515
+ manifestBlocks: readonly string[],
516
+ pageBlocks: readonly string[]
517
+ ): number[] => {
518
+ const aligned: number[] = [];
519
+ let next = 0;
520
+ for (const text of manifestBlocks) {
521
+ const found = pageBlocks.indexOf(text, next);
522
+ aligned.push(found);
523
+ if (found !== -1) {
524
+ next = found + 1;
525
+ }
526
+ }
527
+ return aligned;
528
+ };
529
+
530
+ const isString = (value: unknown): value is string => typeof value === "string";
531
+
532
+ const isManifestSegment = (
533
+ value: unknown
534
+ ): value is NarrationManifestSegment => {
535
+ if (typeof value !== "object" || value === null) {
536
+ return false;
537
+ }
538
+ if (!("audio" in value) || typeof value.audio !== "string") {
539
+ return false;
540
+ }
541
+ if ("text" in value && typeof value.text === "string") {
542
+ return true;
543
+ }
544
+ return (
545
+ "block" in value &&
546
+ "start" in value &&
547
+ "end" in value &&
548
+ Number.isInteger(value.block) &&
549
+ Number.isInteger(value.start) &&
550
+ Number.isInteger(value.end)
551
+ );
552
+ };
553
+
554
+ const CUE_KEYS = [
555
+ "danger",
556
+ "info",
557
+ "note",
558
+ "section",
559
+ "step",
560
+ "success",
561
+ "tab",
562
+ "tip",
563
+ "warning",
564
+ ] as const;
565
+
566
+ /** Whether a value (the player's `data-cues` JSON) is a full set of cues. */
567
+ export const isNarrationCues = (value: unknown): value is NarrationCues => {
568
+ if (typeof value !== "object" || value === null) {
569
+ return false;
570
+ }
571
+ const fields = new Map<string, unknown>(Object.entries(value));
572
+ return CUE_KEYS.every((key) => isString(fields.get(key)));
573
+ };
574
+
575
+ /** Whether a fetched manifest has the shape the player plays. */
576
+ export const isNarrationManifest = (
577
+ value: unknown
578
+ ): value is NarrationManifest => {
579
+ if (typeof value !== "object" || value === null) {
580
+ return false;
581
+ }
582
+ return (
583
+ "version" in value &&
584
+ value.version === 1 &&
585
+ "blocks" in value &&
586
+ Array.isArray(value.blocks) &&
587
+ value.blocks.every(isString) &&
588
+ "segments" in value &&
589
+ Array.isArray(value.segments) &&
590
+ value.segments.every(isManifestSegment)
591
+ );
592
+ };
package/src/og/card.ts CHANGED
@@ -63,15 +63,16 @@ export interface OgFontFamilies {
63
63
  title?: string;
64
64
  }
65
65
 
66
- // Named presets resolve from the theme's own OKLCH table — Takumi parses the
67
- // full CSS color grammar, so the card renders exactly the accent the site
68
- // shows (a separate hand-synced hex palette used to drift: the card's "blue"
69
- // was Tailwind's, not Blume's). Anything else is handed to Takumi as-is, and
70
- // a genuinely malformed value fails the build with a parse error naming it.
66
+ // Named presets resolve from the theme's own OKLCH table, in their light
67
+ // shade since the card is light — Takumi parses the full CSS color grammar,
68
+ // so the card renders exactly the accent the site shows (a separate
69
+ // hand-synced hex palette used to drift: the card's "blue" was Tailwind's,
70
+ // not Blume's). Anything else is handed to Takumi as-is, and a genuinely
71
+ // malformed value fails the build with a parse error naming it.
71
72
  // `isAccentPreset` keeps a preset name like "constructor" from resolving up
72
73
  // the prototype chain.
73
74
  const resolveAccent = (accent: string): string =>
74
- isAccentPreset(accent) ? ACCENTS[accent] : accent;
75
+ isAccentPreset(accent) ? ACCENTS[accent].light : accent;
75
76
 
76
77
  export interface OgCardPalette {
77
78
  accent?: string;