blume 1.6.6 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cli/{chunk-nyqzjdhj.js → chunk-0qhq7b8q.js} +5 -5
  3. package/dist/cli/{chunk-cnvm6k3e.js → chunk-18tjv4f7.js} +11 -11
  4. package/dist/cli/{chunk-62qsssnh.js → chunk-5d4q7121.js} +401 -145
  5. package/dist/cli/chunk-5d4q7121.js.map +40 -0
  6. package/dist/cli/{chunk-ag1zyr5x.js → chunk-9qs6acpw.js} +11 -11
  7. package/dist/cli/{chunk-aerwpe14.js → chunk-agy5rzxy.js} +98 -15
  8. package/dist/cli/chunk-agy5rzxy.js.map +15 -0
  9. package/dist/cli/{chunk-x1vrdjyk.js → chunk-cfw6x4rm.js} +5 -5
  10. package/dist/cli/{chunk-bawgnt8x.js → chunk-ckh3a410.js} +3 -3
  11. package/dist/cli/{chunk-j00ezcg5.js → chunk-drke6t0h.js} +9 -9
  12. package/dist/cli/{chunk-3k0kzs6d.js → chunk-j6pxe0dt.js} +2 -2
  13. package/dist/cli/{chunk-n0y172hf.js → chunk-jk1zwka1.js} +4 -4
  14. package/dist/cli/{chunk-f75cqye8.js → chunk-jxkxjsc1.js} +10 -10
  15. package/dist/cli/{chunk-s4k1pnvf.js → chunk-kwx90v78.js} +11 -11
  16. package/dist/cli/{chunk-9sh49q0h.js → chunk-n0nyat6g.js} +2 -2
  17. package/dist/cli/{chunk-wkq5tbtq.js → chunk-qq9nm3qd.js} +3 -3
  18. package/dist/cli/{chunk-etsqspj6.js → chunk-s102bysw.js} +2 -2
  19. package/dist/cli/{chunk-wb067mv3.js → chunk-s5dsk8bj.js} +18 -7
  20. package/dist/cli/chunk-s5dsk8bj.js.map +13 -0
  21. package/dist/cli/{chunk-m3p3wahd.js → chunk-tnskyrej.js} +4 -4
  22. package/dist/cli/{chunk-vv237fp3.js → chunk-v2ymm99c.js} +26 -12
  23. package/dist/cli/{chunk-vv237fp3.js.map → chunk-v2ymm99c.js.map} +3 -3
  24. package/dist/cli/{chunk-5yvt556e.js → chunk-v5mm027v.js} +2 -2
  25. package/dist/cli/{chunk-vv3f8mb6.js → chunk-xv91q4nm.js} +24 -24
  26. package/dist/cli/{chunk-vv3f8mb6.js.map → chunk-xv91q4nm.js.map} +3 -3
  27. package/dist/cli/{chunk-0ewz4trd.js → chunk-y3g15rvv.js} +6 -6
  28. package/dist/cli/{chunk-tc89yh2r.js → chunk-ye9zdkgv.js} +2 -2
  29. package/dist/cli/{chunk-n4qjabmt.js → chunk-ynacq3ev.js} +4 -4
  30. package/dist/cli/{chunk-s4jn7f1q.js → chunk-zr3ygrq3.js} +2 -2
  31. package/dist/cli/index.js +13 -13
  32. package/dist/types/components/layout/nav-utils.d.ts +33 -1
  33. package/dist/types/theme/fonts.d.ts +22 -22
  34. package/docs/02-deployment.mdx +21 -0
  35. package/docs/content/navigation.mdx +2 -0
  36. package/docs/content/syntax.mdx +1 -1
  37. package/docs/discoverability/open-graph.mdx +4 -0
  38. package/package.json +1 -1
  39. package/src/astro/generate.ts +141 -5
  40. package/src/astro/integration.ts +12 -1
  41. package/src/astro/module-types.ts +9 -0
  42. package/src/astro/templates.ts +171 -11
  43. package/src/cli/commands/build.ts +28 -0
  44. package/src/components/Icon.astro +24 -0
  45. package/src/components/icon-sprite-middleware.ts +41 -0
  46. package/src/components/icon-sprite.ts +93 -0
  47. package/src/components/layout/IconSprite.astro +11 -0
  48. package/src/components/layout/NavTree.astro +156 -188
  49. package/src/components/layout/NavTreeCache.astro +45 -0
  50. package/src/components/layout/NavTreeScript.astro +256 -0
  51. package/src/components/layout/PageActions.astro +11 -5
  52. package/src/components/layout/PageLayout.astro +7 -0
  53. package/src/components/layout/ReferenceLayout.astro +7 -0
  54. package/src/components/layout/RootLayout.astro +30 -2
  55. package/src/components/layout/nav-cache.ts +49 -0
  56. package/src/components/layout/nav-utils.ts +69 -1
  57. package/src/markdown/language-icon.ts +64 -20
  58. package/src/markdown/mermaid.ts +11 -0
  59. package/src/og/cache.ts +236 -0
  60. package/src/og/card.ts +12 -4
  61. package/src/og/index.ts +8 -1
  62. package/src/registry/eject.ts +23 -8
  63. package/src/theme/entry.ts +41 -7
  64. package/src/theme/fonts.ts +30 -23
  65. package/dist/cli/chunk-62qsssnh.js.map +0 -36
  66. package/dist/cli/chunk-aerwpe14.js.map +0 -15
  67. package/dist/cli/chunk-wb067mv3.js.map +0 -13
  68. /package/dist/cli/{chunk-nyqzjdhj.js.map → chunk-0qhq7b8q.js.map} +0 -0
  69. /package/dist/cli/{chunk-cnvm6k3e.js.map → chunk-18tjv4f7.js.map} +0 -0
  70. /package/dist/cli/{chunk-ag1zyr5x.js.map → chunk-9qs6acpw.js.map} +0 -0
  71. /package/dist/cli/{chunk-x1vrdjyk.js.map → chunk-cfw6x4rm.js.map} +0 -0
  72. /package/dist/cli/{chunk-bawgnt8x.js.map → chunk-ckh3a410.js.map} +0 -0
  73. /package/dist/cli/{chunk-j00ezcg5.js.map → chunk-drke6t0h.js.map} +0 -0
  74. /package/dist/cli/{chunk-3k0kzs6d.js.map → chunk-j6pxe0dt.js.map} +0 -0
  75. /package/dist/cli/{chunk-n0y172hf.js.map → chunk-jk1zwka1.js.map} +0 -0
  76. /package/dist/cli/{chunk-f75cqye8.js.map → chunk-jxkxjsc1.js.map} +0 -0
  77. /package/dist/cli/{chunk-s4k1pnvf.js.map → chunk-kwx90v78.js.map} +0 -0
  78. /package/dist/cli/{chunk-9sh49q0h.js.map → chunk-n0nyat6g.js.map} +0 -0
  79. /package/dist/cli/{chunk-wkq5tbtq.js.map → chunk-qq9nm3qd.js.map} +0 -0
  80. /package/dist/cli/{chunk-etsqspj6.js.map → chunk-s102bysw.js.map} +0 -0
  81. /package/dist/cli/{chunk-m3p3wahd.js.map → chunk-tnskyrej.js.map} +0 -0
  82. /package/dist/cli/{chunk-5yvt556e.js.map → chunk-v5mm027v.js.map} +0 -0
  83. /package/dist/cli/{chunk-0ewz4trd.js.map → chunk-y3g15rvv.js.map} +0 -0
  84. /package/dist/cli/{chunk-tc89yh2r.js.map → chunk-ye9zdkgv.js.map} +0 -0
  85. /package/dist/cli/{chunk-n4qjabmt.js.map → chunk-ynacq3ev.js.map} +0 -0
  86. /package/dist/cli/{chunk-s4jn7f1q.js.map → chunk-zr3ygrq3.js.map} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  buildSearchDocuments,
7
7
  markdownRoutePaths,
8
8
  renderRssFeed
9
- } from "./chunk-n4qjabmt.js";
9
+ } from "./chunk-ynacq3ev.js";
10
10
  import {
11
11
  MCP_TOOLS
12
12
  } from "./chunk-pxj10x8y.js";
@@ -19,7 +19,7 @@ import {
19
19
  import {
20
20
  collectContentAssets,
21
21
  rewriteRelativeImages
22
- } from "./chunk-5yvt556e.js";
22
+ } from "./chunk-v5mm027v.js";
23
23
  import {
24
24
  writeTextAtomic
25
25
  } from "./chunk-ev67ycx0.js";
@@ -45,11 +45,13 @@ import {
45
45
  exampleSlug,
46
46
  exampleWrapperTemplate,
47
47
  examplesPageTemplate,
48
+ featuresTemplate,
48
49
  islandMapTemplate,
49
50
  islandWrapperTemplate,
50
51
  mcpEndpointTemplate,
51
52
  mcpPageFile,
52
53
  mixedbreadSearchEndpointTemplate,
54
+ navFragmentTemplate,
53
55
  notFoundJsonTemplate,
54
56
  notFoundMarkdownTemplate,
55
57
  notFoundPageTemplate,
@@ -67,14 +69,14 @@ import {
67
69
  searchEndpointTemplate,
68
70
  stagedContentDir,
69
71
  staticJsonEndpointTemplate
70
- } from "./chunk-aerwpe14.js";
72
+ } from "./chunk-agy5rzxy.js";
71
73
  import {
72
74
  API_NAVIGATION_PATH,
73
75
  API_PAGES_PATH,
74
76
  API_PAGE_PATH,
75
77
  API_SEARCH_PATH,
76
78
  OPENAPI_PATH
77
- } from "./chunk-3k0kzs6d.js";
79
+ } from "./chunk-j6pxe0dt.js";
78
80
  import {
79
81
  absoluteUrl,
80
82
  siteRoot
@@ -91,7 +93,7 @@ import {
91
93
  resolveFallbackLocale,
92
94
  validateNavTargets,
93
95
  validateSearchPopularIcons
94
- } from "./chunk-etsqspj6.js";
96
+ } from "./chunk-s102bysw.js";
95
97
  import {
96
98
  EN_UI,
97
99
  GOOGLE_FONTS,
@@ -108,12 +110,15 @@ import {
108
110
  repoUrl,
109
111
  resolveReferences,
110
112
  resolveUIStrings
111
- } from "./chunk-vv3f8mb6.js";
113
+ } from "./chunk-xv91q4nm.js";
112
114
  import {
113
115
  normalizeBasePath,
114
116
  trimChar,
115
117
  withBasePath
116
118
  } from "./chunk-27gtm2ym.js";
119
+ import {
120
+ getBlumeVersion
121
+ } from "./chunk-4xyggvgf.js";
117
122
  import {
118
123
  packageRoot
119
124
  } from "./chunk-6kzzpsx8.js";
@@ -208,16 +213,53 @@ ${body}`,
208
213
  });
209
214
  };
210
215
 
216
+ // src/og/cache.ts
217
+ import { existsSync } from "node:fs";
218
+ import {
219
+ mkdir,
220
+ readdir,
221
+ readFile,
222
+ rename,
223
+ rm,
224
+ writeFile
225
+ } from "node:fs/promises";
226
+ import { dirname as dirname2, join as join4 } from "pathe";
227
+ var ogCacheDir = (context) => existsSync(join4(context.root, "node_modules")) ? join4(context.root, "node_modules", ".cache", "blume", "og") : join4(context.outDir, ".cache", "og");
228
+ var REGISTRY_KEY2 = Symbol.for("blume.og-cache");
229
+ var registry3 = () => {
230
+ const host = globalThis;
231
+ host[REGISTRY_KEY2] ??= { hits: 0, misses: 0, used: new Set };
232
+ return host[REGISTRY_KEY2];
233
+ };
234
+ var localFontDigests = new Map;
235
+ var inflight = new Map;
236
+ var pruneCardCache = async (dir) => {
237
+ const { used } = registry3();
238
+ let entries;
239
+ try {
240
+ entries = await readdir(dir);
241
+ } catch {
242
+ return 0;
243
+ }
244
+ const stale = entries.filter((name) => name.endsWith(".tmp") || name.endsWith(".png") && !used.has(name.slice(0, -".png".length)));
245
+ await Promise.all(stale.map((name) => rm(join4(dir, name), { force: true })));
246
+ return stale.length;
247
+ };
248
+ var cardCacheTally = () => {
249
+ const { hits, misses } = registry3();
250
+ return hits + misses === 0 ? null : { hits, misses };
251
+ };
252
+
211
253
  // src/astro/generate.ts
212
254
  import { createHash } from "node:crypto";
213
- import { existsSync as existsSync5, readFileSync as readFileSync3, realpathSync } from "node:fs";
255
+ import { existsSync as existsSync6, readFileSync as readFileSync3, realpathSync } from "node:fs";
214
256
  import {
215
257
  lstat,
216
- mkdir,
217
- readFile as readFile4,
258
+ mkdir as mkdir2,
259
+ readFile as readFile5,
218
260
  readlink,
219
261
  realpath,
220
- rm,
262
+ rm as rm2,
221
263
  symlink
222
264
  } from "node:fs/promises";
223
265
  import { createRequire as createRequire2 } from "node:module";
@@ -225,9 +267,9 @@ import { pathToFileURL } from "node:url";
225
267
  import pMap3 from "p-map";
226
268
  import {
227
269
  basename as basename2,
228
- dirname as dirname5,
270
+ dirname as dirname6,
229
271
  isAbsolute as isAbsolute6,
230
- join as join10,
272
+ join as join11,
231
273
  normalize,
232
274
  relative as relative4,
233
275
  resolve as resolve6
@@ -829,6 +871,22 @@ var buildMcpServerCard = (input) => {
829
871
  return card;
830
872
  };
831
873
 
874
+ // src/components/layout/nav-utils.ts
875
+ var hasDeferrableGroups = (sidebar) => sidebar.some((node) => node.kind === "group" && ((node.display ?? "flat") !== "flat" || hasDeferrableGroups(node.children)));
876
+ var navVariants = (data) => [
877
+ { locale: "default", navigation: data.navigation, version: "current" },
878
+ ...Object.entries(data.navigationByLocale).map(([locale, navigation]) => ({
879
+ locale,
880
+ navigation,
881
+ version: "current"
882
+ })),
883
+ ...Object.entries(data.navigationByVersion).flatMap(([version, byLocale]) => Object.entries(byLocale).map(([locale, navigation]) => ({
884
+ locale: locale || "default",
885
+ navigation,
886
+ version
887
+ })))
888
+ ];
889
+
832
890
  // src/core/builtin-tags.ts
833
891
  var BUILTIN_MDX_TAGS = new Set([
834
892
  "Accordion",
@@ -894,8 +952,8 @@ var validateUsedComponents = (pages, extraTags, registryNames) => {
894
952
  };
895
953
 
896
954
  // src/core/component-overrides.ts
897
- import { existsSync } from "node:fs";
898
- import { dirname as dirname2, extname, isAbsolute, resolve as resolve3 } from "pathe";
955
+ import { existsSync as existsSync2 } from "node:fs";
956
+ import { dirname as dirname3, extname, isAbsolute, resolve as resolve3 } from "pathe";
899
957
  import ts from "typescript";
900
958
  var GROUPS = ["mdx", "layout", "islands"];
901
959
  var GROUP_SET = new Set(GROUPS);
@@ -989,7 +1047,7 @@ var findDefaultExportObject = (sourceFile) => {
989
1047
  var probeExtension = (base) => {
990
1048
  for (const extension of COMPONENT_EXTS) {
991
1049
  const candidate = `${base}.${extension}`;
992
- if (existsSync(candidate)) {
1050
+ if (existsSync2(candidate)) {
993
1051
  return candidate;
994
1052
  }
995
1053
  }
@@ -1151,7 +1209,7 @@ var analyzeComponentOverrides = (source, filePath) => {
1151
1209
  return result;
1152
1210
  }
1153
1211
  const imports = collectImports(sourceFile);
1154
- const dir = dirname2(filePath);
1212
+ const dir = dirname3(filePath);
1155
1213
  for (const property of object.properties) {
1156
1214
  collectGroupOverrides(property, imports, dir, result);
1157
1215
  }
@@ -1222,8 +1280,8 @@ var svgDimensions = (svg) => {
1222
1280
  };
1223
1281
 
1224
1282
  // src/core/tsconfig-aliases.ts
1225
- import { existsSync as existsSync2 } from "node:fs";
1226
- import { dirname as dirname3, join as join4, resolve as resolve4 } from "pathe";
1283
+ import { existsSync as existsSync3 } from "node:fs";
1284
+ import { dirname as dirname4, join as join5, resolve as resolve4 } from "pathe";
1227
1285
  import ts2 from "typescript";
1228
1286
  var PARSE_HOST = {
1229
1287
  fileExists: ts2.sys.fileExists,
@@ -1245,7 +1303,7 @@ var toAlias = (key, value, baseDir) => {
1245
1303
  return { find, replacement: resolve4(baseDir, target) };
1246
1304
  };
1247
1305
  var resolveTsconfigAliases = (root) => {
1248
- const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join4(root, name)).find((file) => existsSync2(file));
1306
+ const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join5(root, name)).find((file) => existsSync3(file));
1249
1307
  if (!entry) {
1250
1308
  return {};
1251
1309
  }
@@ -1253,7 +1311,7 @@ var resolveTsconfigAliases = (root) => {
1253
1311
  if (error) {
1254
1312
  return {};
1255
1313
  }
1256
- const configDir = dirname3(entry);
1314
+ const configDir = dirname4(entry);
1257
1315
  const { options } = ts2.parseJsonConfigFileContent(config, PARSE_HOST, configDir, undefined, entry);
1258
1316
  const { paths } = options;
1259
1317
  if (!paths) {
@@ -1271,11 +1329,144 @@ var resolveTsconfigAliases = (root) => {
1271
1329
  return Object.fromEntries(entries);
1272
1330
  };
1273
1331
 
1332
+ // src/markdown/language-icon.ts
1333
+ import {
1334
+ siAstro,
1335
+ siC,
1336
+ siCplusplus,
1337
+ siCss,
1338
+ siDart,
1339
+ siDocker,
1340
+ siGnubash,
1341
+ siGo,
1342
+ siGraphql,
1343
+ siHtml5,
1344
+ siJavascript,
1345
+ siJson,
1346
+ siKotlin,
1347
+ siLess,
1348
+ siLua,
1349
+ siMarkdown,
1350
+ siMdx,
1351
+ siMysql,
1352
+ siNextdotjs,
1353
+ siPhp,
1354
+ siPrisma,
1355
+ siPython,
1356
+ siReact,
1357
+ siRuby,
1358
+ siRust,
1359
+ siSass,
1360
+ siScala,
1361
+ siSvelte,
1362
+ siSvg,
1363
+ siSwift,
1364
+ siToml,
1365
+ siTypescript,
1366
+ siVuedotjs,
1367
+ siWebassembly,
1368
+ siYaml
1369
+ } from "simple-icons";
1370
+ var LANGUAGE_ICONS = {
1371
+ astro: siAstro,
1372
+ bash: siGnubash,
1373
+ c: siC,
1374
+ "c++": siCplusplus,
1375
+ cjs: siJavascript,
1376
+ cpp: siCplusplus,
1377
+ css: siCss,
1378
+ cts: siTypescript,
1379
+ dart: siDart,
1380
+ docker: siDocker,
1381
+ dockerfile: siDocker,
1382
+ go: siGo,
1383
+ gql: siGraphql,
1384
+ graphql: siGraphql,
1385
+ html: siHtml5,
1386
+ javascript: siJavascript,
1387
+ js: siJavascript,
1388
+ json: siJson,
1389
+ json5: siJson,
1390
+ jsonc: siJson,
1391
+ jsx: siReact,
1392
+ kotlin: siKotlin,
1393
+ kt: siKotlin,
1394
+ less: siLess,
1395
+ lua: siLua,
1396
+ markdown: siMarkdown,
1397
+ md: siMarkdown,
1398
+ mdx: siMdx,
1399
+ mjs: siJavascript,
1400
+ mts: siTypescript,
1401
+ nextjs: siNextdotjs,
1402
+ php: siPhp,
1403
+ prisma: siPrisma,
1404
+ py: siPython,
1405
+ python: siPython,
1406
+ rb: siRuby,
1407
+ react: siReact,
1408
+ rs: siRust,
1409
+ ruby: siRuby,
1410
+ rust: siRust,
1411
+ sass: siSass,
1412
+ scala: siScala,
1413
+ scss: siSass,
1414
+ sh: siGnubash,
1415
+ shell: siGnubash,
1416
+ sql: siMysql,
1417
+ svelte: siSvelte,
1418
+ svg: siSvg,
1419
+ swift: siSwift,
1420
+ toml: siToml,
1421
+ ts: siTypescript,
1422
+ tsx: siReact,
1423
+ typescript: siTypescript,
1424
+ vue: siVuedotjs,
1425
+ wasm: siWebassembly,
1426
+ yaml: siYaml,
1427
+ yml: siYaml,
1428
+ zsh: siGnubash
1429
+ };
1430
+ var languageIconSlug = (language) => LANGUAGE_ICONS[language.toLowerCase()]?.slug ?? null;
1431
+ var FENCE_LANGUAGE = /^[ \t]*(?:`{3,}|~{3,})[ \t]*(?<lang>[\w+#.-]+)/gmu;
1432
+ var PROP_LANGUAGE = /\blang(?:uage)?=["'](?<lang>[\w+#.-]+)["']/gu;
1433
+ var languageIconSlugsIn = (markdown) => {
1434
+ const slugs = new Set;
1435
+ for (const pattern of [FENCE_LANGUAGE, PROP_LANGUAGE]) {
1436
+ for (const match of markdown.matchAll(pattern)) {
1437
+ const slug = languageIconSlug(match.groups?.lang ?? "");
1438
+ if (slug) {
1439
+ slugs.add(slug);
1440
+ }
1441
+ }
1442
+ }
1443
+ return [...slugs].toSorted();
1444
+ };
1445
+ var iconBySlug = (slug) => Object.values(LANGUAGE_ICONS).find((icon) => icon.slug === slug);
1446
+ var maskUri = (path) => `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="${path}"/></svg>`)}")`;
1447
+ var languageIconCss = (slugs) => slugs.map((slug) => {
1448
+ const icon = iconBySlug(slug);
1449
+ if (!icon) {
1450
+ return "";
1451
+ }
1452
+ const mask = maskUri(icon.path);
1453
+ return `.prose > :where(pre[data-language][data-icon="${slug}"])::after {
1454
+ background-color: var(--blume-muted-foreground);
1455
+ -webkit-mask-image: ${mask};
1456
+ mask-image: ${mask};
1457
+ }`;
1458
+ }).filter((rule) => rule !== "").join(`
1459
+ `);
1460
+
1461
+ // src/markdown/mermaid.ts
1462
+ var MERMAID_FENCE = /^[ \t]*(?:`{3,}|~{3,})[ \t]*mermaid\b/mu;
1463
+ var hasMermaidFence = (text) => MERMAID_FENCE.test(text);
1464
+
1274
1465
  // src/og/derive.ts
1275
- import { existsSync as existsSync3 } from "node:fs";
1276
- import { isAbsolute as isAbsolute2, join as join5 } from "pathe";
1466
+ import { existsSync as existsSync4 } from "node:fs";
1467
+ import { isAbsolute as isAbsolute2, join as join6 } from "pathe";
1277
1468
  var CARD_WEIGHTS = [400, 600];
1278
- var absoluteSrc = (root, src) => isAbsolute2(src) ? src : join5(root, src);
1469
+ var absoluteSrc = (root, src) => isAbsolute2(src) ? src : join6(root, src);
1279
1470
  var isNumericWeight = (weight) => typeof weight === "number";
1280
1471
  var isRangeWeight = (weight) => typeof weight === "string";
1281
1472
  var isFontName = (value) => typeof value === "string";
@@ -1376,27 +1567,27 @@ var missingFontFiles = (options, root) => {
1376
1567
  sources.push(absoluteSrc(root, font.src));
1377
1568
  }
1378
1569
  }
1379
- return [...new Set(sources)].filter((path) => !existsSync3(path));
1570
+ return [...new Set(sources)].filter((path) => !existsSync4(path));
1380
1571
  };
1381
1572
 
1382
1573
  // src/og/logo.ts
1383
- import { existsSync as existsSync4, readFileSync } from "node:fs";
1384
- import { join as join6 } from "pathe";
1574
+ import { existsSync as existsSync5, readFileSync } from "node:fs";
1575
+ import { join as join7 } from "pathe";
1385
1576
  var resolveOgLogo = (project, source) => {
1386
1577
  if (!source?.toLowerCase().endsWith(".svg")) {
1387
1578
  return;
1388
1579
  }
1389
1580
  const relative2 = source.replace(/^\//u, "");
1390
1581
  const file = [
1391
- join6(project.context.root, "public", relative2),
1392
- join6(project.context.root, relative2)
1393
- ].find((path) => existsSync4(path));
1582
+ join7(project.context.root, "public", relative2),
1583
+ join7(project.context.root, relative2)
1584
+ ].find((path) => existsSync5(path));
1394
1585
  return file ? readFileSync(file, "utf-8") : undefined;
1395
1586
  };
1396
1587
 
1397
1588
  // src/openapi/scalar.ts
1398
- import { readFile } from "node:fs/promises";
1399
- import { isAbsolute as isAbsolute3, join as join7 } from "pathe";
1589
+ import { readFile as readFile2 } from "node:fs/promises";
1590
+ import { isAbsolute as isAbsolute3, join as join8 } from "pathe";
1400
1591
 
1401
1592
  // src/theme/palette.ts
1402
1593
  var FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
@@ -1492,9 +1683,9 @@ var specConfiguration = async (spec, root) => {
1492
1683
  if (URL_SPEC.test(spec)) {
1493
1684
  return { config: { url: spec } };
1494
1685
  }
1495
- const absolute = isAbsolute3(spec) ? spec : join7(root, spec);
1686
+ const absolute = isAbsolute3(spec) ? spec : join8(root, spec);
1496
1687
  try {
1497
- return { config: { content: await readFile(absolute, "utf-8") } };
1688
+ return { config: { content: await readFile2(absolute, "utf-8") } };
1498
1689
  } catch {
1499
1690
  return {
1500
1691
  config: { url: spec },
@@ -1770,6 +1961,31 @@ ${THEME_MAPPING}
1770
1961
  unicode-bidi: isolate;
1771
1962
  }
1772
1963
 
1964
+ /* The sidebar's rows. A docs site renders every sidebar row on every page,
1965
+ so a large site's HTML is mostly this markup — one utility per row kind
1966
+ instead of the dozen classes each expands to keeps every page (and the
1967
+ build's HTML output) small. Kept in sync with NavTree.astro: a page row,
1968
+ a page-mode group's drill-in button, a collapsible group's summary and
1969
+ the link inside it, and a flat group's heading and the link inside it. */
1970
+ @utility blume-nav-link {
1971
+ @apply block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground;
1972
+ }
1973
+ @utility blume-nav-drill {
1974
+ @apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-left font-medium text-foreground text-sm transition-colors hover:bg-muted;
1975
+ }
1976
+ @utility blume-nav-summary {
1977
+ @apply flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden;
1978
+ }
1979
+ @utility blume-nav-summary-link {
1980
+ @apply -my-1 flex flex-1 items-center gap-1.5 rounded py-1 transition-colors aria-[current=page]:text-foreground aria-[current=page]:font-semibold;
1981
+ }
1982
+ @utility blume-nav-heading {
1983
+ @apply mb-2 flex items-center gap-1.5 px-2.5 font-medium text-foreground text-sm;
1984
+ }
1985
+ @utility blume-nav-heading-link {
1986
+ @apply -ml-1 flex flex-1 items-center gap-1.5 rounded-[0.65rem] px-1 py-0.5 text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-muted;
1987
+ }
1988
+
1773
1989
  /* Theme Tailwind Typography (prose) with Blume tokens. */
1774
1990
  .prose {
1775
1991
  --tw-prose-body: var(--blume-foreground);
@@ -1985,15 +2201,17 @@ blume-diff {
1985
2201
  CodeBlock (no title) never gets data-language, so without this gate the
1986
2202
  absolutely-positioned icon would overlap the first code line. Fenced code
1987
2203
  and titled blocks always have a header, so the icon shows there. */
1988
- .blume-lang-icon {
1989
- display: none;
1990
- }
1991
-
1992
- .prose > :where(pre[data-language][data-icon]) > .blume-lang-icon {
1993
- color: var(--blume-muted-foreground);
1994
- display: block;
2204
+ .prose > :where(pre[data-language][data-icon])::after {
2205
+ content: "";
1995
2206
  height: 0.875rem;
1996
2207
  left: 1rem;
2208
+ -webkit-mask-position: center;
2209
+ -webkit-mask-repeat: no-repeat;
2210
+ -webkit-mask-size: contain;
2211
+ mask-position: center;
2212
+ mask-repeat: no-repeat;
2213
+ mask-size: contain;
2214
+ pointer-events: none;
1997
2215
  position: absolute;
1998
2216
  top: 0.875rem;
1999
2217
  width: 0.875rem;
@@ -2003,6 +2221,11 @@ blume-diff {
2003
2221
  padding-left: 2.5rem;
2004
2222
  }
2005
2223
 
2224
+ /* One mask rule per language the site's Markdown uses (see
2225
+ markdown/language-icon.ts); the ::after above only gets a color and a mask
2226
+ from these, so a block whose language has no rule paints nothing. */
2227
+ ${options.languageIcons ?? ""}
2228
+
2006
2229
  .prose :where(pre code) {
2007
2230
  background: transparent;
2008
2231
  border-radius: 0;
@@ -2401,10 +2624,10 @@ ${options.userCss}
2401
2624
  `;
2402
2625
 
2403
2626
  // src/theme/sources.ts
2404
- import { dirname as dirname4, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve5 } from "pathe";
2627
+ import { dirname as dirname5, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve5 } from "pathe";
2405
2628
  var SOURCE_DIRECTIVE = /@source(?<not>\s+not)?\s+(?<quote>["'])(?<path>[^"']+)\k<quote>/gu;
2406
2629
  var rebaseSourceDirectives = (css, options) => {
2407
- const base = dirname4(options.from);
2630
+ const base = dirname5(options.from);
2408
2631
  return css.replace(SOURCE_DIRECTIVE, (directive, not, quote, path) => {
2409
2632
  if (isAbsolute4(path)) {
2410
2633
  return directive;
@@ -2597,15 +2820,15 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
2597
2820
  };
2598
2821
 
2599
2822
  // src/astro/examples.ts
2600
- import { readFile as readFile3 } from "node:fs/promises";
2823
+ import { readFile as readFile4 } from "node:fs/promises";
2601
2824
  import pMap2 from "p-map";
2602
- import { isAbsolute as isAbsolute5, join as join9, relative as relative3 } from "pathe";
2825
+ import { isAbsolute as isAbsolute5, join as join10, relative as relative3 } from "pathe";
2603
2826
  import { glob as glob2 } from "tinyglobby";
2604
2827
 
2605
2828
  // src/astro/islands.ts
2606
- import { readFile as readFile2 } from "node:fs/promises";
2829
+ import { readFile as readFile3 } from "node:fs/promises";
2607
2830
  import pMap from "p-map";
2608
- import { basename, join as join8 } from "pathe";
2831
+ import { basename, join as join9 } from "pathe";
2609
2832
  import { glob } from "tinyglobby";
2610
2833
  var DEFAULT_CLIENT = "visible";
2611
2834
  var READ_CONCURRENCY = 16;
@@ -2636,14 +2859,14 @@ var readClientMode = (source, file, warnings) => {
2636
2859
  return mode;
2637
2860
  };
2638
2861
  var discoverIslands = async (root) => {
2639
- const dir = join8(root, "islands");
2862
+ const dir = join9(root, "islands");
2640
2863
  const matches = await glob(["**/*"], {
2641
2864
  absolute: true,
2642
2865
  cwd: dir,
2643
2866
  onlyFiles: true
2644
2867
  });
2645
2868
  const files = matches.toSorted();
2646
- const sources = await pMap(files, (file) => readFile2(file, "utf-8"), {
2869
+ const sources = await pMap(files, (file) => readFile3(file, "utf-8"), {
2647
2870
  concurrency: READ_CONCURRENCY
2648
2871
  });
2649
2872
  const islands = [];
@@ -2708,14 +2931,14 @@ var splitGlobBase = (pattern) => {
2708
2931
  };
2709
2932
  var discoverExamples = async (root, pattern = "examples") => {
2710
2933
  const { base, rest } = GLOB_MAGIC.test(pattern) ? splitGlobBase(pattern) : { base: pattern, rest: DEFAULT_EXAMPLE_GLOB };
2711
- const dir = join9(root, base);
2934
+ const dir = join10(root, base);
2712
2935
  const matches = await glob2([rest], {
2713
2936
  absolute: true,
2714
2937
  cwd: dir,
2715
2938
  onlyFiles: true
2716
2939
  });
2717
2940
  const files = matches.toSorted();
2718
- const sources = await pMap2(files, (file) => readFile3(file, "utf-8"), {
2941
+ const sources = await pMap2(files, (file) => readFile4(file, "utf-8"), {
2719
2942
  concurrency: READ_CONCURRENCY2
2720
2943
  });
2721
2944
  const examples = [];
@@ -2754,10 +2977,10 @@ var exampleMarkdownLookup = (examples) => Object.fromEntries(examples.map((examp
2754
2977
  ]));
2755
2978
 
2756
2979
  // src/astro/generate.ts
2757
- var BLUME_SRC = join10(packageRoot(), "src");
2980
+ var BLUME_SRC = join11(packageRoot(), "src");
2758
2981
  var canResolveFrom = (fromDir, spec) => {
2759
2982
  try {
2760
- createRequire2(pathToFileURL(join10(fromDir, "_.js")).href).resolve(spec);
2983
+ createRequire2(pathToFileURL(join11(fromDir, "_.js")).href).resolve(spec);
2761
2984
  return true;
2762
2985
  } catch {
2763
2986
  return false;
@@ -2768,7 +2991,7 @@ var resolveReactCompiler = (config, needsReact, pkgDir = packageRoot()) => {
2768
2991
  return null;
2769
2992
  }
2770
2993
  try {
2771
- return createRequire2(pathToFileURL(join10(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
2994
+ return createRequire2(pathToFileURL(join11(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
2772
2995
  } catch {
2773
2996
  return null;
2774
2997
  }
@@ -2778,7 +3001,7 @@ var reactCompilerWarnings = (config, needsReact, compilerPath) => needsReact &&
2778
3001
  ] : [];
2779
3002
  var resolveAstroPackageJson = (modulesDir) => {
2780
3003
  try {
2781
- return realpathSync(join10(modulesDir, "astro", "package.json"));
3004
+ return realpathSync(join11(modulesDir, "astro", "package.json"));
2782
3005
  } catch {
2783
3006
  return null;
2784
3007
  }
@@ -2786,12 +3009,12 @@ var resolveAstroPackageJson = (modulesDir) => {
2786
3009
  var resolvedAstroHit = (fromDir) => {
2787
3010
  let dir = normalize(fromDir);
2788
3011
  while (true) {
2789
- const modulesDir = join10(dir, "node_modules");
3012
+ const modulesDir = join11(dir, "node_modules");
2790
3013
  const pkg = resolveAstroPackageJson(modulesDir);
2791
3014
  if (pkg) {
2792
3015
  return { modulesDir, pkg };
2793
3016
  }
2794
- const parent = dirname5(dir);
3017
+ const parent = dirname6(dir);
2795
3018
  if (parent === dir) {
2796
3019
  return null;
2797
3020
  }
@@ -2806,10 +3029,17 @@ var sameRealDir = (a, b) => {
2806
3029
  }
2807
3030
  };
2808
3031
  var depsCandidates = (pkgDir) => [
2809
- join10(pkgDir, "node_modules"),
2810
- dirname5(pkgDir)
3032
+ join11(pkgDir, "node_modules"),
3033
+ dirname6(pkgDir)
2811
3034
  ];
2812
- var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync5(join10(dir, ...segments))) ?? null;
3035
+ var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync6(join11(dir, ...segments))) ?? null;
3036
+ var symlinkDir = async (target, link) => {
3037
+ try {
3038
+ await symlink(target, link, "dir");
3039
+ } catch {
3040
+ await symlink(target, link, "junction");
3041
+ }
3042
+ };
2813
3043
  var linkDepsJunction = async (link, depsDir) => {
2814
3044
  let existing;
2815
3045
  try {
@@ -2822,14 +3052,14 @@ var linkDepsJunction = async (link, depsDir) => {
2822
3052
  return;
2823
3053
  }
2824
3054
  try {
2825
- if (resolve6(dirname5(link), await readlink(link)) === resolve6(depsDir)) {
3055
+ if (resolve6(dirname6(link), await readlink(link)) === resolve6(depsDir)) {
2826
3056
  return;
2827
3057
  }
2828
3058
  } catch {}
2829
- await rm(link, { force: true });
3059
+ await rm2(link, { force: true });
2830
3060
  }
2831
- await mkdir(dirname5(link), { recursive: true });
2832
- await symlink(depsDir, link, "junction");
3061
+ await mkdir2(dirname6(link), { recursive: true });
3062
+ await symlinkDir(depsDir, link);
2833
3063
  };
2834
3064
  var readPkgVersion = (pkgJsonPath) => {
2835
3065
  if (!pkgJsonPath) {
@@ -2861,13 +3091,13 @@ var dropStaleDepsLink = async (link, pkgDir) => {
2861
3091
  let linkedBlume;
2862
3092
  let runningBlume;
2863
3093
  try {
2864
- linkedBlume = await realpath(join10(link, "blume"));
3094
+ linkedBlume = await realpath(join11(link, "blume"));
2865
3095
  runningBlume = await realpath(pkgDir);
2866
3096
  } catch {
2867
3097
  return;
2868
3098
  }
2869
3099
  if (linkedBlume !== runningBlume) {
2870
- await rm(link, { force: true });
3100
+ await rm2(link, { force: true });
2871
3101
  }
2872
3102
  };
2873
3103
  var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
@@ -2877,7 +3107,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
2877
3107
  }
2878
3108
  const mdxDir = candidateHolding(pkgDir, "@astrojs", "mdx");
2879
3109
  const blumeAstro = resolveAstroPackageJson(astroDir);
2880
- await dropStaleDepsLink(join10(outDir, "node_modules"), pkgDir);
3110
+ await dropStaleDepsLink(join11(outDir, "node_modules"), pkgDir);
2881
3111
  const outDirHit = resolvedAstroHit(outDir);
2882
3112
  const astroCorrect = blumeAstro !== null && outDirHit?.pkg === blumeAstro;
2883
3113
  const walkLandsInDeps = astroCorrect && outDirHit !== null && sameRealDir(outDirHit.modulesDir, astroDir);
@@ -2885,7 +3115,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
2885
3115
  return null;
2886
3116
  }
2887
3117
  if (mdxDir && (mdxDir === astroDir || astroCorrect)) {
2888
- await linkDepsJunction(join10(outDir, "node_modules"), mdxDir);
3118
+ await linkDepsJunction(join11(outDir, "node_modules"), mdxDir);
2889
3119
  return null;
2890
3120
  }
2891
3121
  return astroConflictWarning(blumeAstro, outDirHit?.pkg ?? null);
@@ -2938,14 +3168,14 @@ var askProviderWarnings = (ask, root, pkgDir = packageRoot()) => {
2938
3168
  `Ask AI provider "${ask.provider}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`
2939
3169
  ];
2940
3170
  };
2941
- var examplesCssFile = (root, config) => config.examples.css ? join10(root, config.examples.css) : null;
3171
+ var examplesCssFile = (root, config) => config.examples.css ? join11(root, config.examples.css) : null;
2942
3172
  var writeExamplesPreview = async (options) => {
2943
3173
  const { config, hasExamples, root, srcDir, write } = options;
2944
3174
  if (hasExamples) {
2945
- await write(join10(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
3175
+ await write(join11(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
2946
3176
  }
2947
3177
  const cssFile = examplesCssFile(root, config);
2948
- return cssFile && !existsSync5(cssFile) ? [
3178
+ return cssFile && !existsSync6(cssFile) ? [
2949
3179
  `examples.css points at "${config.examples.css}", which doesn't exist; previews render without it.`
2950
3180
  ] : [];
2951
3181
  };
@@ -2954,7 +3184,7 @@ var readOptional = async (path) => {
2954
3184
  return "";
2955
3185
  }
2956
3186
  try {
2957
- return await readFile4(path, "utf-8");
3187
+ return await readFile5(path, "utf-8");
2958
3188
  } catch {
2959
3189
  return "";
2960
3190
  }
@@ -2979,7 +3209,7 @@ var detectUsesMath = async (root, staged = []) => {
2979
3209
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
2980
3210
  onlyFiles: true
2981
3211
  });
2982
- const contents = await pMap3(files, (file) => readOptional(join10(root, file)), {
3212
+ const contents = await pMap3(files, (file) => readOptional(join11(root, file)), {
2983
3213
  concurrency: READ_CONCURRENCY3
2984
3214
  });
2985
3215
  return [...contents, ...staged].some(containsMath);
@@ -2997,7 +3227,7 @@ var loadIntegrationBridge = async (config, context) => {
2997
3227
  var writeIfChanged = async (path, content) => {
2998
3228
  let existing = null;
2999
3229
  try {
3000
- existing = await readFile4(path, "utf-8");
3230
+ existing = await readFile5(path, "utf-8");
3001
3231
  } catch {
3002
3232
  existing = null;
3003
3233
  }
@@ -3017,7 +3247,7 @@ var pruneOrphans = async (srcDir, written) => {
3017
3247
  for (const path of existing) {
3018
3248
  const normalized = normalize(path);
3019
3249
  if (!written.has(normalized)) {
3020
- removals.push(rm(normalized, { force: true }));
3250
+ removals.push(rm2(normalized, { force: true }));
3021
3251
  }
3022
3252
  }
3023
3253
  await Promise.all(removals);
@@ -3041,11 +3271,11 @@ var writeStagedContent = async (out, staged) => {
3041
3271
  const contentDir = stagedContentDir(out);
3042
3272
  const written = new Set;
3043
3273
  await Promise.all([...staged].map(async ([entryId, text]) => {
3044
- const path = join10(contentDir, entryId);
3274
+ const path = join11(contentDir, entryId);
3045
3275
  written.add(normalize(path));
3046
3276
  await writeIfChanged(path, text);
3047
3277
  }));
3048
- if (existsSync5(contentDir)) {
3278
+ if (existsSync6(contentDir)) {
3049
3279
  await pruneOrphans(contentDir, written);
3050
3280
  }
3051
3281
  };
@@ -3056,9 +3286,9 @@ var readLogoSvg = (project, source) => {
3056
3286
  }
3057
3287
  const rel = source.replace(/^\//u, "");
3058
3288
  const file = [
3059
- join10(project.context.root, "public", rel),
3060
- join10(project.context.root, rel)
3061
- ].find((path) => existsSync5(path));
3289
+ join11(project.context.root, "public", rel),
3290
+ join11(project.context.root, rel)
3291
+ ].find((path) => existsSync6(path));
3062
3292
  return file ? readFileSync3(file, "utf-8") : undefined;
3063
3293
  };
3064
3294
  var isStringShorthand = (value) => typeof value === "string";
@@ -3113,10 +3343,10 @@ var faviconType = (name) => {
3113
3343
  var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync3(file).toString("base64")}`;
3114
3344
  var defaultFavicon = () => ({
3115
3345
  dark: {
3116
- href: inlineDataUri(join10(BLUME_SRC, "assets", "icon-dark.png"), "image/png"),
3346
+ href: inlineDataUri(join11(BLUME_SRC, "assets", "icon-dark.png"), "image/png"),
3117
3347
  type: "image/png"
3118
3348
  },
3119
- href: inlineDataUri(join10(BLUME_SRC, "assets", "icon.png"), "image/png"),
3349
+ href: inlineDataUri(join11(BLUME_SRC, "assets", "icon.png"), "image/png"),
3120
3350
  type: "image/png"
3121
3351
  });
3122
3352
  var APPLE_ICON_CANDIDATES = [
@@ -3128,13 +3358,13 @@ var APPLE_ICON_CANDIDATES = [
3128
3358
  var resolveIconFile = (project, candidates) => {
3129
3359
  const { root } = project.context;
3130
3360
  for (const name of candidates) {
3131
- if (existsSync5(join10(root, "public", name))) {
3361
+ if (existsSync6(join11(root, "public", name))) {
3132
3362
  return { href: `/${name}`, type: faviconType(name) };
3133
3363
  }
3134
3364
  }
3135
3365
  for (const name of candidates) {
3136
- const file = join10(root, name);
3137
- if (existsSync5(file)) {
3366
+ const file = join11(root, name);
3367
+ if (existsSync6(file)) {
3138
3368
  const type = faviconType(name);
3139
3369
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
3140
3370
  }
@@ -3144,19 +3374,19 @@ var resolveIconFile = (project, candidates) => {
3144
3374
  var resolveFavicon = (project) => {
3145
3375
  const { root } = project.context;
3146
3376
  for (const name of FAVICON_CANDIDATES) {
3147
- if (existsSync5(join10(root, "public", name))) {
3377
+ if (existsSync6(join11(root, "public", name))) {
3148
3378
  const type = faviconType(name);
3149
3379
  const sibling = darkSibling(name);
3150
- return existsSync5(join10(root, "public", sibling)) ? { dark: { href: `/${sibling}`, type }, href: `/${name}`, type } : { href: `/${name}`, type };
3380
+ return existsSync6(join11(root, "public", sibling)) ? { dark: { href: `/${sibling}`, type }, href: `/${name}`, type } : { href: `/${name}`, type };
3151
3381
  }
3152
3382
  }
3153
3383
  for (const name of FAVICON_CANDIDATES) {
3154
- const file = join10(root, name);
3155
- if (existsSync5(file)) {
3384
+ const file = join11(root, name);
3385
+ if (existsSync6(file)) {
3156
3386
  const type = faviconType(name);
3157
3387
  const mime = type ?? "image/x-icon";
3158
- const siblingFile = join10(root, darkSibling(name));
3159
- return existsSync5(siblingFile) ? {
3388
+ const siblingFile = join11(root, darkSibling(name));
3389
+ return existsSync6(siblingFile) ? {
3160
3390
  dark: { href: inlineDataUri(siblingFile, mime), type },
3161
3391
  href: inlineDataUri(file, mime),
3162
3392
  type
@@ -3227,7 +3457,7 @@ var buildRuntimeData = (project) => {
3227
3457
  }
3228
3458
  const rel = relative4(context.root, sourcePath).split("\\").join("/");
3229
3459
  const editDir = trimChar(github?.dir ?? "", "/");
3230
- const editPath = normalize(editDir ? join10(editDir, rel) : rel);
3460
+ const editPath = normalize(editDir ? join11(editDir, rel) : rel);
3231
3461
  if (editPath.startsWith("..") || isAbsolute6(editPath)) {
3232
3462
  return null;
3233
3463
  }
@@ -3371,7 +3601,7 @@ var buildRuntimeData = (project) => {
3371
3601
  var planMcp = (project, srcDir, userPages) => {
3372
3602
  const { config } = project;
3373
3603
  const { route } = config.ai.mcp;
3374
- const dir = join10(srcDir, "blume-mcp");
3604
+ const dir = join11(srcDir, "blume-mcp");
3375
3605
  const base = {
3376
3606
  dir,
3377
3607
  discoveryPages: [],
@@ -3395,11 +3625,11 @@ var planMcp = (project, srcDir, userPages) => {
3395
3625
  ...base,
3396
3626
  discoveryPages: [
3397
3627
  {
3398
- entrypoint: join10(dir, "discovery.ts"),
3628
+ entrypoint: join11(dir, "discovery.ts"),
3399
3629
  pattern: "/.well-known/mcp.json"
3400
3630
  },
3401
3631
  {
3402
- entrypoint: join10(dir, "server-card.ts"),
3632
+ entrypoint: join11(dir, "server-card.ts"),
3403
3633
  pattern: "/.well-known/mcp/server-card.json"
3404
3634
  }
3405
3635
  ],
@@ -3426,9 +3656,9 @@ var writeMcpFiles = async (plan, write, data) => {
3426
3656
  version: data.version
3427
3657
  };
3428
3658
  await Promise.all([
3429
- write(join10(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate()),
3430
- write(join10(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
3431
- write(join10(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
3659
+ write(join11(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate()),
3660
+ write(join11(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
3661
+ write(join11(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
3432
3662
  ]);
3433
3663
  };
3434
3664
  var ownsApiRest = (page) => page.pattern.startsWith("/api/[");
@@ -3440,7 +3670,7 @@ var planApi = (project, srcDir, userPages) => {
3440
3670
  catchAll: server && !userPages.some(ownsApiRest) && !project.graph.pages.some(contentUnderApi),
3441
3671
  enabled: config.ai.api,
3442
3672
  server,
3443
- spec: !routeIsTaken(userPages, project.graph.pages, OPENAPI_PATH) && !existsSync5(join10(context.root, "public", "openapi.json")),
3673
+ spec: !routeIsTaken(userPages, project.graph.pages, OPENAPI_PATH) && !existsSync6(join11(context.root, "public", "openapi.json")),
3444
3674
  srcDir
3445
3675
  };
3446
3676
  };
@@ -3450,20 +3680,20 @@ var writeApiFiles = async (project, plan, write, data, mcp) => {
3450
3680
  }
3451
3681
  const { config } = project;
3452
3682
  const mcpRoute = mcp.enabled ? mcp.route : null;
3453
- const apiDir = join10(plan.srcDir, "pages", "api");
3683
+ const apiDir = join11(plan.srcDir, "pages", "api");
3454
3684
  const writes = [
3455
- write(join10(apiDir, "docs", "pages.json.ts"), apiPagesIndexTemplate()),
3456
- write(join10(apiDir, "docs", "pages", "[...route].json.ts"), apiPageTemplate()),
3457
- write(join10(apiDir, "docs", "navigation.json.ts"), apiNavigationTemplate())
3685
+ write(join11(apiDir, "docs", "pages.json.ts"), apiPagesIndexTemplate()),
3686
+ write(join11(apiDir, "docs", "pages", "[...route].json.ts"), apiPageTemplate()),
3687
+ write(join11(apiDir, "docs", "navigation.json.ts"), apiNavigationTemplate())
3458
3688
  ];
3459
3689
  if (plan.server) {
3460
- writes.push(write(join10(apiDir, "docs", "search.ts"), apiSearchTemplate()));
3690
+ writes.push(write(join11(apiDir, "docs", "search.ts"), apiSearchTemplate()));
3461
3691
  }
3462
3692
  if (plan.catchAll) {
3463
- writes.push(write(join10(apiDir, "[...path].ts"), apiNotFoundTemplate({ base: data.base, site: data.site })));
3693
+ writes.push(write(join11(apiDir, "[...path].ts"), apiNotFoundTemplate({ base: data.base, site: data.site })));
3464
3694
  }
3465
3695
  if (plan.spec) {
3466
- writes.push(write(join10(plan.srcDir, "pages", "openapi.json.ts"), staticJsonEndpointTemplate(buildApiSpec({
3696
+ writes.push(write(join11(plan.srcDir, "pages", "openapi.json.ts"), staticJsonEndpointTemplate(buildApiSpec({
3467
3697
  agentReadability: config.seo.agentReadability,
3468
3698
  base: data.base,
3469
3699
  description: config.description,
@@ -3479,7 +3709,7 @@ var writeApiFiles = async (project, plan, write, data, mcp) => {
3479
3709
  };
3480
3710
  var planPlaygroundProxy = (config, srcDir) => ({
3481
3711
  enabled: needsPlaygroundProxy(config),
3482
- entrypoint: join10(srcDir, "blume-openapi", "api-proxy.ts"),
3712
+ entrypoint: join11(srcDir, "blume-openapi", "api-proxy.ts"),
3483
3713
  pattern: "/_api-proxy"
3484
3714
  });
3485
3715
  var specOriginsOf = (spec) => {
@@ -3522,7 +3752,7 @@ var writeAskFiles = async (project, srcDir, write, modules) => {
3522
3752
  if (grounded) {
3523
3753
  modules.set("blume:ask-data", JSON.stringify(await buildAskData(project)));
3524
3754
  }
3525
- await write(join10(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded, {
3755
+ await write(join11(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded, {
3526
3756
  instructions: ask.instructions,
3527
3757
  retrieval: ask.retrieval
3528
3758
  }));
@@ -3532,14 +3762,14 @@ var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
3532
3762
  return;
3533
3763
  }
3534
3764
  await Promise.all([
3535
- write(join10(srcDir, "pages", "404.astro"), notFoundPageTemplate()),
3536
- write(join10(srcDir, "pages", "404.md.ts"), notFoundMarkdownTemplate()),
3537
- write(join10(srcDir, "pages", "404.json.ts"), notFoundJsonTemplate())
3765
+ write(join11(srcDir, "pages", "404.astro"), notFoundPageTemplate()),
3766
+ write(join11(srcDir, "pages", "404.md.ts"), notFoundMarkdownTemplate()),
3767
+ write(join11(srcDir, "pages", "404.json.ts"), notFoundJsonTemplate())
3538
3768
  ]);
3539
3769
  };
3540
3770
  var diagnosticWarning = (diagnostic) => diagnostic.suggestion ? `${diagnostic.message} ${diagnostic.suggestion}` : diagnostic.message;
3541
3771
  var buildComponentSlots = async (componentsFile) => {
3542
- const analysis = componentsFile ? analyzeComponentOverrides(await readFile4(componentsFile, "utf-8"), componentsFile) : null;
3772
+ const analysis = componentsFile ? analyzeComponentOverrides(await readFile5(componentsFile, "utf-8"), componentsFile) : null;
3543
3773
  return {
3544
3774
  plan: planComponentSlots(componentsFile, analysis),
3545
3775
  tags: analysis ? [...analysis.mdx, ...analysis.islands].map((entry) => entry.key) : [],
@@ -3562,17 +3792,30 @@ var assertFontFilesExist = (project) => {
3562
3792
  });
3563
3793
  }
3564
3794
  };
3795
+ var clientFeaturesFrom = (project, rawMarkdown) => ({
3796
+ epub: project.config.export.epub,
3797
+ mermaid: Object.values(rawMarkdown).some((entry) => hasMermaidFence(entry.mdx ?? entry.md ?? "")) || project.graph.pages.some((page) => page.body !== undefined && hasMermaidFence(page.body.text))
3798
+ });
3799
+ var languageIconCssFrom = (project, rawMarkdown) => languageIconCss(languageIconSlugsIn([
3800
+ ...Object.values(rawMarkdown).map((entry) => entry.mdx ?? entry.md ?? ""),
3801
+ ...project.graph.pages.map((page) => page.body?.text ?? "")
3802
+ ].join(`
3803
+ `)));
3804
+ var languageIconCssFor = async (project) => languageIconCssFrom(project, await buildRawMarkdown(project));
3805
+ var writeNavFragments = (write, srcDir, navFragments) => navFragments ? write(join11(srcDir, "pages", "blume-nav", "[version]", "[locale]", "[id].astro"), navFragmentTemplate()) : Promise.resolve(false);
3806
+ var clientFeaturesFor = async (project) => clientFeaturesFrom(project, await buildRawMarkdown(project));
3565
3807
  var generateRuntime = async (project) => {
3566
3808
  const { context, config } = project;
3567
3809
  assertFontFilesExist(project);
3568
3810
  const out = context.outDir;
3569
- const srcDir = join10(out, "src");
3570
- const generatedDir = join10(srcDir, "generated");
3571
- const askPath = join10(srcDir, "generated", "Ask.astro");
3572
- const themePath = join10(srcDir, "generated", "app.css");
3573
- const searchClientPath = join10(srcDir, "generated", "search-client.ts");
3574
- const examplesPath = join10(srcDir, "generated", "examples.ts");
3575
- const examplesThemePath = join10(srcDir, "generated", "examples.css");
3811
+ const srcDir = join11(out, "src");
3812
+ const generatedDir = join11(srcDir, "generated");
3813
+ const askPath = join11(srcDir, "generated", "Ask.astro");
3814
+ const themePath = join11(srcDir, "generated", "app.css");
3815
+ const searchClientPath = join11(srcDir, "generated", "search-client.ts");
3816
+ const featuresPath = join11(srcDir, "generated", "features.ts");
3817
+ const examplesPath = join11(srcDir, "generated", "examples.ts");
3818
+ const examplesThemePath = join11(srcDir, "generated", "examples.css");
3576
3819
  const written = new Set;
3577
3820
  const write = (path, content) => {
3578
3821
  written.add(normalize(path));
@@ -3585,6 +3828,10 @@ var generateRuntime = async (project) => {
3585
3828
  const askEnabled = config.ai.ask?.enabled ?? false;
3586
3829
  const exportPdf = config.export.pdf;
3587
3830
  const exportEpub = config.export.epub;
3831
+ const rawMarkdown = await buildRawMarkdown(project);
3832
+ const clientFeatures = clientFeaturesFrom(project, rawMarkdown);
3833
+ const navFragments = navVariants(project.graph).some(({ navigation }) => hasDeferrableGroups(navigation.sidebar));
3834
+ const languageIcons = languageIconCssFrom(project, rawMarkdown);
3588
3835
  const staged = collectStaged(project);
3589
3836
  const [
3590
3837
  pages,
@@ -3643,7 +3890,7 @@ var generateRuntime = async (project) => {
3643
3890
  const docsCollection = resolveDocsCollection(config, context);
3644
3891
  const [structural] = await Promise.all([
3645
3892
  Promise.all([
3646
- write(join10(out, "astro.config.mjs"), astroConfigTemplate({
3893
+ write(join11(out, "astro.config.mjs"), astroConfigTemplate({
3647
3894
  aliases: resolveTsconfigAliases(context.root),
3648
3895
  askPath,
3649
3896
  config,
@@ -3652,6 +3899,8 @@ var generateRuntime = async (project) => {
3652
3899
  context,
3653
3900
  examplesPath,
3654
3901
  examplesThemePath,
3902
+ features: clientFeatures,
3903
+ featuresPath,
3655
3904
  integrationBridge,
3656
3905
  needsReact,
3657
3906
  needsSvelte,
@@ -3661,25 +3910,27 @@ var generateRuntime = async (project) => {
3661
3910
  searchClientPath,
3662
3911
  themePath
3663
3912
  })),
3664
- write(join10(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
3665
- write(join10(out, "tsconfig.json"), runtimeTsconfigTemplate()),
3666
- write(join10(srcDir, "content.config.ts"), contentConfigTemplate({
3913
+ write(join11(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
3914
+ write(join11(out, "tsconfig.json"), runtimeTsconfigTemplate()),
3915
+ write(join11(srcDir, "content.config.ts"), contentConfigTemplate({
3667
3916
  collection: docsCollection,
3668
3917
  config,
3669
3918
  context,
3670
3919
  filesystem: hasFilesystemSource,
3671
3920
  staged: hasStaged
3672
3921
  })),
3673
- write(join10(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
3922
+ write(join11(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
3674
3923
  exportEpub,
3675
3924
  exportPdf,
3676
3925
  mathEnabled: usesMath,
3926
+ navFragments,
3677
3927
  needsReact
3678
3928
  })),
3929
+ writeNavFragments(write, srcDir, navFragments),
3679
3930
  write(askPath, askComponentTemplate(askEnabled)),
3680
- write(join10(srcDir, "generated", "components.ts"), slotPlan.module),
3681
- write(join10(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
3682
- write(join10(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
3931
+ write(join11(srcDir, "generated", "components.ts"), slotPlan.module),
3932
+ write(join11(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
3933
+ write(join11(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
3683
3934
  write(examplesThemePath, examplesEntryTemplate({
3684
3935
  configTokens: buildThemeCss(config.theme),
3685
3936
  sources: exampleScanRoots(context.root, exampleDiscovery.dir).map((dir) => `${dir}/${EXAMPLE_SCAN_GLOB}`),
@@ -3687,6 +3938,7 @@ var generateRuntime = async (project) => {
3687
3938
  })),
3688
3939
  write(themePath, tailwindEntryTemplate({
3689
3940
  configTokens: `${buildThemeCss(config.theme)}${buildFontsCss(config.theme.fonts)}`,
3941
+ languageIcons,
3690
3942
  sources: [
3691
3943
  `${BLUME_SRC}/**/*.{astro,ts,tsx}`,
3692
3944
  `${context.root}/**/*.{astro,mdx,ts,tsx}`
@@ -3695,22 +3947,26 @@ var generateRuntime = async (project) => {
3695
3947
  userTheme
3696
3948
  }))
3697
3949
  ]),
3698
- Promise.all(islandDiscovery.islands.map((island) => write(join10(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
3699
- Promise.all(slotPlan.wrappers.map((wrapper) => write(join10(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
3700
- Promise.all(exampleDiscovery.examples.map((example) => write(join10(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
3950
+ Promise.all(islandDiscovery.islands.map((island) => write(join11(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
3951
+ Promise.all(slotPlan.wrappers.map((wrapper) => write(join11(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
3952
+ Promise.all(exampleDiscovery.examples.map((example) => write(join11(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
3701
3953
  writeAskFiles(project, srcDir, write, modules),
3702
3954
  writeMcpFiles(mcp, write, agentData),
3703
3955
  writeApiFiles(project, api, write, agentData, mcp),
3704
3956
  playgroundProxy.enabled ? write(playgroundProxy.entrypoint, playgroundProxyTemplate(proxyOrigins)) : Promise.resolve(false)
3705
3957
  ]);
3706
3958
  if (config.seo.og.enabled) {
3707
- await write(join10(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes, {
3959
+ await write(join11(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes, {
3708
3960
  ...projectOgFonts(project),
3961
+ cache: {
3962
+ dir: ogCacheDir(project.context),
3963
+ version: getBlumeVersion()
3964
+ },
3709
3965
  pageDescriptions: config.seo.og.description !== false
3710
3966
  }, changelogIndex));
3711
3967
  }
3712
3968
  if (changelogIndex) {
3713
- await write(join10(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
3969
+ await write(join11(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
3714
3970
  exportEpub,
3715
3971
  exportPdf,
3716
3972
  needsReact,
@@ -3726,32 +3982,32 @@ var generateRuntime = async (project) => {
3726
3982
  write
3727
3983
  }),
3728
3984
  writeNotFoundPage(write, srcDir, pages, project.graph.pages),
3729
- write(searchClientPath, searchClientTemplate(config))
3985
+ write(searchClientPath, searchClientTemplate(config)),
3986
+ write(featuresPath, featuresTemplate(clientFeatures))
3730
3987
  ]);
3731
3988
  if (servesStaticIndex(config.search.provider)) {
3732
3989
  const documents = await buildSearchDocuments(project);
3733
3990
  modules.set("blume:search-index", JSON.stringify(documents));
3734
- await write(join10(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
3991
+ await write(join11(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
3735
3992
  }
3736
3993
  if (config.search.provider === "mixedbread") {
3737
- await write(join10(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
3994
+ await write(join11(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
3738
3995
  }
3739
- await write(join10(srcDir, "generated", "includes.json"), `${JSON.stringify(buildIncludeGraph(project.graph.pages))}
3996
+ await write(join11(srcDir, "generated", "includes.json"), `${JSON.stringify(buildIncludeGraph(project.graph.pages))}
3740
3997
  `);
3741
- const rawMarkdown = await buildRawMarkdown(project);
3742
3998
  modules.set("blume:raw-markdown", JSON.stringify(rawMarkdown));
3743
3999
  const contentAssets = await collectContentAssets(project);
3744
4000
  modules.set("blume:content-assets", JSON.stringify(contentAssets));
3745
4001
  await Promise.all([
3746
- write(join10(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
3747
- write(join10(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx")),
3748
- write(join10(srcDir, "pages", "blume-assets", "[...asset].ts"), contentAssetsEndpointTemplate(join10(project.context.outDir, "public", "blume-assets")))
4002
+ write(join11(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
4003
+ write(join11(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx")),
4004
+ write(join11(srcDir, "pages", "blume-assets", "[...asset].ts"), contentAssetsEndpointTemplate(join11(project.context.outDir, "public", "blume-assets")))
3749
4005
  ]);
3750
4006
  const feeds = buildRssFeeds(project);
3751
4007
  if (feeds.length > 0) {
3752
4008
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
3753
4009
  modules.set("blume:rss", JSON.stringify(feedXml));
3754
- await write(join10(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate());
4010
+ await write(join11(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate());
3755
4011
  }
3756
4012
  const warnings = [
3757
4013
  ...depsLinkWarning ? [depsLinkWarning] : [],
@@ -3787,12 +4043,12 @@ var generateRuntime = async (project) => {
3787
4043
  root: context.root
3788
4044
  });
3789
4045
  warnings.push(...references.warnings);
3790
- await Promise.all(references.files.map((file) => write(join10(srcDir, "pages", file.pagePath), file.content)));
4046
+ await Promise.all(references.files.map((file) => write(join11(srcDir, "pages", file.pagePath), file.content)));
3791
4047
  }
3792
4048
  modules.set("blume:data", buildRuntimeData(project));
3793
4049
  modules.set("blume:openapi", JSON.stringify(openApiData));
3794
4050
  await Promise.all([
3795
- write(join10(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
4051
+ write(join11(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
3796
4052
  `),
3797
4053
  writeStagedContent(out, staged)
3798
4054
  ]);
@@ -3802,7 +4058,7 @@ var generateRuntime = async (project) => {
3802
4058
  return { structuralChange: structural.some(Boolean), warnings };
3803
4059
  };
3804
4060
 
3805
- export { SIGNATURES_DIRECTORY_PATH, SIGNATURES_DIRECTORY_TYPE, publishBuildProject, refreshBlumeContent, showBlumeErrorOverlay, buildAskData, buildMcpDiscovery, buildMcpServerCard, buildThemeCss, buildReferenceFiles, tailwindEntryTemplate, examplesEntryTemplate, rebaseSourceDirectives, twoslashCss, planComponentSlots, discoverIslands, EXAMPLE_SCAN_GLOB, exampleScanRoots, discoverExamples, exampleMarkdownLookup, detectNeedsReact, detectUsesMath, collectStaged, buildRuntimeData, generateRuntime };
4061
+ export { SIGNATURES_DIRECTORY_PATH, SIGNATURES_DIRECTORY_TYPE, publishBuildProject, refreshBlumeContent, showBlumeErrorOverlay, ogCacheDir, pruneCardCache, cardCacheTally, buildAskData, buildMcpDiscovery, buildMcpServerCard, buildThemeCss, buildReferenceFiles, tailwindEntryTemplate, examplesEntryTemplate, rebaseSourceDirectives, twoslashCss, planComponentSlots, discoverIslands, EXAMPLE_SCAN_GLOB, exampleScanRoots, discoverExamples, exampleMarkdownLookup, detectNeedsReact, detectUsesMath, collectStaged, buildRuntimeData, languageIconCssFor, clientFeaturesFor, generateRuntime };
3806
4062
 
3807
- //# debugId=39608A54607A739D64756E2164756E21
3808
- //# sourceMappingURL=chunk-62qsssnh.js.map
4063
+ //# debugId=E13637FE8F0CF5E964756E2164756E21
4064
+ //# sourceMappingURL=chunk-5d4q7121.js.map