@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
@@ -7,8 +7,9 @@ subType: reference
7
7
 
8
8
  # What the theme draws
9
9
 
10
- `package-build site` writes a page's front matter; the shared Hugo theme reads
11
- it and draws the page around it. This reference describes that second half —
10
+ `package-build site build` settles a page's front matter and body; the shared
11
+ site theme draws the page around them. This reference describes that second
12
+ half —
12
13
  the infobox rail, the hero band, the generated sections, the related card,
13
14
  search, and the markup an image or a figure renders as — and names the
14
15
  classes a consumer styles against.
@@ -30,7 +31,7 @@ A place’s Government row links to its authored governing affiliation. Explicit
30
31
  The toolchain settles what each box holds and in what order — see
31
32
  [the infobox](format-details.md#the-infobox) for the field semantics, the
32
33
  three states a system box can say, and the four section layouts a box is
33
- built from. On the website, `partials/infobox.html` draws the whole list by
34
+ built from. On the website, the `infobox` partial draws the whole list by
34
35
  switching on a section's `layout` and a value's `kind`, never on a note type
35
36
  or a field name:
36
37
 
@@ -121,13 +122,13 @@ A page whose front matter declares no `infoboxes:` renders no rail.
121
122
 
122
123
  ## The hero banner
123
124
 
124
- Nearly every page opens with a hero band, and `partials/hero-banner.html`
125
+ Nearly every page opens with a hero band, and the `hero-banner` partial
125
126
  decides what sits behind it. A page arrives with its hero image stated in one
126
127
  of two ways, and the difference is who resolved it: a content page carries
127
128
  `data.banner`, which the build resolved from the asset Address the note
128
129
  declared into the address its asset host serves; a hand-authored page carries
129
130
  a top-level `banner:`, which the theme resolves against `params.cdnBaseURL`
130
- (see [`params.cdnBaseURL`](../configuration.md#the-generated-hugo-configuration)).
131
+ (see [`params.cdnBaseURL`](../configuration.md#what-the-site-build-reads)).
131
132
 
132
133
  **Resolution order.**
133
134
 
@@ -143,13 +144,13 @@ a top-level `banner:`, which the theme resolves against `params.cdnBaseURL`
143
144
  4. `images/banners/default.webp` — when the page has no type, **and** whenever
144
145
  the name picked by 2 or 3 is not in the declared inventory.
145
146
 
146
- **Why step 4 is a declaration, not a test.** Hugo cannot ask a remote host
147
- whether a URL exists, so a banner that was never drawn would 404 in silence —
148
- the band renders with its title, palette and gradient intact behind a dead URL,
149
- and nothing fails: not the build, not Hugo, not the deploy guard.
150
- `data/banners.yaml` is the declaration that stands in for the test the
151
- template cannot make. A name listed in its `available` is asserted to exist; a
152
- resolved name that is absent falls back to `fallback`, and Hugo logs one
147
+ **Why step 4 is a declaration, not a test.** The build cannot ask a remote
148
+ host whether a URL exists, so a banner that was never drawn would 404 in
149
+ silence — the band renders with its title, palette and gradient intact behind
150
+ a dead URL, and nothing fails: not the build, not the deploy guard.
151
+ `banners.yaml` is the declaration that stands in for the test the template
152
+ cannot make. A name listed in its `available` is asserted to exist; a resolved
153
+ name that is absent falls back to `fallback`, and the build reports one
153
154
  deduplicated warning naming the missing banner and the first page that wanted
154
155
  it. The fallback applies only to a relative path landing directly in the
155
156
  declared `dir` — an absolute URL, and a fragment pointing anywhere else under
@@ -158,7 +159,7 @@ either and must not second-guess an address it cannot know about. A step 1
158
159
  address is not checked for the same reason: the question has already been
159
160
  answered upstream.
160
161
 
161
- **The kind mapping.** `data/banner-kinds.yaml` declares, once, which banner a
162
+ **The kind mapping.** `banner-kinds.yaml` declares, once, which banner a
162
163
  page of each note `type` opens with. A type maps to one banner name for all of
163
164
  its subTypes, or to a table that names a banner for every subType the note
164
165
  vocabulary declares for it:
@@ -178,20 +179,15 @@ kinds:
178
179
  warning. A type missing from the file resolves to its own name, so a kind with
179
180
  no declared art still produces the missing-banner warning. The lint check and
180
181
  the test suite fail on a mapped name that `banners.yaml` does not declare, and
181
- on a type or subType of the note vocabulary that the mapping omits. A consumer
182
- publishing its own artwork set ships its own `data/banner-kinds.yaml` beside
183
- its `data/banners.yaml`.
182
+ on a type or subType of the note vocabulary that the mapping omits.
184
183
 
185
- **One resolver.** The order above lives in `partials/banner-url.html`, which
186
- returns a URL or an empty string; `hero-banner.html` calls it with the page's
187
- `type`, `subType` and `category`, for step 3.
184
+ **One resolver.** The order above lives in one function of the engine, which
185
+ returns a URL or an empty string; the `hero-banner` partial is given the
186
+ result.
188
187
 
189
- **Keeping the inventory honest.** The lint check over `data/banners.yaml`
190
- fetches every declared name from the asset host and fails on any non-200
191
- response, so a name added optimistically is caught here rather than on a
192
- published page. A consumer publishing its own artwork set replaces the whole
193
- list by shipping its own `data/banners.yaml`, which Hugo reads in preference
194
- to the theme's bundled one, and can run the same check against it.
188
+ **Keeping the inventory honest.** The lint check over `banners.yaml` fetches
189
+ every declared name from the asset host and fails on any non-200 response, so
190
+ a name added optimistically is caught here rather than on a published page.
195
191
 
196
192
  **Declining a hero image.** `banner: none` on a hand-authored page, and a
197
193
  blank `data.banner` on a note (which the build emits for `data.banner: none`),
@@ -246,7 +242,7 @@ is the way to choose different contents for one, or to turn one off.
246
242
 
247
243
  ## The related card
248
244
 
249
- `partials/related.html` renders a bordered "Related" card below the body,
245
+ The `related` partial renders a bordered "Related" card below the body,
250
246
  listing the pages connected to this one. The build emits this front matter on
251
247
  every content page; what links to a page is a fact about every other page, so
252
248
  an authored `related:` is refused and dropped:
@@ -335,13 +331,88 @@ Caption classes are unrestricted. The built-in `border` class draws the block as
335
331
  | `.content-figure-label` | the label, number and caption together |
336
332
  | `.border` | the block as a boxed aside |
337
333
 
334
+ ## Verse
335
+
336
+ A verse fence reaches the page as a `.verse` block holding a paragraph per
337
+ stanza and a `.verse-line` per line, its indent level as `i1` to `i8`. Its
338
+ keys reach the block as written, `lang` as its language and the rest as
339
+ `data-*` attributes. A numbered verse also carries `.verse-numbered`, and each
340
+ numbered line starts with its `.verse-number`.
341
+
342
+ A [verse in columns](../authoring/links-and-markup.md#verse-in-columns) also
343
+ carries `.verse-columns`, and each verse line is one `.verse-row`:
344
+
345
+ ```html
346
+ <div class="verse verse-columns verse-numbered" lang="art-x-vyl" data-numstep="5">
347
+ <p>
348
+ <span class="verse-row"
349
+ ><span class="verse-number"></span
350
+ ><span class="verse-column"
351
+ ><span class="verse-part"
352
+ ><span class="script hl-semaron-kuriale" lang="art-x-vyl">…</span></span
353
+ ></span
354
+ ><span class="verse-column"
355
+ ><span class="verse-part"><span lang="en">The hallowed boundary I sing,</span></span
356
+ ><span class="verse-part verse-secondary"
357
+ ><span lang="art-x-vyl">Thiruthriman sorimamen,</span></span
358
+ ></span
359
+ ></span
360
+ >
361
+ …
362
+ </p>
363
+ </div>
364
+ ```
365
+
366
+ A numbered verse in columns gives every row a `.verse-number`, empty on the
367
+ lines it does not number, so the columns stay aligned. A part whose text runs
368
+ right to left carries `dir="rtl"`. Each row is its own grid, so a long line
369
+ keeps its partner level with it. Below a block width of `32rem` a row stacks
370
+ its columns in order; it is a container query, so a narrow journal window
371
+ stacks as well. The Foundry stylesheet draws the same.
372
+
373
+ | class | drawn as |
374
+ | ------------------ | ------------------------------------------------------------- |
375
+ | `.verse` | the poem |
376
+ | `.verse-line` | one line of a poem without columns |
377
+ | `.verse-numbered` | a numbered poem, with a margin for its numbers |
378
+ | `.verse-number` | a line's number, muted |
379
+ | `.verse-columns` | a poem in columns, and the container its rows are measured by |
380
+ | `.verse-row` | one verse line: its number and its columns |
381
+ | `.verse-column` | one column, its entries stacked |
382
+ | `.verse-part` | one entry's text, in the body serif unless its face sets it |
383
+ | `.verse-secondary` | every entry after a column's first, muted |
384
+
385
+ ## Emphasis in a script
386
+
387
+ Emphasis inside a run set in a face stays an `<em>` or a `<strong>`, and its
388
+ class names the convention its language or script states (see
389
+ [script notes and emphasis](format-details.md#script-notes-and-emphasis)):
390
+
391
+ | class | drawn as |
392
+ | ---------------- | ----------------------------- |
393
+ | `.emph-rubric` | the text in `var(--rubric)` |
394
+ | `.emph-overline` | the text with a line above it |
395
+
396
+ `.script em` and `.script strong` keep the face's Regular: no slant, no added
397
+ weight, and `font-synthesis: none`. A script whose notes state no convention
398
+ gives a bare `<em>` or `<strong>`, which therefore looks like the text around
399
+ it.
400
+
401
+ The theme's `--rubric` is `#e0705f`, a warm red for its dark ground. Because a
402
+ rubric carries emphasis by colour alone, it holds 4.5:1 against `--bg`,
403
+ `--bg-card` and `--surface`, and 1.75:1 against `--text`, so a reader who
404
+ cannot see its hue still sees a lighter or darker word. A site that overrides
405
+ `--rubric` keeps both. Foundry's base stylesheet sets the same classes from
406
+ `--pb-rubric`, `light-dark(#9c281b, #e0705f)`, against Foundry's journal page in
407
+ each theme, and the book prints its rubric in `#9c281b` on its paper.
408
+
338
409
  ## Search
339
410
 
340
411
  `params.search = true` renders a search box in the header; left unset (or
341
- `false`), `partials/search.html` renders nothing. The box, and everything it
412
+ `false`), the `search` partial renders nothing. The box, and everything it
342
413
  opens, is the theme's own — the index it reads is not.
343
414
 
344
- **Where the index comes from.** `package-build site-root` writes a
415
+ **Where the index comes from.** `package-build site build` writes a
345
416
  [Pagefind](https://pagefind.app) index at `pagefind/` beside the rendered
346
417
  pages, when `site.search` in `package-build.config.yaml` is left at its
347
418
  default of `true` (see [`site.search`](../configuration.md#site) and
@@ -352,26 +423,40 @@ Pagefind version. Turning `params.search` on without `site.search` indexing
352
423
  the site (or with it set `false`) shows an empty results box that never finds
353
424
  anything — the two settings are independent, and a consumer sets both.
354
425
 
355
- Both files are referenced the same way every other theme-bundled asset is —
356
- `relURL`, the same resolution `"css/style.css" | relURL` uses in
357
- `baseof.html` — so a package served under a prefix (`/thalorna/pagefind/…`)
358
- finds its own index rather than another package's, and the theme carries no
359
- address of its own.
426
+ Both files are addressed relative to the site's base, so a package served
427
+ under a prefix (`/thalorna/pagefind/…`) finds its own index rather than
428
+ another package's, and the theme carries no address of its own.
360
429
 
361
430
  **Why a header box and not a `/search/` page.** A site is its homepage and
362
431
  its pages, with nothing generated between them — a results page would need a
363
- consumer content file at that address, which the theme has no way to
364
- conjure. Pagefind's own panel UI needs no page of its own: the header button
365
- opens it in place.
366
-
367
- **What is indexed.** `data-pagefind-body` on `.single-body` scopes indexing to
368
- a page's prose; the theme's own chrome carries `data-pagefind-ignore` — the
369
- header, the footer, the infobox rail, the table of contents, the Related
370
- card, and the hero banner's title and tagline. `_default/single.html` also
371
- tags every page with `data-pagefind-filter="type"` and
372
- `data-pagefind-filter="package"`, read from the page's own `type` and
373
- `package` front matter, so a search can be narrowed to one catalog or one
374
- package without either value appearing in the page itself.
432
+ note at that address. Pagefind's own panel UI needs no page of its own: the
433
+ header button opens it in place.
434
+
435
+ **What is indexed.** `data-pagefind-body` on a page's prose scopes indexing to
436
+ it; the theme's own chrome carries `data-pagefind-ignore` — the header, the
437
+ footer, the infobox rail, the table of contents, the Related card, and the
438
+ hero banner's title and tagline.
439
+
440
+ **What a page tells the search.** Each page carries hidden metadata the
441
+ search reads: its title, its filters, its aliases, its picture and, for a
442
+ post, its date.
443
+
444
+ | Filter | Values |
445
+ | ----------- | -------------------------------------------- |
446
+ | `package` | the package that publishes the note |
447
+ | `type` | the note's `type` |
448
+ | `subtype` | the note's `subType` |
449
+ | `culture` | the name of the culture `data.culture` names |
450
+ | `continent` | the continent the note lies in |
451
+ | `region` | the outermost region beneath that continent |
452
+
453
+ A post carries `author` and `tags` filters and sorts by its `date`. A note's
454
+ name and its aliases count ten times a word of its body, so a search for a name
455
+ opens that note first and a search for an alias finds it. A result shows the
456
+ note's `data.icon`, or its hero banner where it has none. A note's tags and its
457
+ draft state are not filters. Secret blocks, inline secrets and `.secret`
458
+ sections are kept out of the index. [The site guide](../guides/site.md#search)
459
+ says where a note lies and how a merged search reads other sites' indexes.
375
460
 
376
461
  **Classes added:** `.site-search`, `.search-toggle`, `.search-panel`,
377
462
  `.search-panel-inner`, and `.header-actions` (the header's nav/search/toggle
@@ -379,20 +464,20 @@ group). `.search-panel-inner` carries Pagefind's own `--pagefind-ui-*`
379
464
  theming variables, set from the theme's own tokens so results match the
380
465
  chrome around them.
381
466
 
382
- ## What a page, a homepage and a section landing carry
467
+ ## What a page, a homepage and a listing carry
383
468
 
384
469
  ### A page
385
470
 
386
- `_default/single.html` renders every page that is not the homepage: the hero,
387
- the infobox rail, the body, any `related:` block, and prev/next links through
388
- the page's own catalog.
471
+ The `page` layout draws every note that is not the homepage: the hero, the
472
+ infobox rail, the body, any `related:` block, and prev/next links through the
473
+ page's own catalog.
389
474
 
390
475
  **Emitted.** A note's page arrives with `title` (the note's name), `type`,
391
476
  `subType`, `description` and `tags` already set, and `data.banner` resolved.
392
- The form below is the hand-written one, which is what a blog post or a
393
- curated landing carries: `title`, `date` and a top-level `banner:` are keys
394
- the content build never writes, and a note's page therefore has no `date` and
395
- no meta-line date at all.
477
+ The form below is the hand-written one, which is what a curated landing
478
+ carries: `title`, `date` and a top-level `banner:` are keys the content build
479
+ never writes, and a note's page therefore has no `date` and no meta-line date
480
+ at all.
396
481
 
397
482
  ```yaml
398
483
  ---
@@ -408,13 +493,13 @@ tags: [archetype, mercantyl] # optional; shown in the page meta line
408
493
 
409
494
  - **`type`** is the page's catalog key, unless it is `doc` — prose rather than
410
495
  a catalog of its own — in which case the key is `subType` (what the build
411
- compiles from a note's classification) or, failing that, `category` (what a
412
- hand-mounted documentation tree carries). A page with no `type` has no
413
- catalog.
496
+ compiles from a note's classification) or, failing that, `category`. A page
497
+ with no `type` has no catalog.
414
498
  - **`date`**, **`tags`** — each optional and shown only when present, in a
415
- meta line above the body. A tag links to `tags/<tag>/`.
499
+ meta line above the body. A tag links to `tags/<tag>/` where the site writes
500
+ a page for each tag (`site.tags`), and is plain text otherwise.
416
501
  - **`subType`** — shown as a plain label for `type: being` pages. It does not
417
- create a tag or taxonomy term.
502
+ create a tag.
418
503
 
419
504
  **The draft notice.** A page tagged `draft` states that its content is not
420
505
  settled, at the head of the article — above the infobox rail and above the
@@ -433,29 +518,34 @@ out of the meta line's tag row and said here instead.
433
518
  it states may change, and nothing in it is settled._
434
519
  - **`.draft-notice`**, **`.draft-notice-icon`**, **`.draft-notice-text`** and
435
520
  **`.draft-notice-label`** are the classes a consumer styles against.
436
- - The mark is Font Awesome, which `baseof.html` already loads; the sentence
521
+ - The mark is Font Awesome, which the base layout already loads; the sentence
437
522
  reads on its own where it does not arrive.
438
523
  - The sentence is an `em`: the label is the page's own voice naming the
439
524
  state, and the sentence is the page speaking about itself.
440
525
  - A page carrying the notice also carries **`.single-with-notice`** on its
441
526
  article, which is what moves a two-column page's content and rail below it.
442
527
 
443
- **Prev/next.** `.PrevInSection` / `.NextInSection` walk every page under the
444
- site's content mount, since the build emits every note flat there rather than
445
- filing it into a directory named for its catalog. A mount holding more than
446
- one catalog narrows the walk to the pages sharing this page's catalog key, so
447
- reading through the afflictions does not surface a skill; a mount holding a
448
- single catalog is untouched, because `.PrevInSection` already walks exactly
449
- that catalog there.
528
+ **Prev/next.** The links walk the pages of the site in the order a reader
529
+ walks them. A site holding more than one catalog narrows the walk to the pages
530
+ sharing this page's catalog key, so reading through the afflictions does not
531
+ surface a skill; a site holding a single catalog is untouched.
532
+
533
+ ### A post
534
+
535
+ A note of `type: doc` and `subType: post` is drawn by the `blog-post` layout:
536
+ the post's title, its date and writer, its series where it states one, its
537
+ tags, and its body. Posts are listed newest first on the page of the section
538
+ `site.feed` names, and the site writes an Atom feed beside that page.
450
539
 
451
540
  ### The homepage
452
541
 
453
- A package's home page is `content/_index.md`, mounted at the package's own
454
- prefix. `layouts/index.html` selects between two shapes by what it carries.
542
+ A package's home page is the `type: homepage` note, drawn by the `home`
543
+ layout at the package root. The layout selects between two shapes by what the
544
+ page carries.
455
545
 
456
546
  **`type: homepage`** renders the page through the same structure a note gets
457
- from `_default/single.html` — the hero from its `banner:`, then its body —
458
- sharing the markup through `partials/page-body.html` so a homepage and a page
547
+ from the `page` layout — the hero from its `banner:`, then its body —
548
+ sharing the markup through the `page-body` partial so a homepage and a page
459
549
  read as one family:
460
550
 
461
551
  ```yaml
@@ -465,11 +555,10 @@ title: Song of Heroic Lands # hero heading
465
555
  description: A classless, skill-based fantasy system … # hero standfirst
466
556
 
467
557
  # The hero image. The same `banner:` every other page in this theme uses,
468
- # resolved by `partials/hero-banner.html` in the documented order and through
469
- # `params.cdnBaseURL` — see "The hero banner" above. With none set, the
470
- # subtype default lands on `images/banners/homepage.webp`, since the subtype
471
- # default is keyed on the page's `type`. `banner: none` declines a hero image
472
- # entirely.
558
+ # resolved in the documented order and through `params.cdnBaseURL` — see "The
559
+ # hero banner" above. With none set, the subtype default lands on
560
+ # `images/banners/homepage.webp`, since the subtype default is keyed on the
561
+ # page's `type`. `banner: none` declines a hero image entirely.
473
562
  banner: brand/sohl-banner.webp
474
563
  ---
475
564
  Everything published for the system lives under this address …
@@ -479,39 +568,20 @@ The home page declares no `infoboxes:`, `related:`, `date:` or `tags:`, so
479
568
  those pieces of a page stay silent.
480
569
 
481
570
  **No `type` at all** — a site whose home page is a set of entry points into
482
- everything it publishes — renders the featured grid from `params.home`.
483
- A package-build configuration supplies those values through
484
- [`site.hugo`](../configuration.md#site).
485
-
486
- ### A section landing
487
-
488
- **Hand-written.** `_default/list.html` renders whatever a consumer still
489
- gives Hugo's `section`, `taxonomy` or `term` kinds to render — a
490
- hand-curated landing at a real subdirectory, and Hugo's own tag pages. A
491
- `package-build` site disables all three (`disableKinds`), so this layout
492
- never runs for one; a site that keeps them enabled still needs a landing, and
493
- this is it.
494
-
495
- It opens with the hero and any authored body, then auto-lists the section's
496
- members (`.Pages`) as a gap-filler: a member the body already links,
497
- directly or transitively, is not repeated, and the rest appear under
498
- "Orphaned Pages" when the body links some of them, or plainly when it links
499
- none. A tag's term page lists the pages carrying it; the tag index itself
500
- lists every tag with its page count.
501
-
502
- ```yaml
503
- ---
504
- # content/projects/_index.md — a hand-curated landing, no `type:` at all
505
- title: Projects
506
- description: Foundry VTT systems, modules, and reference content …
507
- ---
508
- Each section below has its own landing page.
509
-
510
- # [Song of Heroic Lands](/projects/song-of-heroic-lands/)
511
- …
512
- ```
513
-
514
- A row is a linked title and, when the page carries one, its description.
571
+ everything it publishes — renders the featured grid from `params.home`: a hero
572
+ (`heroimage`, `herotitle`, `herotagline`), the section headings, and a card for
573
+ each entry of `cards`, each with a title, an address, a line of text and an
574
+ optional image.
575
+
576
+ ### A listing
577
+
578
+ The `list` layout draws a page that lists other pages: a section a note
579
+ stands for (`site.sections`), and the tag pages `site.tags` writes. It opens
580
+ with the hero and any authored body, then lists the members the body does not
581
+ already link, directly or transitively, under a heading when the body links
582
+ some of them, or plainly when it links none. A tag's page lists the pages
583
+ carrying it; the tag index lists every tag with its page count. A row is a
584
+ linked title and, when the page carries one, its description.
515
585
 
516
586
  ## Alerts
517
587
 
@@ -9,7 +9,8 @@ subType: reference
9
9
 
10
10
  Preview renders only the supplied Markdown buffer. It works with ordinary
11
11
  Markdown, optional YAML frontmatter, and local Heroic Lands extensions, without
12
- configuration, a content tree, dependency caches, a content index or Pandoc.
12
+ configuration, a content tree, dependency caches, a content index or Pandoc. A
13
+ host that holds a content index can give it to the preview for a fuller view.
13
14
 
14
15
  ```sh
15
16
  package-build-preview < note.md > preview.html
@@ -25,6 +26,19 @@ is the default; `--dark` is explicit and independent of browser preferences.
25
26
  options. Unsupported arguments exit 1. Findings go to stderr; success exits 0,
26
27
  and render errors exit 1. A partial preview may accompany error findings.
27
28
 
29
+ Preview renders through the same renderer as the packs and the websites, so an
30
+ alert, a secret block, a verse, a captioned item, a footnote section and a
31
+ picture's size and position read as they do on those surfaces. Fenced code that
32
+ names a language is highlighted. A tag written in a note is shown as text.
33
+
34
+ The default view needs no content index, so it shows what it cannot resolve as
35
+ source. A host that holds the package's content index builds an index with
36
+ `previewIndex` and passes it to `renderPreview` as `index`. Rendered that way, a
37
+ wikilink is a link, a link to a concealed note is concealed, an embed draws its
38
+ picture, marked text is mapped and set in its face, an icon is drawn, and a
39
+ caption is numbered with its asset's role, so a map is a **Map**. A query, which
40
+ a buffer cannot run, stays highlighted source in both views.
41
+
28
42
  Wikilinks, image and portrait links retain their complete source: blue brackets,
29
43
  purple addresses, bold foreground labels and grey attributes. Entire spans are bold, including their visible brackets and attributes.
30
44
  Definition-list terms are bold and definitions use normal text. SQL and pagelist fences remain grey source;
@@ -156,7 +170,7 @@ go run check-sanitizer.go /path/to/gitea.ini < preview.html > sanitized.html
156
170
  ```
157
171
 
158
172
  Check both light and dark fixtures containing all four alert types, a custom
159
- title, nesting, wikilinks and attributes, SQL/pagelist fences, infoboxes, poetry
173
+ title, nesting, wikilinks and attributes, SQL/pagelist fences, infoboxes, verse
160
174
  and footnotes. All wrapper/token classes and icon shapes should remain, while
161
175
  injected scripts and event-handler attributes should disappear. This replay
162
176
  checks the sanitizer primitives; also inspect the result in the actual Gitea
@@ -37,7 +37,7 @@
37
37
 
38
38
  import { scanAlerts } from "./content-alerts.mjs";
39
39
  import { scanBlocks } from "./content-blocks.mjs";
40
- import { scanPoetry } from "./content-poetry.mjs";
40
+ import { scanVerse } from "./content-verse.mjs";
41
41
  import { scanSpans } from "./content-spans.mjs";
42
42
  import { slugify } from "./content-slug.mjs";
43
43
  import { scanFigures } from "./content-figures.mjs";
@@ -123,7 +123,7 @@ export function collectAnchors(body, bodyLine = 1, resolveRole, syntax) {
123
123
  kind: "prose",
124
124
  });
125
125
  }
126
- for (const poem of scanPoetry(body).blocks) {
126
+ for (const poem of scanVerse(body).blocks) {
127
127
  if (poem.id)
128
128
  anchors.push({
129
129
  slug: slugify(poem.id),
@@ -272,8 +272,11 @@ export function assetsManifest(staged, stageDir) {
272
272
  *
273
273
  * The pictures are written beside whatever a `packageBuild.assets` row already
274
274
  * put under `icons/` and `images/`; nothing there is removed. A picture whose
275
- * deployed path is already a file in the stage is a finding, and no picture is
276
- * written. Clearing an earlier run's pictures is the stage reset's work.
275
+ * deployed path already holds a file with the bytes it would stage — after the
276
+ * transform, when there is one — is its own earlier output and is skipped, so
277
+ * staging twice into one directory is quiet. A file there with other bytes is a
278
+ * finding, and no picture is written. Clearing an earlier run's pictures is the
279
+ * stage reset's work.
277
280
  *
278
281
  * @param {object} opts - Options.
279
282
  * @param {string} opts.assetsBase - The package's asset directory, the roots'
@@ -309,13 +312,22 @@ export function stagePackageAssets({
309
312
  if (findings.length) return { staged: [], excluded: [], findings, manifest: null };
310
313
 
311
314
  const { staged, excluded } = selectStagedAssets(records, shippingLicenses);
312
- const planned = staged.map((record) => ({
313
- record,
314
- from: path.join(assetsBase, ...record.asset.path.split("/")),
315
- to: path.join(stageDir, .../** @type {string} */ (deployedAssetPath(record)).split("/")),
316
- }));
317
- for (const { record, from, to } of planned) {
315
+ const planned = staged.map((record) => {
316
+ const from = path.join(assetsBase, ...record.asset.path.split("/"));
317
+ return {
318
+ record,
319
+ from,
320
+ transformed: transform ? transform(from) : null,
321
+ to: path.join(
322
+ stageDir,
323
+ .../** @type {string} */ (deployedAssetPath(record)).split("/"),
324
+ ),
325
+ };
326
+ });
327
+ for (const { record, from, to, transformed } of planned) {
318
328
  if (!fs.existsSync(to)) continue;
329
+ const bytes = transformed != null ? transformed : fs.readFileSync(from);
330
+ if (Buffer.from(fs.readFileSync(to)).equals(Buffer.from(bytes))) continue;
319
331
  findings.push({
320
332
  file: to,
321
333
  severity: "error",
@@ -326,9 +338,9 @@ export function stagePackageAssets({
326
338
  });
327
339
  }
328
340
  if (findings.length) return { staged: [], excluded: [], findings, manifest: null };
329
- for (const { from, to } of planned) {
341
+ for (const { from, to, transformed } of planned) {
342
+ if (fs.existsSync(to)) continue;
330
343
  fs.mkdirSync(path.dirname(to), { recursive: true });
331
- const transformed = transform ? transform(from) : null;
332
344
  if (transformed != null) fs.writeFileSync(to, transformed);
333
345
  else fs.copyFileSync(from, to);
334
346
  }