@heroiclands/package-build 23.12.8 → 24.0.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 (258) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +3 -2
  3. package/assets/preview/gitea.ini +12 -7
  4. package/assets/styles/content-base.css +293 -24
  5. package/bin/content-commands.mjs +370 -213
  6. package/bin/package-build-preview.mjs +2 -2
  7. package/bin/package-build.mjs +3 -2
  8. package/ci/check-banners.mjs +7 -7
  9. package/config.mjs +21 -6
  10. package/content-config.mjs +199 -80
  11. package/docs/api.md +110 -89
  12. package/docs/authoring/links-and-markup.md +469 -97
  13. package/docs/commands.md +259 -78
  14. package/docs/configuration.md +211 -103
  15. package/docs/content-format.md +4 -4
  16. package/docs/diagnostics.md +44 -2
  17. package/docs/getting-started.md +1 -1
  18. package/docs/guides/icons.md +1 -1
  19. package/docs/guides/module-project.md +5 -8
  20. package/docs/guides/release-and-deploy.md +2 -2
  21. package/docs/guides/site.md +112 -23
  22. package/docs/guides/system-project.md +1 -1
  23. package/docs/guides/writing-in-invented-scripts.md +1247 -0
  24. package/docs/index.md +1 -0
  25. package/docs/project-setup.md +28 -30
  26. package/docs/reference/format-details.md +677 -319
  27. package/docs/reference/note-types.md +47 -10
  28. package/docs/reference/theme-pages.md +178 -108
  29. package/docs/source-preview.md +16 -2
  30. package/engine/anchors.mjs +2 -2
  31. package/engine/asset-shipping.mjs +22 -10
  32. package/engine/base-compiler.mjs +62 -104
  33. package/engine/changelog-lint.mjs +1 -1
  34. package/engine/code-fences.mjs +3 -3
  35. package/engine/content-address.mjs +1 -1
  36. package/engine/content-alerts.mjs +8 -4
  37. package/engine/content-blocks.mjs +29 -14
  38. package/engine/content-embeds.mjs +1 -1
  39. package/engine/content-expand.mjs +376 -0
  40. package/engine/content-figures.mjs +18 -11
  41. package/engine/content-format.yaml +8 -0
  42. package/engine/content-glyphs.mjs +213 -0
  43. package/engine/content-images.mjs +6 -7
  44. package/engine/content-index.mjs +1 -1
  45. package/engine/content-links.mjs +3 -3
  46. package/engine/content-lint.mjs +1 -1
  47. package/engine/content-render.mjs +453 -0
  48. package/engine/content-scripts.mjs +376 -43
  49. package/engine/content-spans.mjs +6 -3
  50. package/engine/content-styles.mjs +91 -0
  51. package/engine/content-symbols.mjs +186 -0
  52. package/engine/content-verse.mjs +738 -0
  53. package/engine/core-report.mjs +169 -0
  54. package/engine/core-vocabulary.mjs +208 -0
  55. package/engine/dictionary-html.mjs +119 -0
  56. package/engine/dictionary-rows.mjs +15 -4
  57. package/engine/field-reference.mjs +5 -5
  58. package/engine/first-use.mjs +17 -27
  59. package/engine/frontmatter-lint.mjs +1 -1
  60. package/engine/heading-attributes.mjs +89 -0
  61. package/engine/helpers.mjs +61 -101
  62. package/engine/homepage.mjs +17 -29
  63. package/engine/html-minify.mjs +70 -0
  64. package/engine/index.mjs +8 -5
  65. package/engine/key-case-collisions.mjs +136 -0
  66. package/engine/language-data.mjs +203 -6
  67. package/engine/language-rows.mjs +8 -2
  68. package/engine/language-terms.mjs +186 -16
  69. package/engine/language-words.mjs +12 -0
  70. package/engine/logographic.mjs +255 -0
  71. package/engine/map-lexicon.mjs +262 -47
  72. package/engine/map-places.mjs +5 -4
  73. package/engine/markdown-expressions.mjs +0 -2
  74. package/engine/markdownlint-run.mjs +9 -3
  75. package/engine/markup-validation.mjs +21 -0
  76. package/engine/metre-declaration.mjs +134 -0
  77. package/engine/names.mjs +36 -9
  78. package/engine/note-matter.mjs +66 -0
  79. package/engine/note-vocabulary.mjs +187 -3
  80. package/engine/pdf-build.mjs +69 -133
  81. package/engine/pdf-render.mjs +205 -58
  82. package/engine/post-fields.mjs +157 -0
  83. package/engine/preview-index.mjs +130 -0
  84. package/engine/preview-source.mjs +450 -0
  85. package/engine/project-init.mjs +5 -12
  86. package/engine/prose-config.mjs +3 -3
  87. package/engine/read-stdin.mjs +29 -0
  88. package/engine/run-rows.mjs +2 -2
  89. package/engine/run-words.mjs +6 -112
  90. package/engine/script-emphasis-terms.mjs +38 -0
  91. package/engine/script-emphasis.mjs +295 -0
  92. package/engine/script-font-delivery.mjs +4 -4
  93. package/engine/site-build.mjs +260 -315
  94. package/engine/site-config.mjs +26 -238
  95. package/engine/site-files.mjs +175 -0
  96. package/engine/site-index.mjs +9 -4
  97. package/engine/site-preview.mjs +52 -525
  98. package/engine/site-render.mjs +593 -0
  99. package/engine/site-root.mjs +22 -13
  100. package/engine/site-search.mjs +161 -0
  101. package/engine/site-serve.mjs +273 -0
  102. package/engine/site-theme-banner.mjs +154 -0
  103. package/engine/site-theme-urls.mjs +125 -0
  104. package/engine/site-theme.mjs +926 -0
  105. package/engine/sql-tables.mjs +20 -3
  106. package/engine/text-maps.mjs +57 -10
  107. package/engine/typographer.mjs +248 -0
  108. package/engine/verse-metre.mjs +357 -0
  109. package/engine/wikilink-syntax.mjs +29 -47
  110. package/engine/wikilinks.mjs +758 -279
  111. package/engine/word-forms.mjs +364 -0
  112. package/engine/word-rows.mjs +12 -2
  113. package/engine/word-terms.mjs +38 -4
  114. package/manifest.mjs +3 -4
  115. package/package.json +11 -8
  116. package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
  117. package/site-theme/layouts/base.hbs +31 -0
  118. package/site-theme/layouts/blog-list.hbs +30 -0
  119. package/site-theme/layouts/blog-post.hbs +40 -0
  120. package/site-theme/layouts/home.hbs +35 -0
  121. package/site-theme/layouts/list.hbs +20 -0
  122. package/site-theme/layouts/not-found.hbs +31 -0
  123. package/site-theme/layouts/page.hbs +7 -0
  124. package/site-theme/partials/copy-buttons.hbs +37 -0
  125. package/site-theme/partials/draft-notice.hbs +5 -0
  126. package/site-theme/partials/exturl.hbs +2 -0
  127. package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
  128. package/site-theme/partials/header.hbs +44 -0
  129. package/site-theme/partials/hero-banner.hbs +11 -0
  130. package/site-theme/partials/infobox-entry.hbs +2 -0
  131. package/site-theme/partials/infobox-section.hbs +31 -0
  132. package/site-theme/partials/infobox-value.hbs +2 -0
  133. package/site-theme/partials/infobox.hbs +18 -0
  134. package/site-theme/partials/list-members.hbs +9 -0
  135. package/site-theme/partials/nav-links.hbs +5 -0
  136. package/site-theme/partials/page-body.hbs +48 -0
  137. package/site-theme/partials/page-list.hbs +14 -0
  138. package/site-theme/partials/related.hbs +24 -0
  139. package/site-theme/partials/search-meta.hbs +6 -0
  140. package/site-theme/partials/search-names.hbs +2 -0
  141. package/site-theme/partials/search.hbs +52 -0
  142. package/site-theme/partials/tags.hbs +4 -0
  143. package/site-theme/partials/toc.hbs +7 -0
  144. package/{hugo-theme → site-theme}/static/css/style.css +106 -14
  145. package/stage.mjs +1 -2
  146. package/types/config.d.mts +8 -4
  147. package/types/content-config.d.mts +15 -15
  148. package/types/engine/asset-shipping.d.mts +5 -2
  149. package/types/engine/base-compiler.d.mts +20 -2
  150. package/types/engine/code-fences.d.mts +3 -3
  151. package/types/engine/content-address.d.mts +1 -1
  152. package/types/engine/content-alerts.d.mts +8 -2
  153. package/types/engine/content-blocks.d.mts +11 -4
  154. package/types/engine/content-expand.d.mts +288 -0
  155. package/types/engine/content-figures.d.mts +11 -3
  156. package/types/engine/content-glyphs.d.mts +85 -0
  157. package/types/engine/content-images.d.mts +6 -7
  158. package/types/engine/content-links.d.mts +1 -1
  159. package/types/engine/content-render.d.mts +199 -0
  160. package/types/engine/content-scripts.d.mts +70 -4
  161. package/types/engine/content-spans.d.mts +1 -1
  162. package/types/engine/content-styles.d.mts +33 -0
  163. package/types/engine/content-symbols.d.mts +35 -0
  164. package/types/engine/content-verse.d.mts +240 -0
  165. package/types/engine/core-report.d.mts +45 -0
  166. package/types/engine/core-vocabulary.d.mts +53 -0
  167. package/types/engine/dictionary-html.d.mts +8 -0
  168. package/types/engine/dictionary-rows.d.mts +10 -0
  169. package/types/engine/field-reference.d.mts +2 -2
  170. package/types/engine/heading-attributes.d.mts +20 -0
  171. package/types/engine/helpers.d.mts +3 -10
  172. package/types/engine/homepage.d.mts +11 -24
  173. package/types/engine/html-minify.d.mts +9 -0
  174. package/types/engine/index.d.mts +2 -1
  175. package/types/engine/key-case-collisions.d.mts +28 -0
  176. package/types/engine/language-data.d.mts +0 -2
  177. package/types/engine/language-terms.d.mts +24 -2
  178. package/types/engine/logographic.d.mts +45 -0
  179. package/types/engine/map-lexicon.d.mts +66 -13
  180. package/types/engine/map-places.d.mts +8 -0
  181. package/types/engine/markdown-expressions.d.mts +0 -2
  182. package/types/engine/metre-declaration.d.mts +39 -0
  183. package/types/engine/names.d.mts +34 -3
  184. package/types/engine/note-matter.d.mts +48 -0
  185. package/types/engine/pdf-render.d.mts +6 -2
  186. package/types/engine/post-fields.d.mts +44 -0
  187. package/types/engine/preview-index.d.mts +72 -0
  188. package/types/engine/preview-source.d.mts +51 -0
  189. package/types/engine/read-stdin.d.mts +11 -0
  190. package/types/engine/run-rows.d.mts +2 -2
  191. package/types/engine/run-words.d.mts +0 -15
  192. package/types/engine/script-emphasis-terms.d.mts +21 -0
  193. package/types/engine/script-emphasis.d.mts +84 -0
  194. package/types/engine/script-font-delivery.d.mts +4 -4
  195. package/types/engine/site-build.d.mts +71 -113
  196. package/types/engine/site-config.d.mts +14 -134
  197. package/types/engine/site-files.d.mts +73 -0
  198. package/types/engine/site-index.d.mts +11 -3
  199. package/types/engine/site-preview.d.mts +15 -19
  200. package/types/engine/site-render.d.mts +171 -0
  201. package/types/engine/site-root.d.mts +12 -4
  202. package/types/engine/site-search.d.mts +54 -0
  203. package/types/engine/site-serve.d.mts +44 -0
  204. package/types/engine/site-theme-banner.d.mts +51 -0
  205. package/types/engine/site-theme-urls.d.mts +60 -0
  206. package/types/engine/site-theme.d.mts +127 -0
  207. package/types/engine/sql-tables.d.mts +4 -1
  208. package/types/engine/text-maps.d.mts +17 -3
  209. package/types/engine/typographer.d.mts +11 -0
  210. package/types/engine/verse-metre.d.mts +131 -0
  211. package/types/engine/wikilink-syntax.d.mts +30 -48
  212. package/types/engine/wikilinks.d.mts +330 -14
  213. package/types/engine/word-forms.d.mts +83 -0
  214. package/types/manifest.d.mts +3 -4
  215. package/types/stage.d.mts +1 -2
  216. package/types/vendor/markdownlint.d.mts +8 -0
  217. package/vendor/markdownlint.mjs +20388 -0
  218. package/engine/content-poetry.mjs +0 -240
  219. package/engine/web-wikilinks.mjs +0 -594
  220. package/hugo-theme/layouts/404.html +0 -85
  221. package/hugo-theme/layouts/_default/baseof.html +0 -67
  222. package/hugo-theme/layouts/_default/list.html +0 -139
  223. package/hugo-theme/layouts/_default/single.html +0 -4
  224. package/hugo-theme/layouts/blog/list.html +0 -54
  225. package/hugo-theme/layouts/blog-post/single.html +0 -36
  226. package/hugo-theme/layouts/index.html +0 -75
  227. package/hugo-theme/layouts/partials/banner-url.html +0 -141
  228. package/hugo-theme/layouts/partials/cdn-url.html +0 -36
  229. package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
  230. package/hugo-theme/layouts/partials/draft-notice.html +0 -41
  231. package/hugo-theme/layouts/partials/draft-state.html +0 -26
  232. package/hugo-theme/layouts/partials/header.html +0 -35
  233. package/hugo-theme/layouts/partials/hero-banner.html +0 -85
  234. package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
  235. package/hugo-theme/layouts/partials/infobox/items.html +0 -26
  236. package/hugo-theme/layouts/partials/infobox/section.html +0 -56
  237. package/hugo-theme/layouts/partials/infobox/value.html +0 -43
  238. package/hugo-theme/layouts/partials/infobox.html +0 -57
  239. package/hugo-theme/layouts/partials/page-body.html +0 -152
  240. package/hugo-theme/layouts/partials/related.html +0 -138
  241. package/hugo-theme/layouts/partials/search.html +0 -84
  242. package/hugo-theme/layouts/partials/site-url.html +0 -32
  243. package/hugo-theme/layouts/partials/tags.html +0 -30
  244. package/hugo-theme/layouts/partials/toc.html +0 -28
  245. package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
  246. package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
  247. package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
  248. package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
  249. package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
  250. package/hugo-theme/theme.toml +0 -11
  251. package/types/engine/content-poetry.d.mts +0 -58
  252. package/types/engine/web-wikilinks.d.mts +0 -107
  253. /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
  254. /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
  255. /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
  256. /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
  257. /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
  258. /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
@@ -0,0 +1,213 @@
1
+ /*
2
+ * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
+ *
5
+ * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
+ * You may copy, modify, and distribute it under the terms of that license.
7
+ *
8
+ * For full terms, see the LICENSE.md file in the project root or visit:
9
+ * https://www.gnu.org/licenses/gpl-3.0.html
10
+ *
11
+ * SPDX-License-Identifier: GPL-3.0-or-later
12
+ */
13
+
14
+ /**
15
+ * The vocabulary of the two picture constructs a note writes for a sign.
16
+ *
17
+ * Both are spans, set in a line of text:
18
+ *
19
+ * ```
20
+ * The name [Ra]{.glyph slug=ra} is written with one sign, and
21
+ * [Au'Khelathu]{.cartouche slug=aukhelathu height=10rem align=right caption="Au'Khelathu"}
22
+ * is sealed in a frame.
23
+ * ```
24
+ *
25
+ * A span's label is its alt text; with an empty label the humanized slug
26
+ * stands in. A cartouche's caption is an attribute.
27
+ *
28
+ * The picture is a file the package ships, addressed by the one pathname rule
29
+ * every picture follows, so each target serves the address it can: `icons/<slug>.svg`
30
+ * for a glyph and `images/cartouches/<slug>.svg` for a cartouche. Both
31
+ * closed vocabularies below are refused when departed from, because an
32
+ * attribute that cannot mean anything must not read as one that worked.
33
+ *
34
+ * @module
35
+ */
36
+
37
+ /**
38
+ * Text safe inside a double-quoted attribute or an element.
39
+ * @param {string} value - Authored text.
40
+ * @returns {string} The text with markup characters escaped.
41
+ */
42
+ function escapeHtml(value) {
43
+ return String(value)
44
+ .replace(/&/g, "&amp;")
45
+ .replace(/</g, "&lt;")
46
+ .replace(/>/g, "&gt;")
47
+ .replace(/"/g, "&quot;");
48
+ }
49
+
50
+ /** The frames a glyph may sit in. */
51
+ export const GLYPH_FRAMES = Object.freeze(["tablet"]);
52
+
53
+ /** The sizes a glyph may take. */
54
+ export const GLYPH_SIZES = Object.freeze(["inline", "block"]);
55
+
56
+ /** The sides a cartouche may float to; absent, it is centred. */
57
+ export const CARTOUCHE_ALIGNS = Object.freeze(["left", "right"]);
58
+
59
+ /** The attribute keys a glyph takes. */
60
+ export const GLYPH_KEYS = Object.freeze(["slug", "frame", "size"]);
61
+ /** The attribute keys a cartouche takes. */
62
+ export const CARTOUCHE_KEYS = Object.freeze(["slug", "align", "height", "caption"]);
63
+
64
+ /**
65
+ * The units a cartouche height may be written in: every one a printed page
66
+ * can honour as well as a screen.
67
+ */
68
+ export const HEIGHT_UNITS = Object.freeze(["rem", "em", "px", "pt", "cm", "mm", "in"]);
69
+
70
+ const SLUG = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
71
+ const HEIGHT = new RegExp(`^(\\d+(?:\\.\\d+)?)(${HEIGHT_UNITS.join("|")})$`);
72
+
73
+ /**
74
+ * The pathname of a glyph's picture.
75
+ * @param {string} slug - The glyph's slug.
76
+ * @returns {string} The authored pathname.
77
+ */
78
+ export function glyphPathname(slug) {
79
+ return `icons/${slug}.svg`;
80
+ }
81
+
82
+ /**
83
+ * The pathname of a cartouche's picture.
84
+ * @param {string} slug - The cartouche's slug.
85
+ * @returns {string} The authored pathname.
86
+ */
87
+ export function cartouchePathname(slug) {
88
+ return `images/cartouches/${slug}.svg`;
89
+ }
90
+
91
+ /**
92
+ * A slug as words, for the alt text of a sign whose author wrote none.
93
+ * @param {string} slug - The slug.
94
+ * @returns {string} Title-cased words.
95
+ */
96
+ export function humanizeSlug(slug) {
97
+ return String(slug)
98
+ .split(/[-_]+/)
99
+ .filter(Boolean)
100
+ .map((word) => word[0].toUpperCase() + word.slice(1))
101
+ .join(" ");
102
+ }
103
+
104
+ /**
105
+ * What is wrong with an attribute list common to both constructs.
106
+ * @param {{id: string, classes: string[], values: Record<string, string>}} parsed - The parsed attributes.
107
+ * @param {string} noun - `glyph` or `cartouche`.
108
+ * @param {readonly string[]} keys - The keys the construct takes.
109
+ * @returns {string[]} One message per problem.
110
+ */
111
+ function sharedProblems({ id, classes, values }, noun, keys) {
112
+ const problems = [];
113
+ if (id) problems.push(`a ${noun} takes no #id`);
114
+ for (const name of classes.filter((name) => name !== noun))
115
+ problems.push(`a ${noun} takes no class besides .${noun}; .${name} is not one`);
116
+ for (const key of Object.keys(values))
117
+ if (!keys.includes(key) && !/^on/i.test(key) && !["id", "class"].includes(key))
118
+ problems.push(`a ${noun} takes ${keys.join(", ")}; \`${key}\` is not one of them`);
119
+ if (!Object.hasOwn(values, "slug"))
120
+ problems.push(`a ${noun} needs slug="…" naming its picture`);
121
+ else if (!SLUG.test(values.slug))
122
+ problems.push(`slug \`${values.slug}\` must be letters, digits, \`-\` and \`_\``);
123
+ return problems;
124
+ }
125
+
126
+ /**
127
+ * What is wrong with a glyph's attributes.
128
+ * @param {{id: string, classes: string[], values: Record<string, string>}} parsed - The parsed attributes.
129
+ * @returns {string[]} One message per problem.
130
+ */
131
+ export function glyphProblems(parsed) {
132
+ const problems = sharedProblems(parsed, "glyph", GLYPH_KEYS);
133
+ const { values } = parsed;
134
+ if (Object.hasOwn(values, "alt"))
135
+ problems.splice(
136
+ problems.findIndex((message) => message.includes("`alt`")),
137
+ 1,
138
+ "write a glyph's alt text as its label: [alt text]{.glyph slug=…}",
139
+ );
140
+ if (Object.hasOwn(values, "frame") && !GLYPH_FRAMES.includes(values.frame))
141
+ problems.push(`frame \`${values.frame}\` is not one of ${GLYPH_FRAMES.join(", ")}`);
142
+ if (Object.hasOwn(values, "size") && !GLYPH_SIZES.includes(values.size))
143
+ problems.push(`size \`${values.size}\` is not one of ${GLYPH_SIZES.join(", ")}`);
144
+ return problems;
145
+ }
146
+
147
+ /**
148
+ * What is wrong with a cartouche's attributes.
149
+ * @param {{id: string, classes: string[], values: Record<string, string>}} parsed - The parsed attributes.
150
+ * @returns {string[]} One message per problem.
151
+ */
152
+ export function cartoucheProblems(parsed) {
153
+ const { values } = parsed;
154
+ const problems = sharedProblems(parsed, "cartouche", CARTOUCHE_KEYS);
155
+ if (Object.hasOwn(values, "alt"))
156
+ problems.splice(
157
+ problems.findIndex((message) => message.includes("`alt`")),
158
+ 1,
159
+ "write a cartouche's alt text as its label: [alt text]{.cartouche slug=…}",
160
+ );
161
+ if (Object.hasOwn(values, "align") && !CARTOUCHE_ALIGNS.includes(values.align))
162
+ problems.push(`align \`${values.align}\` is not one of ${CARTOUCHE_ALIGNS.join(", ")}`);
163
+ if (Object.hasOwn(values, "height") && !HEIGHT.test(values.height))
164
+ problems.push(
165
+ `height \`${values.height}\` must be a number in ${HEIGHT_UNITS.join(", ")}, such as 10rem`,
166
+ );
167
+ return problems;
168
+ }
169
+
170
+ /**
171
+ * A cartouche's height as Typst writes a length.
172
+ * @param {string} height - A validated CSS length.
173
+ * @returns {string} The same length in a unit Typst reads.
174
+ */
175
+ export function typstLength(height) {
176
+ const [, number, unit] = HEIGHT.exec(height);
177
+ if (unit === "rem") return `${number}em`;
178
+ if (unit === "px") return `${Number(number) * 0.75}pt`;
179
+ return `${number}${unit}`;
180
+ }
181
+
182
+ /**
183
+ * A glyph as HTML.
184
+ * @param {{slug: string, alt: string, frame?: string, size?: string}} glyph - Its attributes.
185
+ * @param {string} src - The address the target serves its picture at.
186
+ * @returns {string} A span holding an image.
187
+ */
188
+ export function glyphHtml({ slug, alt, frame, size }, src) {
189
+ const classes = [
190
+ "glyph",
191
+ ...(frame ? [`glyph-${frame}`] : []),
192
+ ...(size ? [`glyph-${size}`] : []),
193
+ ];
194
+ return `<span class="${classes.join(" ")}"><img src="${escapeHtml(src)}" alt="${escapeHtml(alt || humanizeSlug(slug))}" loading="lazy" /></span>`;
195
+ }
196
+
197
+ /**
198
+ * A cartouche as HTML.
199
+ * @param {{slug: string, alt?: string, align?: string, height?: string, caption?: string}} cartouche - Its attributes.
200
+ * @param {string} src - The address the target serves its picture at.
201
+ * @returns {string} A figure holding an image and an optional caption, on one
202
+ * line so that it sits inside a paragraph without ending it.
203
+ */
204
+ export function cartoucheHtml({ slug, alt, align, height, caption }, src) {
205
+ const style =
206
+ height ? ` style="height: ${escapeHtml(height)}; width: auto; max-width: none;"` : "";
207
+ return [
208
+ `<figure class="cartouche${align ? ` cartouche-${align}` : ""}">`,
209
+ `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt || humanizeSlug(slug))}" loading="lazy"${style} />`,
210
+ ...(caption ? [`<figcaption>${escapeHtml(caption)}</figcaption>`] : []),
211
+ "</figure>",
212
+ ].join("");
213
+ }
@@ -344,7 +344,7 @@ export function escapeHtml(text) {
344
344
  *
345
345
  * **The role class and the `width`/`height` attributes are what a stylesheet
346
346
  * elsewhere keys a role's size on.** This module states none — the measure a
347
- * `portrait` fills on a page is a Hugo theme's or a Foundry system's own
347
+ * `portrait` fills on a page is a site theme's or a Foundry system's own
348
348
  * decision — and emits only what the resolved asset record states: a role
349
349
  * class when the picture declares one, and the attributes whenever the
350
350
  * picture resolved to an asset record at all, blank for a vector. The
@@ -684,12 +684,11 @@ export function lintContentImages(contentBase, { skipDirectories = [], config, s
684
684
  /**
685
685
  * Rewrite every block image in a body into the figure the website publishes.
686
686
  *
687
- * **Hugo is handed markdown, not a rendered page.** The site emitter writes a
688
- * note's body through verbatim, so a `{…}` directive left in it reaches the
689
- * page as its own literal braces: Goldmark's block-attribute parser is off, and
690
- * turning it on would accept `style` and `id` alongside the closed vocabularies,
691
- * which is the injection surface this rule exists to avoid. So the directive is
692
- * resolved here, into markup Goldmark passes through.
687
+ * **The directive is resolved before the markdown is parsed.** A `{…}`
688
+ * directive left in the body would reach the page as its own literal braces: a
689
+ * block-attribute parser would accept `style` and `id` alongside the closed
690
+ * vocabularies, which is the injection surface this rule exists to avoid. So
691
+ * the directive is resolved here, into markup the renderer passes through.
693
692
  *
694
693
  * An image whose directive does not parse is left exactly as written, so the
695
694
  * page shows the braces and the lint says why — the same visible degradation an
@@ -1512,7 +1512,7 @@ function dynamicPublicationBody(body) {
1512
1512
  const source = String(body ?? "");
1513
1513
  return (
1514
1514
  [...matchAllOutsideCode(source, EXPRESSION)].length > 0 ||
1515
- codeRegions(source, { spans: false, poetryAsProse: false }).some(({ start, end }) => {
1515
+ codeRegions(source, { spans: false, verseAsProse: false }).some(({ start, end }) => {
1516
1516
  const opener = FENCE_LINE.exec(source.slice(start, end).split("\n", 1)[0]);
1517
1517
  const language = opener && parseHeaderArgs(opener[3]).language;
1518
1518
  return language === "sql" || language === PAGE_LIST_LANGUAGE;
@@ -89,7 +89,7 @@ import {
89
89
  readCanonicalKey,
90
90
  } from "./content-address.mjs";
91
91
  import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs";
92
- import { frontmatterWikilinks, slugify } from "./web-wikilinks.mjs";
92
+ import { frontmatterWikilinks, slugify } from "./wikilinks.mjs";
93
93
  import { homepageAddresses, isHomepage } from "./homepage.mjs";
94
94
  import { isEventAnchor, parseWikilink, WIKILINK } from "./wikilink-syntax.mjs";
95
95
  import { readQualifier } from "./wikilinks.mjs";
@@ -677,7 +677,7 @@ export function buildLinkIndex(
677
677
  linksOf,
678
678
  embedsOf,
679
679
  /**
680
- * Resolve a link target the way both builds do, or `undefined`. Every
680
+ * Resolve a link target the way every build does, or `undefined`. Every
681
681
  * link is an address, so this is {@link resolveAddress} under the name
682
682
  * the walkers use.
683
683
  */
@@ -955,7 +955,7 @@ export function auditHomepageLinks(index) {
955
955
  * `"unresolved"`, never `"ambiguous"`: every target here expands to one
956
956
  * canonical address before anything is looked up, so there is no candidate
957
957
  * set for two packages to claim — and every reason is an **error**: the
958
- * three resolvers agree on severity for every class.
958
+ * checker and the resolver agree on severity for every class.
959
959
  */
960
960
  export function auditLinks(index) {
961
961
  const { notes, anchors, linksOf, embedsOf, resolve, manifestHit, isAddress } = index;
@@ -43,7 +43,7 @@
43
43
  *
44
44
  * **There is deliberately no third rule** requiring every note to repeat its
45
45
  * own `type-shortcode` address in a top-level `aliases:` list. It would serve
46
- * no build reader: both resolvers parse the hyphen
46
+ * no build reader: the resolver and the checker parse the hyphen
47
47
  * qualifier themselves.
48
48
  *
49
49
  * **What is deliberately absent.** Corpus reachability — "every Rules document