blume 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/bin/blume.mjs +18 -0
  2. package/dist/cli/index.js +12696 -0
  3. package/dist/cli/index.js.map +145 -0
  4. package/dist/types/core/bridge.d.ts +24 -0
  5. package/dist/types/core/config.d.ts +35 -0
  6. package/dist/types/core/data.d.ts +129 -0
  7. package/dist/types/core/define-components.d.ts +27 -0
  8. package/dist/types/core/define-meta.d.ts +16 -0
  9. package/dist/types/core/deployment-env.d.ts +9 -0
  10. package/dist/types/core/diagnostics.d.ts +17 -0
  11. package/dist/types/core/i18n-ui.d.ts +500 -0
  12. package/dist/types/core/load-module.d.ts +7 -0
  13. package/dist/types/core/package-root.d.ts +17 -0
  14. package/dist/types/core/project.d.ts +9 -0
  15. package/dist/types/core/schema.d.ts +3451 -0
  16. package/dist/types/core/sources/types.d.ts +107 -0
  17. package/dist/types/core/types.d.ts +245 -0
  18. package/dist/types/core/ui-packs/ar.d.ts +3 -0
  19. package/dist/types/core/ui-packs/bg.d.ts +3 -0
  20. package/dist/types/core/ui-packs/bn.d.ts +3 -0
  21. package/dist/types/core/ui-packs/ca.d.ts +3 -0
  22. package/dist/types/core/ui-packs/cs.d.ts +3 -0
  23. package/dist/types/core/ui-packs/da.d.ts +3 -0
  24. package/dist/types/core/ui-packs/de.d.ts +3 -0
  25. package/dist/types/core/ui-packs/el.d.ts +3 -0
  26. package/dist/types/core/ui-packs/es.d.ts +3 -0
  27. package/dist/types/core/ui-packs/fa.d.ts +3 -0
  28. package/dist/types/core/ui-packs/fi.d.ts +3 -0
  29. package/dist/types/core/ui-packs/fr.d.ts +3 -0
  30. package/dist/types/core/ui-packs/he.d.ts +3 -0
  31. package/dist/types/core/ui-packs/hi.d.ts +3 -0
  32. package/dist/types/core/ui-packs/hr.d.ts +3 -0
  33. package/dist/types/core/ui-packs/hu.d.ts +3 -0
  34. package/dist/types/core/ui-packs/id.d.ts +3 -0
  35. package/dist/types/core/ui-packs/index.d.ts +13 -0
  36. package/dist/types/core/ui-packs/it.d.ts +3 -0
  37. package/dist/types/core/ui-packs/ja.d.ts +3 -0
  38. package/dist/types/core/ui-packs/ko.d.ts +3 -0
  39. package/dist/types/core/ui-packs/nl.d.ts +3 -0
  40. package/dist/types/core/ui-packs/no.d.ts +3 -0
  41. package/dist/types/core/ui-packs/pl.d.ts +3 -0
  42. package/dist/types/core/ui-packs/pt-br.d.ts +3 -0
  43. package/dist/types/core/ui-packs/pt.d.ts +3 -0
  44. package/dist/types/core/ui-packs/ro.d.ts +3 -0
  45. package/dist/types/core/ui-packs/ru.d.ts +3 -0
  46. package/dist/types/core/ui-packs/sk.d.ts +3 -0
  47. package/dist/types/core/ui-packs/sr.d.ts +3 -0
  48. package/dist/types/core/ui-packs/sv.d.ts +3 -0
  49. package/dist/types/core/ui-packs/th.d.ts +3 -0
  50. package/dist/types/core/ui-packs/tr.d.ts +3 -0
  51. package/dist/types/core/ui-packs/uk.d.ts +3 -0
  52. package/dist/types/core/ui-packs/vi.d.ts +3 -0
  53. package/dist/types/core/ui-packs/zh-tw.d.ts +3 -0
  54. package/dist/types/core/ui-packs/zh.d.ts +3 -0
  55. package/dist/types/core/version.d.ts +8 -0
  56. package/dist/types/index.d.ts +10 -0
  57. package/dist/types/migrate/mintlify/config.d.ts +2 -0
  58. package/dist/types/migrate/mintlify/i18n.d.ts +7 -0
  59. package/dist/types/theme/fonts.d.ts +163 -0
  60. package/docs/01-quickstart.mdx +99 -0
  61. package/docs/02-deployment.mdx +129 -0
  62. package/docs/advanced/api-reference.mdx +114 -0
  63. package/docs/advanced/blog.mdx +121 -0
  64. package/docs/advanced/changelog.mdx +113 -0
  65. package/docs/advanced/custom-pages.mdx +268 -0
  66. package/docs/advanced/meta.ts +7 -0
  67. package/docs/changelog/v0-1-0.mdx +12 -0
  68. package/docs/changelog/v0-2-0.mdx +16 -0
  69. package/docs/configuration/ai.mdx +228 -0
  70. package/docs/configuration/analytics.mdx +98 -0
  71. package/docs/configuration/customization.mdx +91 -0
  72. package/docs/configuration/export.mdx +70 -0
  73. package/docs/configuration/index.mdx +290 -0
  74. package/docs/configuration/meta.ts +15 -0
  75. package/docs/configuration/search.mdx +172 -0
  76. package/docs/configuration/seo.mdx +196 -0
  77. package/docs/configuration/theming.mdx +178 -0
  78. package/docs/content/components.mdx +651 -0
  79. package/docs/content/i18n.mdx +205 -0
  80. package/docs/content/index.mdx +161 -0
  81. package/docs/content/islands.mdx +94 -0
  82. package/docs/content/meta.mdx +119 -0
  83. package/docs/content/meta.ts +15 -0
  84. package/docs/content/navigation.mdx +168 -0
  85. package/docs/content/sources.mdx +216 -0
  86. package/docs/content/syntax.mdx +445 -0
  87. package/docs/index.mdx +112 -0
  88. package/docs/reference/cli.mdx +43 -0
  89. package/docs/reference/frontmatter.mdx +74 -0
  90. package/docs/reference/meta.ts +7 -0
  91. package/package.json +150 -6
  92. package/src/ai/ask.ts +93 -0
  93. package/src/ai/llms.ts +64 -0
  94. package/src/ai/markdown.ts +31 -0
  95. package/src/ai/mcp/data.ts +74 -0
  96. package/src/ai/mcp/discovery.ts +49 -0
  97. package/src/ai/mcp/server.ts +225 -0
  98. package/src/ai/mcp/tools.ts +47 -0
  99. package/src/assets/icon.png +0 -0
  100. package/src/astro/examples.ts +104 -0
  101. package/src/astro/generate.ts +1006 -0
  102. package/src/astro/index.ts +4 -0
  103. package/src/astro/integration.ts +74 -0
  104. package/src/astro/islands.ts +131 -0
  105. package/src/astro/markdown-negotiation.ts +68 -0
  106. package/src/astro/pages.ts +87 -0
  107. package/src/astro/templates.ts +1326 -0
  108. package/src/cli/commands/add.ts +81 -0
  109. package/src/cli/commands/build.ts +103 -0
  110. package/src/cli/commands/dev.ts +108 -0
  111. package/src/cli/commands/doctor.ts +74 -0
  112. package/src/cli/commands/eject.ts +57 -0
  113. package/src/cli/commands/init.ts +98 -0
  114. package/src/cli/commands/migrate.ts +39 -0
  115. package/src/cli/commands/preview.ts +39 -0
  116. package/src/cli/commands/sync.ts +52 -0
  117. package/src/cli/commands/validate.ts +61 -0
  118. package/src/cli/index.ts +35 -0
  119. package/src/cli/log.ts +37 -0
  120. package/src/cli/prepare.ts +80 -0
  121. package/src/components/Icon.astro +99 -0
  122. package/src/components/content/Accordion.astro +8 -0
  123. package/src/components/content/AccordionItem.astro +121 -0
  124. package/src/components/content/AutoTypeTable.astro +51 -0
  125. package/src/components/content/Badge.astro +124 -0
  126. package/src/components/content/Callout.astro +73 -0
  127. package/src/components/content/Card.astro +104 -0
  128. package/src/components/content/CardGroup.astro +14 -0
  129. package/src/components/content/CodeBlock.astro +28 -0
  130. package/src/components/content/CodeGroup.astro +13 -0
  131. package/src/components/content/Color.astro +15 -0
  132. package/src/components/content/ColorItem.astro +87 -0
  133. package/src/components/content/ColorRow.astro +10 -0
  134. package/src/components/content/Column.astro +6 -0
  135. package/src/components/content/Columns.astro +9 -0
  136. package/src/components/content/Component.astro +65 -0
  137. package/src/components/content/Diff.astro +44 -0
  138. package/src/components/content/Expandable.astro +11 -0
  139. package/src/components/content/FileTree.astro +8 -0
  140. package/src/components/content/Frame.astro +70 -0
  141. package/src/components/content/GithubInfo.astro +110 -0
  142. package/src/components/content/Math.astro +24 -0
  143. package/src/components/content/Panel.astro +20 -0
  144. package/src/components/content/Prompt.astro +129 -0
  145. package/src/components/content/Step.astro +34 -0
  146. package/src/components/content/Steps.astro +20 -0
  147. package/src/components/content/Tab.astro +46 -0
  148. package/src/components/content/Tabs.astro +273 -0
  149. package/src/components/content/Tile.astro +42 -0
  150. package/src/components/content/Tooltip.astro +68 -0
  151. package/src/components/content/Tree.astro +300 -0
  152. package/src/components/content/TreeFile.astro +15 -0
  153. package/src/components/content/TreeFolder.astro +62 -0
  154. package/src/components/content/TypeTable.astro +106 -0
  155. package/src/components/content/Update.astro +66 -0
  156. package/src/components/content/Visibility.astro +12 -0
  157. package/src/components/content/Warning.astro +9 -0
  158. package/src/components/content/auto-type-table.ts +141 -0
  159. package/src/components/content/diff.ts +95 -0
  160. package/src/components/content/github-info.ts +79 -0
  161. package/src/components/content/mermaid-element.ts +68 -0
  162. package/src/components/github-mark.ts +9 -0
  163. package/src/components/index.ts +14 -0
  164. package/src/components/islands/AskAI.astro +12 -0
  165. package/src/components/islands/ask-ai.tsx +156 -0
  166. package/src/components/layout/Analytics.astro +63 -0
  167. package/src/components/layout/Banner.astro +50 -0
  168. package/src/components/layout/Breadcrumbs.astro +31 -0
  169. package/src/components/layout/Favicon.astro +22 -0
  170. package/src/components/layout/Fonts.astro +14 -0
  171. package/src/components/layout/Header.astro +188 -0
  172. package/src/components/layout/LanguageSwitcher.astro +56 -0
  173. package/src/components/layout/NavTree.astro +462 -0
  174. package/src/components/layout/PageActions.astro +438 -0
  175. package/src/components/layout/PageFeedback.astro +58 -0
  176. package/src/components/layout/PageLayout.astro +173 -0
  177. package/src/components/layout/Pagination.astro +56 -0
  178. package/src/components/layout/ReferenceLayout.astro +107 -0
  179. package/src/components/layout/RootLayout.astro +537 -0
  180. package/src/components/layout/Search.astro +608 -0
  181. package/src/components/layout/TableOfContents.astro +68 -0
  182. package/src/components/layout/analytics-client.ts +38 -0
  183. package/src/components/layout/head-scripts.ts +19 -0
  184. package/src/components/layout/nav-utils.ts +87 -0
  185. package/src/components/layout/overrides.ts +32 -0
  186. package/src/components/layout/search/algolia.ts +43 -0
  187. package/src/components/layout/search/endpoint.ts +22 -0
  188. package/src/components/layout/search/flexsearch.ts +52 -0
  189. package/src/components/layout/search/orama-cloud.ts +41 -0
  190. package/src/components/layout/search/orama.ts +26 -0
  191. package/src/components/layout/search/pagefind.ts +43 -0
  192. package/src/components/layout/search/types.ts +163 -0
  193. package/src/components/layout/search/typesense.ts +60 -0
  194. package/src/components/layout/toc-element.ts +108 -0
  195. package/src/core/bridge.ts +92 -0
  196. package/src/core/config.ts +112 -0
  197. package/src/core/content.ts +50 -0
  198. package/src/core/data.ts +128 -0
  199. package/src/core/define-components.ts +34 -0
  200. package/src/core/define-meta.ts +20 -0
  201. package/src/core/deployment-env.ts +73 -0
  202. package/src/core/diagnostics.ts +104 -0
  203. package/src/core/frontmatter.ts +43 -0
  204. package/src/core/graph.ts +128 -0
  205. package/src/core/i18n-ui.ts +171 -0
  206. package/src/core/i18n.ts +169 -0
  207. package/src/core/last-modified.ts +88 -0
  208. package/src/core/links.ts +352 -0
  209. package/src/core/load-module.ts +15 -0
  210. package/src/core/manifest.ts +126 -0
  211. package/src/core/meta.ts +97 -0
  212. package/src/core/navigation.ts +392 -0
  213. package/src/core/package-root.ts +37 -0
  214. package/src/core/project-graph.ts +153 -0
  215. package/src/core/project.ts +56 -0
  216. package/src/core/schema.ts +1057 -0
  217. package/src/core/server-features.ts +23 -0
  218. package/src/core/sources/assets.ts +77 -0
  219. package/src/core/sources/cache.ts +122 -0
  220. package/src/core/sources/filesystem.ts +99 -0
  221. package/src/core/sources/mdx-remote.ts +215 -0
  222. package/src/core/sources/mintlify.ts +161 -0
  223. package/src/core/sources/normalize.ts +243 -0
  224. package/src/core/sources/notion.ts +440 -0
  225. package/src/core/sources/portable-text.ts +143 -0
  226. package/src/core/sources/read.ts +36 -0
  227. package/src/core/sources/resolve.ts +158 -0
  228. package/src/core/sources/sanity.ts +218 -0
  229. package/src/core/sources/types.ts +105 -0
  230. package/src/core/tsconfig-aliases.ts +201 -0
  231. package/src/core/types.ts +261 -0
  232. package/src/core/ui-packs/ar.ts +47 -0
  233. package/src/core/ui-packs/bg.ts +47 -0
  234. package/src/core/ui-packs/bn.ts +47 -0
  235. package/src/core/ui-packs/ca.ts +47 -0
  236. package/src/core/ui-packs/cs.ts +47 -0
  237. package/src/core/ui-packs/da.ts +47 -0
  238. package/src/core/ui-packs/de.ts +47 -0
  239. package/src/core/ui-packs/el.ts +47 -0
  240. package/src/core/ui-packs/es.ts +47 -0
  241. package/src/core/ui-packs/fa.ts +47 -0
  242. package/src/core/ui-packs/fi.ts +47 -0
  243. package/src/core/ui-packs/fr.ts +47 -0
  244. package/src/core/ui-packs/he.ts +47 -0
  245. package/src/core/ui-packs/hi.ts +47 -0
  246. package/src/core/ui-packs/hr.ts +47 -0
  247. package/src/core/ui-packs/hu.ts +47 -0
  248. package/src/core/ui-packs/id.ts +47 -0
  249. package/src/core/ui-packs/index.ts +87 -0
  250. package/src/core/ui-packs/it.ts +47 -0
  251. package/src/core/ui-packs/ja.ts +47 -0
  252. package/src/core/ui-packs/ko.ts +47 -0
  253. package/src/core/ui-packs/nl.ts +47 -0
  254. package/src/core/ui-packs/no.ts +47 -0
  255. package/src/core/ui-packs/pl.ts +47 -0
  256. package/src/core/ui-packs/pt-br.ts +47 -0
  257. package/src/core/ui-packs/pt.ts +47 -0
  258. package/src/core/ui-packs/ro.ts +47 -0
  259. package/src/core/ui-packs/ru.ts +47 -0
  260. package/src/core/ui-packs/sk.ts +47 -0
  261. package/src/core/ui-packs/sr.ts +47 -0
  262. package/src/core/ui-packs/sv.ts +47 -0
  263. package/src/core/ui-packs/th.ts +47 -0
  264. package/src/core/ui-packs/tr.ts +47 -0
  265. package/src/core/ui-packs/uk.ts +47 -0
  266. package/src/core/ui-packs/vi.ts +47 -0
  267. package/src/core/ui-packs/zh-tw.ts +47 -0
  268. package/src/core/ui-packs/zh.ts +47 -0
  269. package/src/core/version.ts +23 -0
  270. package/src/deploy/robots.ts +20 -0
  271. package/src/deploy/rss.ts +128 -0
  272. package/src/deploy/sitemap.ts +28 -0
  273. package/src/index.ts +39 -0
  274. package/src/markdown/code-title.ts +71 -0
  275. package/src/markdown/directives.ts +83 -0
  276. package/src/markdown/heading-anchors.ts +137 -0
  277. package/src/markdown/index.ts +228 -0
  278. package/src/markdown/inline-code.ts +108 -0
  279. package/src/markdown/language-icon.ts +172 -0
  280. package/src/markdown/math.ts +32 -0
  281. package/src/markdown/mdast.ts +48 -0
  282. package/src/markdown/mermaid.ts +37 -0
  283. package/src/markdown/package-commands.ts +159 -0
  284. package/src/markdown/package-install.ts +40 -0
  285. package/src/migrate/fumadocs/config.ts +155 -0
  286. package/src/migrate/fumadocs/content.ts +365 -0
  287. package/src/migrate/fumadocs/frontmatter.ts +18 -0
  288. package/src/migrate/fumadocs/groups.ts +230 -0
  289. package/src/migrate/fumadocs/index.ts +337 -0
  290. package/src/migrate/fumadocs/meta.ts +244 -0
  291. package/src/migrate/migrate.ts +53 -0
  292. package/src/migrate/mintlify/config.ts +1040 -0
  293. package/src/migrate/mintlify/content.ts +98 -0
  294. package/src/migrate/mintlify/frontmatter.ts +126 -0
  295. package/src/migrate/mintlify/i18n.ts +51 -0
  296. package/src/migrate/mintlify/icons.ts +128 -0
  297. package/src/migrate/mintlify/index.ts +266 -0
  298. package/src/migrate/mintlify/snippets.ts +306 -0
  299. package/src/migrate/mintlify/transform.ts +80 -0
  300. package/src/migrate/nextra/content.ts +46 -0
  301. package/src/migrate/nextra/frontmatter.ts +40 -0
  302. package/src/migrate/nextra/index.ts +374 -0
  303. package/src/migrate/nextra/meta.ts +266 -0
  304. package/src/migrate/shared.ts +720 -0
  305. package/src/migrate/starlight/config.ts +459 -0
  306. package/src/migrate/starlight/content.ts +78 -0
  307. package/src/migrate/starlight/frontmatter.ts +111 -0
  308. package/src/migrate/starlight/i18n.ts +54 -0
  309. package/src/migrate/starlight/index.ts +131 -0
  310. package/src/og/card.ts +92 -0
  311. package/src/og/index.ts +2 -0
  312. package/src/openapi/scalar.ts +246 -0
  313. package/src/registry/eject.ts +310 -0
  314. package/src/registry/registry.ts +100 -0
  315. package/src/registry/rewrite-imports.ts +39 -0
  316. package/src/runtime/index.ts +14 -0
  317. package/src/search/build.ts +23 -0
  318. package/src/search/documents.ts +164 -0
  319. package/src/search/orama-index.ts +66 -0
  320. package/src/search/providers.ts +91 -0
  321. package/src/search/sync/algolia.ts +30 -0
  322. package/src/search/sync/index.ts +50 -0
  323. package/src/search/sync/orama-cloud.ts +40 -0
  324. package/src/search/sync/typesense.ts +65 -0
  325. package/src/seo/jsonld.ts +113 -0
  326. package/src/theme/entry.ts +637 -0
  327. package/src/theme/fonts.ts +198 -0
  328. package/src/theme/icons.ts +184 -0
  329. package/src/theme/palette.ts +143 -0
  330. package/src/theme/twoslash.ts +81 -0
@@ -0,0 +1,56 @@
1
+ ---
2
+ import type { UIStrings } from "../../core/i18n-ui.ts";
3
+ import type { FlatPage } from "./nav-utils.ts";
4
+ import Icon from "../Icon.astro";
5
+
6
+ interface Props {
7
+ /** Previous page in reading order, or `null` at the start. */
8
+ prev: FlatPage | null;
9
+ /** Next page in reading order, or `null` at the end. */
10
+ next: FlatPage | null;
11
+ /** Localized page-level strings (`previous`, `next`). */
12
+ strings: UIStrings["page"];
13
+ }
14
+
15
+ const { prev, next, strings } = Astro.props;
16
+ ---
17
+
18
+ {
19
+ (prev || next) && (
20
+ <nav
21
+ aria-label="Pagination"
22
+ class="mx-auto mt-12 flex max-w-[42rem] justify-between gap-4 border-border border-t pt-6 max-md:flex-col"
23
+ >
24
+ {prev ? (
25
+ <a
26
+ class="flex max-w-[48%] flex-1 items-center gap-2 rounded-full border border-border px-4 py-3 text-foreground transition-colors hover:border-foreground max-md:max-w-full"
27
+ href={prev.route}
28
+ >
29
+ <Icon class="rtl:-scale-x-100" name="arrow-left" size={16} />
30
+ <span>
31
+ <span class="block text-muted-foreground text-xs">
32
+ {strings.previous}
33
+ </span>
34
+ <span class="block font-medium">{prev.label}</span>
35
+ </span>
36
+ </a>
37
+ ) : (
38
+ <span />
39
+ )}
40
+ {next && (
41
+ <a
42
+ class="ms-auto flex max-w-[48%] flex-1 items-center justify-end gap-2 rounded-full border border-border px-4 py-3 text-end text-foreground transition-colors hover:border-foreground max-md:max-w-full"
43
+ href={next.route}
44
+ >
45
+ <span>
46
+ <span class="block text-muted-foreground text-xs">
47
+ {strings.next}
48
+ </span>
49
+ <span class="block font-medium">{next.label}</span>
50
+ </span>
51
+ <Icon class="rtl:-scale-x-100" name="arrow-right" size={16} />
52
+ </a>
53
+ )}
54
+ </nav>
55
+ )
56
+ }
@@ -0,0 +1,107 @@
1
+ ---
2
+ import "blume:theme";
3
+ import type { Navigation } from "../../core/types.ts";
4
+ import Analytics from "./Analytics.astro";
5
+ import Banner from "./Banner.astro";
6
+ import Favicon from "./Favicon.astro";
7
+ import Fonts from "./Fonts.astro";
8
+ import Header from "./Header.astro";
9
+
10
+ // A minimal shell for the Scalar API/AsyncAPI reference: Blume's banner + navbar
11
+ // on top, then a full-height region the reference mounts into. Unlike RootLayout
12
+ // it has no docs sidebar/TOC/prose column, so the reference renders edge to edge.
13
+ interface Props {
14
+ site: { title: string; description?: string };
15
+ logo?: {
16
+ svg?: string;
17
+ light?: string;
18
+ dark?: string;
19
+ alt: string;
20
+ href: string;
21
+ } | null;
22
+ favicon?: {
23
+ href: string;
24
+ type?: string;
25
+ } | null;
26
+ appleIcon?: {
27
+ href: string;
28
+ type?: string;
29
+ } | null;
30
+ banner?: {
31
+ content: string;
32
+ link?: { text: string; href: string };
33
+ dismissible: boolean;
34
+ key: string;
35
+ } | null;
36
+ analytics?: {
37
+ posthog?: { host?: string; key: string };
38
+ scripts?: {
39
+ attributes?: Record<string, string>;
40
+ content?: string;
41
+ src?: string;
42
+ strategy?: "async" | "defer";
43
+ }[];
44
+ vercel?: boolean;
45
+ } | null;
46
+ navigation: Navigation;
47
+ route: string;
48
+ themeMode: "system" | "light" | "dark";
49
+ fontCssVars?: string[];
50
+ searchEnabled: boolean;
51
+ pageTitle: string;
52
+ }
53
+
54
+ const {
55
+ site,
56
+ logo,
57
+ favicon,
58
+ appleIcon,
59
+ banner,
60
+ analytics,
61
+ navigation,
62
+ route,
63
+ themeMode,
64
+ fontCssVars,
65
+ searchEnabled,
66
+ pageTitle,
67
+ } = Astro.props;
68
+
69
+ // Set the theme before paint so the navbar never flashes the wrong colors
70
+ // (mirrors RootLayout's pre-paint script).
71
+ const initialThemeScript = `(()=>{const m=${JSON.stringify(themeMode)};const s=localStorage.getItem("blume-theme");const sys=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.dataset.theme=s??(m==="system"?sys:m);})();`;
72
+
73
+ // Pre-paint: hide a previously-dismissed banner before it flashes in.
74
+ const bannerScript = banner?.dismissible
75
+ ? `(()=>{if(localStorage.getItem("blume-banner:"+${JSON.stringify(banner.key)}))document.documentElement.setAttribute("data-blume-banner-hidden","");})();`
76
+ : null;
77
+ ---
78
+
79
+ <!doctype html>
80
+ <html lang="en">
81
+ <head>
82
+ <meta charset="utf-8" />
83
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
84
+ <title>{pageTitle}</title>
85
+ <Favicon favicon={favicon} appleIcon={appleIcon} />
86
+ <Fonts cssVars={fontCssVars ?? []} />
87
+ {bannerScript && <script is:inline set:html={bannerScript} />}
88
+ <script is:inline set:html={initialThemeScript} />
89
+ <Analytics analytics={analytics} />
90
+ </head>
91
+ <body class="bg-background font-sans text-foreground antialiased">
92
+ <Banner banner={banner} />
93
+ <Header
94
+ hasSidebar={false}
95
+ logo={logo}
96
+ navigation={navigation}
97
+ route={route}
98
+ searchEnabled={searchEnabled}
99
+ site={site}
100
+ />
101
+ <div
102
+ class="h-[calc(100dvh-4rem)] overflow-auto border-border border-t [&>[data-scalar-client]]:h-full"
103
+ >
104
+ <slot />
105
+ </div>
106
+ </body>
107
+ </html>
@@ -0,0 +1,537 @@
1
+ ---
2
+ import { EN_UI } from "../../core/i18n-ui.ts";
3
+ import type { UIStrings } from "../../core/i18n-ui.ts";
4
+ import type {
5
+ Heading,
6
+ LocaleSwitchOption,
7
+ Navigation,
8
+ } from "../../core/types.ts";
9
+ import "blume:theme";
10
+ import type { ComponentOverride } from "../../core/define-components.ts";
11
+ import { buildStructuredData } from "../../seo/jsonld.ts";
12
+ import Analytics from "./Analytics.astro";
13
+ import Banner from "./Banner.astro";
14
+ import Breadcrumbs from "./Breadcrumbs.astro";
15
+ import Favicon from "./Favicon.astro";
16
+ import Fonts from "./Fonts.astro";
17
+ import { bannerInitScript, themeInitScript } from "./head-scripts.ts";
18
+ import Header from "./Header.astro";
19
+ import { findBreadcrumbs, flattenPages, getPagination } from "./nav-utils.ts";
20
+ import NavTree from "./NavTree.astro";
21
+ import { resolveSlot } from "./overrides.ts";
22
+ import PageActions from "./PageActions.astro";
23
+ import PageFeedback from "./PageFeedback.astro";
24
+ import Pagination from "./Pagination.astro";
25
+ import TableOfContents from "./TableOfContents.astro";
26
+
27
+ interface Props {
28
+ site: { title: string; description?: string };
29
+ logo?: {
30
+ svg?: string;
31
+ light?: string;
32
+ dark?: string;
33
+ alt: string;
34
+ href: string;
35
+ } | null;
36
+ favicon?: {
37
+ href: string;
38
+ type?: string;
39
+ } | null;
40
+ appleIcon?: {
41
+ href: string;
42
+ type?: string;
43
+ } | null;
44
+ banner?: {
45
+ content: string;
46
+ link?: { text: string; href: string };
47
+ dismissible: boolean;
48
+ key: string;
49
+ } | null;
50
+ analytics?: {
51
+ posthog?: { host?: string; key: string };
52
+ scripts?: {
53
+ attributes?: Record<string, string>;
54
+ content?: string;
55
+ src?: string;
56
+ strategy?: "async" | "defer";
57
+ }[];
58
+ vercel?: boolean;
59
+ } | null;
60
+ navigation: Navigation;
61
+ mcp?: { name: string; route: string } | null;
62
+ page: { title: string; description?: string; route: string };
63
+ headings: Heading[];
64
+ imageZoom?: boolean;
65
+ codeWrap?: boolean;
66
+ themeMode: "system" | "light" | "dark";
67
+ fontCssVars?: string[];
68
+ searchEnabled: boolean;
69
+ indexable: boolean;
70
+ ogImage?: string | null;
71
+ canonical?: string | null;
72
+ editUrl?: string | null;
73
+ askEnabled?: boolean;
74
+ /** Show the "Was this page helpful?" rating below the content. */
75
+ feedback?: boolean;
76
+ exportPdf?: boolean;
77
+ exportEpub?: boolean;
78
+ feeds?: { title: string; href: string }[];
79
+ siteUrl?: string | null;
80
+ pageType?: string;
81
+ published?: string | Date | null;
82
+ lastModified?: string | null;
83
+ noindex?: boolean;
84
+ structuredDataEnabled?: boolean;
85
+ /** Active locale code for `<html lang>` (defaults to `en`). */
86
+ locale?: string;
87
+ /** Text direction for `<html dir>` (defaults to `ltr`). */
88
+ dir?: "ltr" | "rtl";
89
+ /**
90
+ * Direction of the page content's own language — differs from `dir` on a
91
+ * fallback page (e.g. English content under an RTL locale). Applied to the
92
+ * article so prose punctuation reads correctly; the chrome still mirrors.
93
+ */
94
+ contentDir?: "ltr" | "rtl";
95
+ /** Resolved UI dictionary; English baseline when omitted. */
96
+ ui?: UIStrings;
97
+ /** `hreflang` alternates (absolute URLs) for this page's translations. */
98
+ localeAlternates?: { hreflang: string; href: string }[];
99
+ /** Absolute URL of the default-locale translation, for `hreflang=x-default`. */
100
+ xDefault?: string | null;
101
+ /** Language-switcher entries for the current page. */
102
+ localeSwitch?: LocaleSwitchOption[];
103
+ /**
104
+ * User layout-slot overrides from `components.ts` (`defineComponents`). Each
105
+ * key replaces the matching built-in; unknown keys are ignored. Wired slots:
106
+ * `Header`, `Sidebar`, `Breadcrumbs`, `TableOfContents`, `Pagination`.
107
+ */
108
+ layout?: Record<string, ComponentOverride>;
109
+ }
110
+
111
+ const {
112
+ site,
113
+ logo,
114
+ favicon,
115
+ appleIcon,
116
+ banner,
117
+ analytics,
118
+ navigation,
119
+ mcp,
120
+ page,
121
+ headings,
122
+ imageZoom = true,
123
+ codeWrap = false,
124
+ themeMode,
125
+ fontCssVars,
126
+ searchEnabled,
127
+ indexable,
128
+ ogImage,
129
+ canonical,
130
+ editUrl,
131
+ askEnabled,
132
+ feedback = false,
133
+ exportPdf,
134
+ exportEpub,
135
+ feeds,
136
+ siteUrl,
137
+ pageType,
138
+ published,
139
+ lastModified,
140
+ noindex,
141
+ structuredDataEnabled,
142
+ locale = "en",
143
+ dir = "ltr",
144
+ contentDir,
145
+ ui,
146
+ localeAlternates,
147
+ xDefault,
148
+ localeSwitch,
149
+ layout = {},
150
+ } = Astro.props;
151
+
152
+ // Layout-slot overrides: render the user's component when configured, else the
153
+ // built-in. Each override receives the same props as the component it replaces.
154
+ const HeaderSlot = resolveSlot(layout.Header, Header);
155
+ const SidebarSlot = resolveSlot(layout.Sidebar, NavTree);
156
+ const BreadcrumbsSlot = resolveSlot(layout.Breadcrumbs, Breadcrumbs);
157
+ const TableOfContentsSlot = resolveSlot(layout.TableOfContents, TableOfContents);
158
+ const PaginationSlot = resolveSlot(layout.Pagination, Pagination);
159
+
160
+ const strings = ui ?? EN_UI;
161
+ // Filter search to the active language only when the site is multi-locale.
162
+ const searchLocale =
163
+ localeSwitch && localeSwitch.length > 1 ? locale : undefined;
164
+ const tocHeadings = headings.filter((h) => h.depth >= 2 && h.depth <= 3);
165
+ const pageTitle = page.title ? `${page.title} - ${site.title}` : site.title;
166
+ const description = page.description ?? site.description;
167
+
168
+ // "Last updated on <date>" — formatted in UTC to match the changelog timeline.
169
+ const lastModifiedDate = lastModified ? new Date(lastModified) : null;
170
+ const formattedLastModified =
171
+ lastModifiedDate && !Number.isNaN(lastModifiedDate.getTime())
172
+ ? new Intl.DateTimeFormat(locale || "en", {
173
+ dateStyle: "long",
174
+ timeZone: "UTC",
175
+ }).format(lastModifiedDate)
176
+ : null;
177
+
178
+ // The hosted MCP server's absolute URL, used by the page-actions install menu.
179
+ // Needs a configured site to be useful, so the menu is hidden without one.
180
+ const mcpUrl = mcp && siteUrl ? new URL(mcp.route, siteUrl).href : null;
181
+
182
+ const crumbs = findBreadcrumbs(navigation.sidebar, page.route);
183
+ const { prev, next } = getPagination(
184
+ flattenPages(navigation.sidebar),
185
+ page.route
186
+ );
187
+
188
+ // Structured data is skipped when disabled or for pages we ask crawlers not to
189
+ // index. Escape `<` so a title/description containing `</script>` can't break
190
+ // out of the inline JSON-LD tag.
191
+ const structuredData =
192
+ structuredDataEnabled === false || noindex
193
+ ? null
194
+ : buildStructuredData({
195
+ breadcrumbs: crumbs,
196
+ description,
197
+ locale,
198
+ modified: lastModified,
199
+ pageType,
200
+ published,
201
+ route: page.route,
202
+ siteName: site.title,
203
+ siteUrl: siteUrl ?? null,
204
+ title: page.title,
205
+ });
206
+ const structuredDataJson = structuredData
207
+ ? JSON.stringify(structuredData).replaceAll("<", "\\u003c")
208
+ : null;
209
+
210
+ const initialThemeScript = themeInitScript(themeMode);
211
+ const bannerScript = banner?.dismissible
212
+ ? bannerInitScript(banner.key)
213
+ : null;
214
+ ---
215
+
216
+ <!doctype html>
217
+ <html dir={dir} lang={locale}>
218
+ <head>
219
+ <meta charset="utf-8" />
220
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
221
+ <title>{pageTitle}</title>
222
+ <Favicon favicon={favicon} appleIcon={appleIcon} />
223
+ <Fonts cssVars={fontCssVars ?? []} />
224
+ {canonical && <link rel="canonical" href={canonical} />}
225
+ {
226
+ localeAlternates?.map((alt) => (
227
+ <link href={alt.href} hreflang={alt.hreflang} rel="alternate" />
228
+ ))
229
+ }
230
+ {xDefault && <link href={xDefault} hreflang="x-default" rel="alternate" />}
231
+ {noindex && <meta name="robots" content="noindex" />}
232
+ {description && <meta name="description" content={description} />}
233
+ <meta property="og:title" content={pageTitle} />
234
+ {description && <meta property="og:description" content={description} />}
235
+ {
236
+ ogImage && (
237
+ <>
238
+ <meta property="og:image" content={ogImage} />
239
+ <meta name="twitter:image" content={ogImage} />
240
+ <meta name="twitter:card" content="summary_large_image" />
241
+ </>
242
+ )
243
+ }
244
+ {
245
+ feeds?.map((feed) => (
246
+ <link
247
+ href={feed.href}
248
+ rel="alternate"
249
+ title={feed.title}
250
+ type="application/rss+xml"
251
+ />
252
+ ))
253
+ }
254
+ {
255
+ structuredDataJson && (
256
+ <script
257
+ is:inline
258
+ set:html={structuredDataJson}
259
+ type="application/ld+json"
260
+ />
261
+ )
262
+ }
263
+ {bannerScript && <script is:inline set:html={bannerScript} />}
264
+ <script is:inline set:html={initialThemeScript} />
265
+ <Analytics analytics={analytics} />
266
+ </head>
267
+ <body
268
+ class="bg-background font-sans text-foreground antialiased"
269
+ data-blume-code-wrap={codeWrap ? "" : undefined}
270
+ data-blume-image-zoom={imageZoom ? "" : undefined}
271
+ >
272
+ <a
273
+ class="absolute start-[-999px] top-0 z-[100] bg-accent px-4 py-2 text-accent-foreground focus:start-0"
274
+ href="#blume-content">{strings.page.skipToContent}</a
275
+ >
276
+ <Banner banner={banner} />
277
+ <HeaderSlot
278
+ askEnabled={askEnabled}
279
+ localeSwitch={localeSwitch}
280
+ logo={logo}
281
+ navigation={navigation}
282
+ route={page.route}
283
+ searchEnabled={searchEnabled}
284
+ searchLocale={searchLocale}
285
+ searchStrings={strings.search}
286
+ site={site}
287
+ switcherStrings={strings.languageSwitcher}
288
+ >
289
+ <slot name="ask" slot="ask" />
290
+ </HeaderSlot>
291
+ <div class="mx-auto grid grid-cols-1 items-start lg:grid-cols-[17.5rem_minmax(0,1fr)] xl:grid-cols-[17.5rem_minmax(0,1fr)_17.5rem]">
292
+ <aside
293
+ aria-label="Primary"
294
+ class="fixed top-16 start-0 z-[35] h-[calc(100dvh-4rem)] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:z-auto lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
295
+ >
296
+ <nav>
297
+ <SidebarSlot currentRoute={page.route} items={navigation.sidebar} />
298
+ </nav>
299
+ </aside>
300
+ <main class="min-w-0 px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
301
+ <BreadcrumbsSlot crumbs={crumbs} />
302
+ <TableOfContentsSlot
303
+ headings={tocHeadings}
304
+ title={strings.toc.title}
305
+ variant="mobile"
306
+ />
307
+ <article class="prose mx-auto max-w-[42rem]" dir={contentDir}>
308
+ <slot />
309
+ </article>
310
+ {
311
+ formattedLastModified && (
312
+ <p class="mx-auto mt-10 max-w-[42rem] text-muted-foreground text-sm">
313
+ {strings.page.lastUpdated} {formattedLastModified}
314
+ </p>
315
+ )
316
+ }
317
+ {feedback && <PageFeedback strings={strings.feedback} />}
318
+ <PaginationSlot next={next} prev={prev} strings={strings.page} />
319
+ </main>
320
+ <aside
321
+ aria-label={strings.toc.title}
322
+ class="sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto px-4 pt-6 pb-10 text-sm xl:block"
323
+ >
324
+ <TableOfContentsSlot
325
+ headings={tocHeadings}
326
+ title={strings.toc.title}
327
+ variant="desktop"
328
+ />
329
+ <PageActions
330
+ askEnabled={askEnabled}
331
+ editUrl={editUrl}
332
+ exportEpub={exportEpub}
333
+ exportPdf={exportPdf}
334
+ mcpName={mcp?.name}
335
+ mcpUrl={mcpUrl}
336
+ route={page.route}
337
+ strings={strings.actions}
338
+ />
339
+ </aside>
340
+ </div>
341
+ <button
342
+ aria-label="Close navigation"
343
+ class="fixed inset-0 z-[30] hidden cursor-pointer border-0 bg-black/40 [:where([data-blume-nav-open])_&]:block lg:hidden"
344
+ data-blume-nav-toggle
345
+ type="button"
346
+ ></button>
347
+ <script>
348
+ import { icons } from "../../theme/icons.ts";
349
+ // Registers the <blume-mermaid> custom element (emitted by ```mermaid
350
+ // fences). Mermaid itself is lazy-loaded only on pages that use a diagram.
351
+ import "../content/mermaid-element.ts";
352
+ // Registers the <blume-toc> custom element: scrollspy for the table of
353
+ // contents, highlighting the section currently in view as you scroll.
354
+ import "./toc-element.ts";
355
+
356
+ const svg = (name: string) =>
357
+ `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${icons[name]}</svg>`;
358
+
359
+ const buttonClass =
360
+ "absolute right-3 inline-flex size-7 items-center justify-center rounded-full bg-transparent text-muted-foreground opacity-70 transition hover:bg-muted hover:text-foreground hover:opacity-100 focus-visible:opacity-100";
361
+
362
+ const languageLabels: Record<string, string> = {
363
+ astro: "Astro",
364
+ bash: "Bash",
365
+ css: "CSS",
366
+ html: "HTML",
367
+ js: "JavaScript",
368
+ json: "JSON",
369
+ jsx: "JSX",
370
+ md: "Markdown",
371
+ mdx: "MDX",
372
+ plaintext: "Text",
373
+ sh: "Shell",
374
+ shell: "Shell",
375
+ ts: "TypeScript",
376
+ tsx: "TSX",
377
+ txt: "Text",
378
+ yaml: "YAML",
379
+ yml: "YAML",
380
+ zsh: "Zsh",
381
+ };
382
+
383
+ for (const pre of document.querySelectorAll(".prose pre")) {
384
+ if (pre.querySelector("[data-blume-copy]")) {
385
+ continue;
386
+ }
387
+ // Skip nested <pre>: Twoslash renders each hover popup's type signature
388
+ // as a <pre> inside the code block, which shouldn't get its own button.
389
+ if (pre.parentElement?.closest("pre")) {
390
+ continue;
391
+ }
392
+ const language = pre.getAttribute("data-language");
393
+ if (language) {
394
+ pre.setAttribute(
395
+ "data-language",
396
+ languageLabels[language.toLowerCase()] ?? language
397
+ );
398
+ }
399
+ pre.classList.add("group", "relative");
400
+ const button = document.createElement("button");
401
+ button.type = "button";
402
+ // The language-label bar (prose) vs flush code (tabs) need a different
403
+ // offset; pick the Tailwind class by context instead of a CSS override.
404
+ const topClass = pre.closest("blume-tabs, .not-prose")
405
+ ? "top-2.5"
406
+ : "top-2";
407
+ button.className = `${buttonClass} ${topClass}`;
408
+ button.setAttribute("data-blume-copy", "");
409
+ button.setAttribute("aria-label", "Copy code");
410
+ button.innerHTML = svg("copy");
411
+ button.addEventListener("click", async () => {
412
+ const text = pre.querySelector("code")?.textContent ?? "";
413
+ try {
414
+ await navigator.clipboard.writeText(text);
415
+ } catch {
416
+ return;
417
+ }
418
+ button.innerHTML = svg("check");
419
+ setTimeout(() => {
420
+ button.innerHTML = svg("copy");
421
+ }, 1500);
422
+ });
423
+ pre.appendChild(button);
424
+ }
425
+
426
+ // Click-to-zoom for content images (gated by `markdown.imageZoom`).
427
+ // Enhances every `.prose img` so plain Markdown images open in a lightbox.
428
+ // Opt out per-image with `data-no-zoom`.
429
+ const zoomImages: Iterable<HTMLImageElement> =
430
+ document.body.hasAttribute("data-blume-image-zoom")
431
+ ? document.querySelectorAll<HTMLImageElement>(
432
+ ".prose img:not([data-no-zoom])"
433
+ )
434
+ : [];
435
+ const zoomTargets = Array.from(zoomImages);
436
+ if (zoomTargets.length > 0) {
437
+ const reduceMotion = window.matchMedia(
438
+ "(prefers-reduced-motion: reduce)"
439
+ ).matches;
440
+ let active: {
441
+ clone: HTMLImageElement;
442
+ original: HTMLImageElement;
443
+ overlay: HTMLElement;
444
+ } | null = null;
445
+
446
+ const closeZoom = () => {
447
+ if (!active) {
448
+ return;
449
+ }
450
+ const { clone, original, overlay } = active;
451
+ active = null;
452
+ overlay.style.opacity = "0";
453
+ clone.style.transform = "translate(0px, 0px) scale(1)";
454
+ const cleanup = () => {
455
+ overlay.remove();
456
+ original.style.visibility = "";
457
+ document.removeEventListener("keydown", onKey);
458
+ window.removeEventListener("scroll", closeZoom);
459
+ };
460
+ if (reduceMotion) {
461
+ cleanup();
462
+ } else {
463
+ clone.addEventListener("transitionend", cleanup, { once: true });
464
+ }
465
+ };
466
+
467
+ const onKey = (event: KeyboardEvent) => {
468
+ if (event.key === "Escape") {
469
+ closeZoom();
470
+ }
471
+ };
472
+
473
+ const openZoom = (image: HTMLImageElement) => {
474
+ if (active) {
475
+ return;
476
+ }
477
+ const start = image.getBoundingClientRect();
478
+ if (start.width === 0 || start.height === 0) {
479
+ return;
480
+ }
481
+
482
+ const overlay = document.createElement("div");
483
+ overlay.className =
484
+ "fixed inset-0 z-[100] flex cursor-zoom-out items-center justify-center bg-background/80 opacity-0 backdrop-blur-sm transition-opacity duration-300";
485
+
486
+ const clone = image.cloneNode(true) as HTMLImageElement;
487
+ clone.className = "absolute m-0 max-w-none rounded-blume shadow-2xl";
488
+ clone.style.top = `${start.top}px`;
489
+ clone.style.left = `${start.left}px`;
490
+ clone.style.width = `${start.width}px`;
491
+ clone.style.height = `${start.height}px`;
492
+ clone.style.transformOrigin = "top left";
493
+ if (!reduceMotion) {
494
+ clone.style.transition =
495
+ "transform 300ms cubic-bezier(0.22, 1, 0.36, 1)";
496
+ }
497
+
498
+ overlay.appendChild(clone);
499
+ document.body.appendChild(overlay);
500
+ image.style.visibility = "hidden";
501
+ active = { clone, original: image, overlay };
502
+
503
+ const margin = 0.92;
504
+ const ratio = start.width / start.height;
505
+ let targetWidth = window.innerWidth * margin;
506
+ let targetHeight = targetWidth / ratio;
507
+ if (targetHeight > window.innerHeight * margin) {
508
+ targetHeight = window.innerHeight * margin;
509
+ targetWidth = targetHeight * ratio;
510
+ }
511
+ const naturalWidth = image.naturalWidth || targetWidth;
512
+ if (targetWidth > naturalWidth) {
513
+ targetWidth = naturalWidth;
514
+ targetHeight = targetWidth / ratio;
515
+ }
516
+ const scale = targetWidth / start.width;
517
+ const dx = (window.innerWidth - targetWidth) / 2 - start.left;
518
+ const dy = (window.innerHeight - targetHeight) / 2 - start.top;
519
+
520
+ requestAnimationFrame(() => {
521
+ overlay.style.opacity = "1";
522
+ clone.style.transform = `translate(${dx}px, ${dy}px) scale(${scale})`;
523
+ });
524
+
525
+ overlay.addEventListener("click", closeZoom);
526
+ document.addEventListener("keydown", onKey);
527
+ window.addEventListener("scroll", closeZoom, { passive: true });
528
+ };
529
+
530
+ for (const image of zoomTargets) {
531
+ image.classList.add("cursor-zoom-in");
532
+ image.addEventListener("click", () => openZoom(image));
533
+ }
534
+ }
535
+ </script>
536
+ </body>
537
+ </html>