@heroiclands/package-build 23.13.0 → 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 (233) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -1
  3. package/assets/preview/gitea.ini +12 -7
  4. package/assets/styles/content-base.css +150 -1
  5. package/bin/content-commands.mjs +356 -210
  6. package/bin/package-build.mjs +3 -2
  7. package/ci/check-banners.mjs +7 -7
  8. package/config.mjs +21 -6
  9. package/content-config.mjs +199 -80
  10. package/docs/api.md +93 -72
  11. package/docs/authoring/links-and-markup.md +316 -78
  12. package/docs/commands.md +244 -78
  13. package/docs/configuration.md +210 -102
  14. package/docs/content-format.md +2 -2
  15. package/docs/diagnostics.md +2 -2
  16. package/docs/getting-started.md +1 -1
  17. package/docs/guides/icons.md +1 -1
  18. package/docs/guides/module-project.md +5 -8
  19. package/docs/guides/release-and-deploy.md +2 -2
  20. package/docs/guides/site.md +112 -23
  21. package/docs/guides/system-project.md +1 -1
  22. package/docs/guides/writing-in-invented-scripts.md +1247 -0
  23. package/docs/index.md +1 -0
  24. package/docs/project-setup.md +28 -30
  25. package/docs/reference/format-details.md +589 -308
  26. package/docs/reference/note-types.md +40 -10
  27. package/docs/reference/theme-pages.md +127 -108
  28. package/docs/source-preview.md +15 -1
  29. package/engine/asset-shipping.mjs +22 -10
  30. package/engine/base-compiler.mjs +62 -104
  31. package/engine/changelog-lint.mjs +1 -1
  32. package/engine/content-address.mjs +1 -1
  33. package/engine/content-alerts.mjs +8 -4
  34. package/engine/content-blocks.mjs +26 -11
  35. package/engine/content-embeds.mjs +1 -1
  36. package/engine/content-expand.mjs +376 -0
  37. package/engine/content-figures.mjs +13 -6
  38. package/engine/content-format.yaml +8 -0
  39. package/engine/content-glyphs.mjs +213 -0
  40. package/engine/content-images.mjs +6 -7
  41. package/engine/content-links.mjs +3 -3
  42. package/engine/content-lint.mjs +1 -1
  43. package/engine/content-render.mjs +453 -0
  44. package/engine/content-scripts.mjs +326 -27
  45. package/engine/content-spans.mjs +4 -1
  46. package/engine/content-styles.mjs +91 -0
  47. package/engine/content-symbols.mjs +186 -0
  48. package/engine/content-verse.mjs +6 -18
  49. package/engine/core-report.mjs +169 -0
  50. package/engine/core-vocabulary.mjs +208 -0
  51. package/engine/dictionary-html.mjs +119 -0
  52. package/engine/field-reference.mjs +1 -1
  53. package/engine/frontmatter-lint.mjs +1 -1
  54. package/engine/heading-attributes.mjs +89 -0
  55. package/engine/helpers.mjs +61 -101
  56. package/engine/homepage.mjs +17 -29
  57. package/engine/html-minify.mjs +70 -0
  58. package/engine/index.mjs +8 -5
  59. package/engine/language-rows.mjs +8 -2
  60. package/engine/language-terms.mjs +115 -14
  61. package/engine/language-words.mjs +12 -0
  62. package/engine/logographic.mjs +255 -0
  63. package/engine/map-lexicon.mjs +221 -25
  64. package/engine/map-places.mjs +5 -4
  65. package/engine/markdown-expressions.mjs +0 -2
  66. package/engine/markdownlint-run.mjs +9 -3
  67. package/engine/markup-validation.mjs +21 -0
  68. package/engine/metre-declaration.mjs +134 -0
  69. package/engine/note-matter.mjs +2 -2
  70. package/engine/note-vocabulary.mjs +187 -3
  71. package/engine/pdf-build.mjs +68 -133
  72. package/engine/pdf-render.mjs +102 -44
  73. package/engine/post-fields.mjs +157 -0
  74. package/engine/preview-index.mjs +130 -0
  75. package/engine/preview-source.mjs +450 -0
  76. package/engine/project-init.mjs +5 -12
  77. package/engine/prose-config.mjs +3 -3
  78. package/engine/run-words.mjs +3 -2
  79. package/engine/script-emphasis-terms.mjs +38 -0
  80. package/engine/script-emphasis.mjs +295 -0
  81. package/engine/script-font-delivery.mjs +4 -4
  82. package/engine/site-build.mjs +259 -314
  83. package/engine/site-config.mjs +26 -238
  84. package/engine/site-files.mjs +175 -0
  85. package/engine/site-index.mjs +9 -4
  86. package/engine/site-preview.mjs +50 -523
  87. package/engine/site-render.mjs +593 -0
  88. package/engine/site-root.mjs +22 -13
  89. package/engine/site-search.mjs +161 -0
  90. package/engine/site-serve.mjs +273 -0
  91. package/engine/site-theme-banner.mjs +154 -0
  92. package/engine/site-theme-urls.mjs +125 -0
  93. package/engine/site-theme.mjs +926 -0
  94. package/engine/text-maps.mjs +57 -10
  95. package/engine/typographer.mjs +248 -0
  96. package/engine/verse-metre.mjs +357 -0
  97. package/engine/wikilink-syntax.mjs +29 -47
  98. package/engine/wikilinks.mjs +758 -279
  99. package/engine/word-forms.mjs +39 -24
  100. package/engine/word-rows.mjs +12 -2
  101. package/engine/word-terms.mjs +29 -4
  102. package/manifest.mjs +3 -4
  103. package/package.json +10 -7
  104. package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
  105. package/site-theme/layouts/base.hbs +31 -0
  106. package/site-theme/layouts/blog-list.hbs +30 -0
  107. package/site-theme/layouts/blog-post.hbs +40 -0
  108. package/site-theme/layouts/home.hbs +35 -0
  109. package/site-theme/layouts/list.hbs +20 -0
  110. package/site-theme/layouts/not-found.hbs +31 -0
  111. package/site-theme/layouts/page.hbs +7 -0
  112. package/site-theme/partials/copy-buttons.hbs +37 -0
  113. package/site-theme/partials/draft-notice.hbs +5 -0
  114. package/site-theme/partials/exturl.hbs +2 -0
  115. package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
  116. package/site-theme/partials/header.hbs +44 -0
  117. package/site-theme/partials/hero-banner.hbs +11 -0
  118. package/site-theme/partials/infobox-entry.hbs +2 -0
  119. package/site-theme/partials/infobox-section.hbs +31 -0
  120. package/site-theme/partials/infobox-value.hbs +2 -0
  121. package/site-theme/partials/infobox.hbs +18 -0
  122. package/site-theme/partials/list-members.hbs +9 -0
  123. package/site-theme/partials/nav-links.hbs +5 -0
  124. package/site-theme/partials/page-body.hbs +48 -0
  125. package/site-theme/partials/page-list.hbs +14 -0
  126. package/site-theme/partials/related.hbs +24 -0
  127. package/site-theme/partials/search-meta.hbs +6 -0
  128. package/site-theme/partials/search-names.hbs +2 -0
  129. package/site-theme/partials/search.hbs +52 -0
  130. package/site-theme/partials/tags.hbs +4 -0
  131. package/site-theme/partials/toc.hbs +7 -0
  132. package/{hugo-theme → site-theme}/static/css/style.css +14 -0
  133. package/stage.mjs +1 -2
  134. package/types/config.d.mts +8 -4
  135. package/types/content-config.d.mts +15 -15
  136. package/types/engine/asset-shipping.d.mts +5 -2
  137. package/types/engine/base-compiler.d.mts +20 -2
  138. package/types/engine/content-address.d.mts +1 -1
  139. package/types/engine/content-alerts.d.mts +8 -2
  140. package/types/engine/content-blocks.d.mts +11 -4
  141. package/types/engine/content-expand.d.mts +288 -0
  142. package/types/engine/content-figures.d.mts +11 -3
  143. package/types/engine/content-glyphs.d.mts +85 -0
  144. package/types/engine/content-images.d.mts +6 -7
  145. package/types/engine/content-links.d.mts +1 -1
  146. package/types/engine/content-render.d.mts +199 -0
  147. package/types/engine/content-scripts.d.mts +62 -0
  148. package/types/engine/content-spans.d.mts +1 -1
  149. package/types/engine/content-styles.d.mts +33 -0
  150. package/types/engine/content-symbols.d.mts +35 -0
  151. package/types/engine/core-report.d.mts +45 -0
  152. package/types/engine/core-vocabulary.d.mts +53 -0
  153. package/types/engine/dictionary-html.d.mts +8 -0
  154. package/types/engine/field-reference.d.mts +1 -1
  155. package/types/engine/heading-attributes.d.mts +20 -0
  156. package/types/engine/helpers.d.mts +3 -10
  157. package/types/engine/homepage.d.mts +11 -24
  158. package/types/engine/html-minify.d.mts +9 -0
  159. package/types/engine/index.d.mts +2 -1
  160. package/types/engine/language-terms.d.mts +24 -2
  161. package/types/engine/logographic.d.mts +45 -0
  162. package/types/engine/map-lexicon.d.mts +48 -8
  163. package/types/engine/map-places.d.mts +8 -0
  164. package/types/engine/markdown-expressions.d.mts +0 -2
  165. package/types/engine/metre-declaration.d.mts +39 -0
  166. package/types/engine/pdf-render.d.mts +6 -2
  167. package/types/engine/post-fields.d.mts +44 -0
  168. package/types/engine/preview-index.d.mts +72 -0
  169. package/types/engine/preview-source.d.mts +51 -0
  170. package/types/engine/script-emphasis-terms.d.mts +21 -0
  171. package/types/engine/script-emphasis.d.mts +84 -0
  172. package/types/engine/script-font-delivery.d.mts +4 -4
  173. package/types/engine/site-build.d.mts +71 -113
  174. package/types/engine/site-config.d.mts +14 -134
  175. package/types/engine/site-files.d.mts +73 -0
  176. package/types/engine/site-index.d.mts +11 -3
  177. package/types/engine/site-preview.d.mts +15 -19
  178. package/types/engine/site-render.d.mts +171 -0
  179. package/types/engine/site-root.d.mts +12 -4
  180. package/types/engine/site-search.d.mts +54 -0
  181. package/types/engine/site-serve.d.mts +44 -0
  182. package/types/engine/site-theme-banner.d.mts +51 -0
  183. package/types/engine/site-theme-urls.d.mts +60 -0
  184. package/types/engine/site-theme.d.mts +127 -0
  185. package/types/engine/text-maps.d.mts +17 -3
  186. package/types/engine/typographer.d.mts +11 -0
  187. package/types/engine/verse-metre.d.mts +131 -0
  188. package/types/engine/wikilink-syntax.d.mts +30 -48
  189. package/types/engine/wikilinks.d.mts +330 -14
  190. package/types/engine/word-forms.d.mts +3 -2
  191. package/types/manifest.d.mts +3 -4
  192. package/types/stage.d.mts +1 -2
  193. package/types/vendor/markdownlint.d.mts +8 -0
  194. package/vendor/markdownlint.mjs +20388 -0
  195. package/engine/web-wikilinks.mjs +0 -594
  196. package/hugo-theme/layouts/404.html +0 -85
  197. package/hugo-theme/layouts/_default/baseof.html +0 -67
  198. package/hugo-theme/layouts/_default/list.html +0 -139
  199. package/hugo-theme/layouts/_default/single.html +0 -4
  200. package/hugo-theme/layouts/blog/list.html +0 -54
  201. package/hugo-theme/layouts/blog-post/single.html +0 -36
  202. package/hugo-theme/layouts/index.html +0 -75
  203. package/hugo-theme/layouts/partials/banner-url.html +0 -141
  204. package/hugo-theme/layouts/partials/cdn-url.html +0 -36
  205. package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
  206. package/hugo-theme/layouts/partials/draft-notice.html +0 -41
  207. package/hugo-theme/layouts/partials/draft-state.html +0 -26
  208. package/hugo-theme/layouts/partials/header.html +0 -35
  209. package/hugo-theme/layouts/partials/hero-banner.html +0 -85
  210. package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
  211. package/hugo-theme/layouts/partials/infobox/items.html +0 -26
  212. package/hugo-theme/layouts/partials/infobox/section.html +0 -56
  213. package/hugo-theme/layouts/partials/infobox/value.html +0 -43
  214. package/hugo-theme/layouts/partials/infobox.html +0 -57
  215. package/hugo-theme/layouts/partials/page-body.html +0 -152
  216. package/hugo-theme/layouts/partials/related.html +0 -138
  217. package/hugo-theme/layouts/partials/search.html +0 -84
  218. package/hugo-theme/layouts/partials/site-url.html +0 -32
  219. package/hugo-theme/layouts/partials/tags.html +0 -30
  220. package/hugo-theme/layouts/partials/toc.html +0 -28
  221. package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
  222. package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
  223. package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
  224. package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
  225. package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
  226. package/hugo-theme/theme.toml +0 -11
  227. package/types/engine/web-wikilinks.d.mts +0 -107
  228. /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
  229. /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
  230. /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
  231. /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
  232. /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
  233. /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
@@ -202,7 +202,7 @@ The optional `type` attribute overrides inference and accepts `figure`, `table`,
202
202
 
203
203
  Identifiers are optional and must be unique within the note. Classes are unrestricted; `.border` uses the shared border style, while other classes can be styled by the medium. On a captioned table or SQL fence, `.full-width` sets the table across the page in a PDF book instead of in one column (`: Regional trade routes {.full-width}`); the other surfaces ignore it. Named attributes use `key=value`, with quoted values for text containing spaces. Use `#id` and `.class` rather than `id=` and `class=`; event-handler attributes are refused.
204
204
 
205
- Each numbered type has its own counter. Websites and Foundry number within a note; books number across their reading order. Unnumbered captions neither display nor consume a number. A map image can retain its asset-derived **Map** classification when no type is specified.
205
+ Each numbered type has its own counter. Websites and Foundry number within a note; books number across their reading order. Unnumbered captions neither display nor consume a number. A picture whose asset declares the `map` role counts as a **Map** when no type is specified, and a reference to it reads **Map 1** on every surface: in the note's own page, in a journal, and from another note's page. The editor preview knows the role when it has the package's content index; without one it numbers the picture a **Figure**.
206
206
 
207
207
  `{{ref "#trade"}}` displays the numbered label as a link. `form="full"` includes caption text and `form="title"` displays the caption alone. Use title form for an unnumbered caption. Cross-note references can name the note address before the anchor, such as `{{ref "place-harbor#trade"}}`; the displayed number belongs to the target on the surface rendering it. Links inside caption text contribute their visible text to references, so the reference does not contain a nested link.
208
208
 
@@ -373,7 +373,7 @@ fence is indented inside a list item. Verse is upright by default; use normal
373
373
  Markdown emphasis when italics are part of the poem.
374
374
 
375
375
  ````markdown
376
- ```verse {form=ballad meter="common meter" rhyme=ABCB syllables="8,6,8,6" lang=en}
376
+ ```verse {form=ballad lang=en}
377
377
  The lantern burns beside the gate,
378
378
  The harbor sleeps below.
379
379
 
@@ -382,16 +382,75 @@ And keeps a light aglow.
382
382
  ```
383
383
  ````
384
384
 
385
- The optional `form`, `meter` and `rhyme` attributes record the author's intent;
386
- the build does not infer stress, rhyme or pronunciation. `syllables` gives a
387
- positive count for each verse line in order, separated by commas, with blank
388
- stanza lines omitted. The build checks the number of counts, not the syllables
389
- spoken. `numstep` numbers the lines, and `col1` and `col2` set the poem in
390
- columns; see below. `lang` names the poem's language, `font` the font it is set in and
391
- `map` the mapping its text is rewritten by; see
385
+ The optional `form` attribute records the author's intent; the build does not
386
+ infer stress, rhyme or pronunciation. `numstep` numbers the lines, and `col1` and
387
+ `col2` set the poem in columns; see below. `lang` names the poem's language,
388
+ `font` the font it is set in and `map` the mapping its text is rewritten by; see
392
389
  [marking language, font and mapping](#marking-language-font-and-mapping). An optional `#id`
393
390
  addresses the poem itself. There are no `title` or `lines` attributes, and no
394
- special punctuation for stress, pauses or refrains.
391
+ special punctuation for stress, pauses or refrains. A poem's metre, rhyme or
392
+ syllable pattern is described in the note's prose. `scan` names a metre the
393
+ package declares, and the build counts the poem's beats against it, as the next
394
+ section describes.
395
+
396
+ #### Metre
397
+
398
+ `scan=` names a metre declared once in the package, as an entry of a `doc`
399
+ note's `data.metres`:
400
+
401
+ ```yaml
402
+ data:
403
+ metres:
404
+ - { name: hexameter, beats: 6, kind: accentual }
405
+ ```
406
+
407
+ `name` is lowercase letters, digits and `-`, unique in the package. `beats` is
408
+ the beats of one line, a whole number above zero. `kind` is `accentual`, the
409
+ only kind: a beat falls on a stressed syllable, and the length of a syllable
410
+ is not counted.
411
+
412
+ A verse fence that states `scan=` with the declared name, and `lang=` with a
413
+ language whose `note` names a language note stating
414
+ [`data.sounds.stress`](../reference/format-details.md#a-languages-register-sounds-and-history),
415
+ is counted line by line. A line that cannot have the metre's beats is a warning
416
+ at that line:
417
+
418
+ ````markdown
419
+ ```verse {form=epic scan=hexameter lang=vyl}
420
+ Thiruthriman sorimamen, ul alathas kedis alatha valmazor,
421
+ ```
422
+ ````
423
+
424
+ > ``this line has 4 beats (5 with secondary beats); the metre `hexameter` has 6``
425
+
426
+ Beats are counted this way:
427
+
428
+ - **Syllables** are vowel groups. A vowel is a letter whose base is `a`, `e`,
429
+ `i`, `o` or `u`, and a pair the language lists in
430
+ `data.sounds.syllables.vowelPairs` is one syllable.
431
+ - **Every word of two or more syllables has one beat**, on its stressed
432
+ syllable. The language's stress rule places it: `two` for a word of two
433
+ syllables, `longer` for a word of three or more, and a vowel carrying the
434
+ rule's `mark` is stressed wherever it sits.
435
+ - **A one-syllable noun, verb, adjective, numeral or name has a beat.** A
436
+ one-syllable particle, preposition, conjunction, adverb or pronoun has none.
437
+ The `class` of the lexicon entry decides, so the language's lexicon is read
438
+ through `lang=` as it is for the word check, and a form the language's rules
439
+ derive takes its word's class. A word whose entries differ in class is a
440
+ beat that may be left out.
441
+ - **A one-syllable word the lexicon does not hold is a content word** and has a
442
+ beat. The word check reports the word separately.
443
+ - **A word of four or more syllables may take a second, lighter beat on its
444
+ first syllable** where the line needs it, unless its stress is already
445
+ there. A line scans when its count can be the metre's, choosing second beats
446
+ as needed; the warning gives the count without them and, in brackets, with
447
+ all of them.
448
+ - The caesura is not written.
449
+
450
+ `scan=` is refused where it cannot mean anything: a name no `data.metres`
451
+ declares, a fence with no `lang=`, a language without a stress rule, and a verse
452
+ in columns. A fence with no `scan=` is not counted. Readers see nothing of the
453
+ count; the fence carries `data-scan` with the metre's name.
395
454
 
396
455
  To number and caption a poem, put a leading numbered caption before the verse fence:
397
456
 
@@ -656,7 +715,8 @@ vyl zaron` converts a passage, both through the build's own code; see
656
715
  the language's [`note`](../configuration.md#languages) names its language note.
657
716
  Each word, case folded, is one of:
658
717
 
659
- 1. **a lexicon word**: a `data.words[].form` of the language;
718
+ 1. **a lexicon word**: a `data.words[].form` of the language, or one of its
719
+ `variants`, which takes every rule below as its word does;
660
720
  2. **a word of a registered name**: a word of a `name` in the language note's
661
721
  own `data.register`, under any `tongue` but `translation`. A name of several
662
722
  words is checked word by word, so `Revothar Baremzator` admits `Revothar`
@@ -763,6 +823,94 @@ A run with `lang=` and no `map=`, a run with `map=` and no
763
823
  `lang=`, a bare BCP 47 tag and a declared language with no `note` are not
764
824
  checked.
765
825
 
826
+ ### Logographic maps
827
+
828
+ **A script that writes meanings is written from the dictionary, never by
829
+ sound.** Its mapping note states `logographic: true`, and every word of a run
830
+ under it is written from the language's lexicon. The map's rows write only the
831
+ text between words: the spaces, the points, and anything else with no letter
832
+ in it. A word never falls through to the rows, so a word the lexicon gives no
833
+ writing is an error rather than a guess.
834
+
835
+ A word of the run is written by the first of these that gives a writing:
836
+
837
+ 1. **The word's own `native` entry** for the map, the one for the run's face
838
+ where an entry names one, else the one for no face. A word written its own
839
+ way, such as a set compound, states this even where its parts would give
840
+ another writing.
841
+ 2. **Its parts.**
842
+ - **A word stating `built`** is written by its pieces' writings joined in
843
+ order. A piece is a word of the lexicon, matched exactly and then with its
844
+ joining hyphens taken off, or an affix of `data.affixes` with a `native`
845
+ entry of its own. A word piece is written by this same rule, so a compound
846
+ of compounds resolves all the way down.
847
+ - **A noun's plural and a verb's present forms** are written by the stem's
848
+ writing followed by the ending's, which `data.grammar.native` states once
849
+ per ending: `plural`, or one of the persons `I`, `you`, `heShe`, `we`,
850
+ `youAll` and `they`. The forms themselves are the ones the language's
851
+ rules give, as for the lexicon check.
852
+ - **A hyphenated word no entry states** is written by each of its parts in
853
+ turn, so `Gar-halzunet` is `Gar-`'s writing followed by `halzunet`'s.
854
+ 3. **Otherwise the word is an error** at the word. Coin the word or give it a
855
+ writing, or write the signs directly.
856
+
857
+ A word is matched case folded, because the script has no capitals: `Halzi` and
858
+ `halzi` both find the entries `Halzi` and `halzi`. **Where one roman form
859
+ stands for entries written differently**, or where one has a writing and
860
+ another has none, the run cannot tell which sense is meant, and the word is an
861
+ error naming every entry's gloss. Write that word directly in signs, picking
862
+ the sense by its gloss from `package-build lexicon khel --map zethu`. Signs typed
863
+ in the run are no word, and pass through as typed. Entries that share a form
864
+ and are written alike are written so.
865
+
866
+ Khelathi in Zethu, with its roots, an affix and the plural written once:
867
+
868
+ ```yaml
869
+ affixes:
870
+ - form: "-unet"
871
+ gloss: "a thing made of"
872
+ native:
873
+ - { map: zethu, text: "\U0001EBC7" }
874
+ grammar:
875
+ plural:
876
+ - { ending: "", becomes: "im" }
877
+ native:
878
+ - { ending: plural, map: zethu, text: "\U0001EBE1" }
879
+ words:
880
+ - form: halzi
881
+ class: n
882
+ field: "The body"
883
+ gloss: heart
884
+ native:
885
+ - { map: zethu, text: "\U0001EABB" }
886
+ - form: halzunet
887
+ class: n
888
+ field: "Counting-house and water-works"
889
+ gloss: the account
890
+ built: [halzi, -unet]
891
+ ```
892
+
893
+ `[halzi halzunet halziim]{lang=khel font=khelathi-zethu-glyphs map=zethu}` is
894
+ written U+1EABB, then U+1EABB U+1EBC7, then U+1EABB U+1EBE1, with the spaces
895
+ the rows write between them. A word of the run that resolves to nothing is
896
+ reported once per run:
897
+
898
+ > `` `zamlu` has no `zethu` writing in the `khel` lexicon, and its parts do not give one; state one, or write the word directly in signs ``
899
+
900
+ The lexicon check still applies: every word of the run is a word of its
901
+ language, a registered name or a regular form of one. A registered name is
902
+ written like any other word, so a name with no writing is an error; a foreign
903
+ name is written deliberately, in signs or through a map that rewrites a
904
+ sign-by-sign notation.
905
+
906
+ An affix's `native` and `data.grammar.native` are read only under a logographic
907
+ map, so either one naming any other map is an error at the entry, and an ending
908
+ of a person is an error where `data.grammar.verbs` states no `persons`. A
909
+ word's `native` under a logographic map is not held to what the rows give,
910
+ because the rows spell no word. `package-build lexicon khel --map zethu` lists
911
+ every word with its writing and where it comes from, and marks the words that
912
+ have none.
913
+
766
914
  ### Mapping notes
767
915
 
768
916
  A mapping note is a `doc` note whose `data.map` lists the rewrites:
@@ -812,6 +960,10 @@ How Vylari's roman spelling is written in the Semaron chancery hand.
812
960
  reaches only code points up to U+FFFF. A row matches and writes whole code
813
961
  points.
814
962
  - **`vowels`** lists the characters `before: vowel` reads as vowels.
963
+ - **`logographic: true`** marks a script that writes meanings: every word of
964
+ a run is written from the lexicon, and the rows write only the text between
965
+ words; see [logographic maps](#logographic-maps). A row whose `from` holds a
966
+ letter could never apply on such a map, and is an error at the row.
815
967
 
816
968
  How a map reads text:
817
969
 
@@ -864,6 +1016,8 @@ plain text.
864
1016
 
865
1017
  - **A span or a verse line** holds no emphasis, link, image, footnote,
866
1018
  wikilink, code or HTML, and no span but a marked one inside a div or a fence.
1019
+ Text set in a face is the one exception: it takes `*…*` and `**…**`, as
1020
+ [emphasis in a script](#emphasis-in-a-script) describes.
867
1021
  - **A div** holds paragraphs and line breaks only: no heading, list, block
868
1022
  quote, code fence, verse fence, table, rule, footnote definition or nested
869
1023
  block. Mark a poem's own fence instead of a div around it.
@@ -877,6 +1031,67 @@ defines `[^id]:`. A bracket with no partner takes a backslash, `\[`.
877
1031
  Text marked with a real language's tag alone keeps its Markdown, so
878
1032
  `[ad _astra_]{lang=la}` and an English poem with emphasis are both accepted.
879
1033
 
1034
+ ### Emphasis in a script
1035
+
1036
+ Inside a run set in a face, `*…*` and `**…**` are written exactly as in
1037
+ English. No script marks a stressed word by a bold or a slant, so the build sets
1038
+ emphasis by the scribal convention the text's language or its script states:
1039
+
1040
+ | Convention | On the site and in Foundry | In the book |
1041
+ | ---------- | -------------------------------------------------- | --------------------- |
1042
+ | `rubric` | `<em class="emph-rubric">`, in red | in the book's red ink |
1043
+ | `overline` | `<em class="emph-overline">`, with a line above it | under an overline |
1044
+ | none | `<em>` with no class, in the face's Regular | in the face's Regular |
1045
+
1046
+ `**…**` gives a `<strong>` in the same way. The element stays an `<em>` or a
1047
+ `<strong>`, so a screen reader and a copied passage keep the emphasis.
1048
+
1049
+ **The convention is the language's, else the script's.** A run's `lang=`
1050
+ language, through the note its [`languages.<name>.note`](../configuration.md#languages)
1051
+ names, may state `data.emphasis`; otherwise the script note listing the run's
1052
+ face states it in `data.script`; otherwise there is none. A script note:
1053
+
1054
+ ```yaml
1055
+ ---
1056
+ shortcode: semaronscrpt
1057
+ name: { full: Semaron Script }
1058
+ type: skill
1059
+ subType: script
1060
+ data:
1061
+ script:
1062
+ fonts: [semaron-kuriale]
1063
+ emphasis: rubric
1064
+ ---
1065
+ ```
1066
+
1067
+ So a tongue that states nothing takes its script's convention. Provenzal set in
1068
+ Semaron is rubric:
1069
+
1070
+ ```markdown
1071
+ The lintel reads [a _stressed_ word]{lang=prv font=semaron-kuriale map=semaron}.
1072
+ ```
1073
+
1074
+ and renders on the site as
1075
+
1076
+ ```html
1077
+ <span class="script hl-semaron-kuriale" lang="art-x-prv" data-pagefind-ignore="true"
1078
+ >… <em class="emph-rubric">…</em> …</span
1079
+ >
1080
+ ```
1081
+
1082
+ **A language may mark emphasis with a word** instead of a mark:
1083
+ `data.emphasis: words` on its language note. Emphasis in a run of that language
1084
+ is then an error at the emphasis, and the remedy is the word the language
1085
+ stresses with. A language may also state `rubric` or `overline`, which it takes
1086
+ in every script it is written in.
1087
+
1088
+ `_…_` is emphasis equal to `*…*`, and `__…__` equal to `**…**`, so a note keeps
1089
+ its meaning when a formatter rewrites one pair to the other. A strikethrough is
1090
+ an error in a face; an escaped `\*`, or a star that pairs with none, is a character the map and the
1091
+ face are checked for. A stressed word is best enclosed whole: a star inside a
1092
+ word splits it, so a map's row or a lexicon word that crosses the star is not
1093
+ matched.
1094
+
880
1095
  ### Errors
881
1096
 
882
1097
  Each of these is an error at its line and column, reported by `lint`, the site
@@ -894,9 +1109,14 @@ that `lint` reports and that fails nothing:
894
1109
  | `` `ch` maps to U+F0299 in the map `semaron`, which the font `semaron-kuriale` does not draw `` | Correct the row's `to`. |
895
1110
  | `` `zaron` is written U+F0299 by the lexicon of `vyl` for the map `semaron`, which the font `semaron-kuriale` does not draw `` | Correct the word's `native` text. |
896
1111
  | `` `Orlan` is capitalised, and the native form the lexicon of `vyl` states for `orlan` under the map `semaron` does not begin as the rows spell its first letter, so the rows cannot capitalise it; state `Orlan` as a word of its own with its native form `` | State the capitalised word as a lexicon word of its own with its native form. |
1112
+ | `` `zamlu` has no `zethu` writing in the `khel` lexicon, and its parts do not give one; state one, or write the word directly in signs `` | Give the word, or its pieces and endings, a writing for the map, or write its signs directly. |
1113
+ | `` `nelgir` stands for 2 words of the `khel` lexicon that the map `zethu` writes differently, “a god” and “god”; write the word directly in signs, choosing by its sense in `package-build lexicon khel --map zethu` `` | Write the signs of the sense meant, from the lexicon listing. |
1114
+ | `` `halzi` has no `zethu` writing: the map `zethu` is logographic, and the run names no `lang=` whose lexicon writes it; mark the run with its language, or write the word directly in signs `` | Mark the run with `lang=`, or write its signs directly. |
897
1115
  | `` `thiruthrimar` is not a word of the `vyl` lexicon, a registered name, or a regular form of one; coin it in the lexicon (or register the name) first `` | Coin the word in the language note's `data.words`, register the name in its own `data.register`, or correct the spelling. |
898
1116
  | `` `thuravark-carved` is not a font of the map `semaron`; it is for `semaron-kuriale` `` | Set the run in a face the map lists, or list the face in `data.map.fonts`. |
899
1117
  | ``text marked `lang=vyl` is plain text and holds no emphasis`` | Remove the markup, or escape the character with a backslash. |
1118
+ | ``text marked `lang=prv font=semaron-kuriale map=semaron` takes emphasis only as `*…*`, `_…_`, `**…**` or `__…__` `` | Write the emphasis with stars or underscores. |
1119
+ | `` `vyl` marks emphasis with a word, not a mark; write the word `` | Remove the emphasis marks and write the word the language stresses with. |
900
1120
  | ``a div marked `lang=vyl font=semaron-kuriale map=semaron` holds paragraphs and line breaks only, not a list`` | Move the list out of the div. |
901
1121
  | `` `font=` is read on a span, a div or a verse fence, not on a secret block `` | Put a marked div inside the secret block. |
902
1122
  | `` `vly` is read as a BCP 47 tag; did you mean the declared language `vyl`? `` (a warning) | Write the declared name, or ignore it when the tag is meant. |
@@ -920,6 +1140,48 @@ the `tag` it resolves to (`NULL` when it resolves to none), `font`, `map`, `line
920
1140
  and `column`. A run inside a div or a fence records the keys it inherits. See
921
1141
  [the `runs` relation](../reference/format-details.md#content-tables).
922
1142
 
1143
+ ### Tying words to the core vocabulary
1144
+
1145
+ A setting can name the concepts every language of it has a word for, and measure
1146
+ each language against them without reading a gloss. Three things state it:
1147
+
1148
+ 1. **A concept list** in the `data.concepts` of a `doc` note: each concept has
1149
+ an `id` (lowercase letters, digits and `-`, unique in the package), a short
1150
+ `gloss` and a `group` the report lists it under.
1151
+ 2. **`core: true`** in the `data` of each language note that must cover the list.
1152
+ 3. **`concept`** on each word that serves a concept: one id, or a list of ids
1153
+ for a word that serves several.
1154
+
1155
+ ```yaml
1156
+ # Docs/Core_Concepts.md
1157
+ type: doc
1158
+ subType: reference
1159
+ data:
1160
+ concepts:
1161
+ - { id: see-verb, gloss: to see, group: senses }
1162
+ - { id: smell-noun, gloss: "a smell; a scent", group: senses }
1163
+ - { id: knee, gloss: the knee, group: body }
1164
+
1165
+ # Skills/Vylari.md
1166
+ type: skill
1167
+ subType: language
1168
+ data:
1169
+ core: true
1170
+ words:
1171
+ - { form: veda, class: n, field: Senses, gloss: a sight, concept: see-verb }
1172
+ ```
1173
+
1174
+ A `concept` that names no entry of the list is an error at the id, with the
1175
+ nearest id offered: `` `smel-noun` is not a core concept; did you mean
1176
+ `smell-noun`? ``. A language reads the words of its `data.family` chain as its
1177
+ own, so a parent's tagged word covers the daughter's concept.
1178
+
1179
+ `package-build core` lists, for each language that states `core: true`, the
1180
+ concepts it has no word for, by group with counts. A missing concept is never an
1181
+ error: the report is the work list, and `--language <name> --json` serves an
1182
+ agent working through one language. See
1183
+ [the command reference](../commands.md#package-build-core).
1184
+
923
1185
  ## Footnotes and definition lists
924
1186
 
925
1187
  Write a footnote reference as `[^id]` and its definition as `[^id]: text`.
@@ -1003,6 +1265,18 @@ plain words, not a checkbox — write what is done into the item itself:
1003
1265
  - the ferry crossing: owed
1004
1266
  ```
1005
1267
 
1268
+ ## Fenced code
1269
+
1270
+ A fence that names a language highlight.js knows is highlighted when the page is built, so a Foundry journal and the editor preview show a listing with its keywords, strings and comments coloured. The colours follow the light or dark scheme the page is in. A fence with no language, or a language highlight.js does not know, is shown as plain text.
1271
+
1272
+ ````markdown
1273
+ ```js
1274
+ const lantern = "lit"; // the harbor light
1275
+ ```
1276
+ ````
1277
+
1278
+ A fence named `sql`, `pagelist` or `verse` is not a listing: a query is replaced by its table, and verse is set as a poem.
1279
+
1006
1280
  ## SQL generated tables
1007
1281
 
1008
1282
  A fence marked `sql` is replaced by a table built from the content index. The query reads the frontmatter of every indexed note, so a table is written once and stays true as notes are added.
@@ -1041,7 +1315,7 @@ A column's heading is its SQL alias, so `AS "Name"` is what a reader sees. A cel
1041
1315
  | `edges` | One row per entry of a language note's `data.sounds.edges`: the `edge` and its `sounds` as a list. |
1042
1316
  | `clusters` | One row per language note stating `data.sounds.clusters`, with its `max`. |
1043
1317
  | `cluster_groups` | One row per group of a language note's `data.sounds.clusters.groups`: `what`, `clusters` as a list, and `use`. |
1044
- | `stress` | One row per language note stating `data.sounds.stress`, with its `stress`. |
1318
+ | `stress` | One row per language note stating `data.sounds.stress`, with its `two`, `longer` and `mark` (`NULL` where unstated). |
1045
1319
  | `finals` | One row per entry of a language note's `data.sounds.finals`: `final` and `like`. |
1046
1320
  | `syllables` | One row per language note stating `data.sounds.syllables`: each rule a column, the harmony as `harmony_back`, `harmony_front`, `harmony_neutral`, `harmony_neutralAs` and `harmony_partners`, a list of `{ vowel, partner }`. |
1047
1321
  | `joining` | One row per entry of a language note's `data.sounds.joining`: `first`, `second`, `written` and `example`. |
@@ -1357,7 +1631,7 @@ The stress rule, the syllable rules and the finals. A rule of `syllables` is a c
1357
1631
 
1358
1632
  ````markdown
1359
1633
  ```sql
1360
- SELECT 'Stress falls on' AS "Rule", stress AS "Value"
1634
+ SELECT 'Stress falls on' AS "Rule", two || ' of two syllables, ' || longer || ' of more' AS "Value"
1361
1635
  FROM stress WHERE language = 'thalorna-note-skill-vylarilng'
1362
1636
  ```
1363
1637
 
@@ -1776,92 +2050,56 @@ A page list names this package's own pages. A **stub** — a note with neither a
1776
2050
 
1777
2051
  See [page-list details](../reference/format-details.md#page-lists) for the directive's full behaviour.
1778
2052
 
1779
- ## Theme shortcodes
1780
-
1781
- The Hugo theme that renders every site defines four shortcodes. An author writes one in a note body as `{{< name … >}}`, and only the website renders it. The shared note pipeline leaves a shortcode in the source untouched, so the compendium packs and the printed book carry the call as literal text. Use a shortcode only on a page whose purpose is the website. Where a construct must reach all three surfaces, use one of the constructs earlier on this page, which every surface resolves.
2053
+ ## Signs: glyphs and cartouches
1782
2054
 
1783
- A shortcode takes either positional arguments or named ones, and a single call cannot mix the two. `{{< glyph "anubis" >}}` and `{{< glyph slug="anubis" frame="tablet" >}}` are both valid; `{{< glyph "anubis" frame="tablet" >}}` is refused by Hugo. A call to a shortcode that has a required argument and omits it fails the site build with the note's position.
2055
+ A **glyph** is a small picture set in a line of text, such as the sign that writes a deity's name. A **cartouche** is a framed seal set among the words around it. Both are pictures the package ships, and both render on every surface: the Foundry journals, the website, the editor preview and the book.
1784
2056
 
1785
- ### `cartouche`
1786
-
1787
- Renders a named SVG seal as an ornamental accent, as a block figure or inline with text. The image is read from `img/cartouches/<slug>.svg` in the consuming site's static files.
1788
-
1789
- | Parameter | Value | Default | What it does |
1790
- | --------- | ------------------ | ------------------ | ----------------------------------------------------------------------------------------------------- |
1791
- | `slug` | the SVG's basename | required | Names the file. May be given as the first positional argument when no other parameter is used. |
1792
- | `size` | `inline` | block | `inline` flows the image with the surrounding text. Any other value adds a `cartouche-<value>` class. |
1793
- | `align` | `left` or `right` | centered | Floats the figure so prose wraps around it. |
1794
- | `height` | a CSS length | set by the theme | Sets the image's height, for example `10rem` or `160px`. The width follows the image's aspect ratio. |
1795
- | `alt` | text | the humanized slug | The image's text alternative. |
1796
- | `caption` | text | none | A caption under the image. Block mode only; an inline cartouche ignores it. |
2057
+ Each is a bracket span: a glyph has the `.glyph` class and a cartouche has the `.cartouche` class.
1797
2058
 
1798
2059
  ```markdown
1799
- {{< cartouche "takheperu" >}}
1800
-
1801
- {{< cartouche slug="aukhelathu" height="10rem" align="right" >}}
2060
+ The name [Ra]{.glyph slug=ra} is written with one sign.
1802
2061
 
1803
- {{< cartouche slug="anubis" caption="Anpu" >}}
2062
+ | God | Sign |
2063
+ | ------ | ------------------------------------------------- |
2064
+ | Anubis | [Anubis, jackal]{.glyph slug=anubis frame=tablet} |
1804
2065
 
1805
- The sign {{< cartouche slug="ra" size="inline" >}} stands for the sun.
2066
+ The seal [Au'Khelathu]{.cartouche slug=aukhelathu height=10rem align=right caption="Au'Khelathu"} is cut above the gate.
1806
2067
  ```
1807
2068
 
1808
- The first call centers the `takheperu` seal as a block. The second floats the `aukhelathu` seal to the right at ten rem tall, with the following paragraph wrapping around it. The third adds a caption under the seal. The fourth sets the seal in the line of text.
1809
-
1810
- ### `exturl`
1811
-
1812
- Renders a web address as a code-styled string followed by a button that copies it to the clipboard. It exists for an address a reader is meant to paste somewhere, such as a server address or a manifest URL, where a clickable link would be the wrong affordance. The address is the first positional argument and the shortcode takes no other.
1813
-
1814
- | Parameter | Value | Default | What it does |
1815
- | ------------------ | ------------------------- | ------- | ---------------------------------------------------------------------- |
1816
- | first (positional) | the text to show and copy | none | The string displayed in the code element and written to the clipboard. |
1817
-
1818
- ```markdown
1819
- Install from {{< exturl "https://example.org/module/manifest.json" >}} in the module browser.
1820
- ```
1821
-
1822
- The page shows the address in a code element with a copy icon beside it. Selecting the icon copies the address and briefly marks the button as copied. The text is not a link.
2069
+ Each picture is named by `slug` and is a file the package ships: a glyph is `assets/icons/<slug>.svg` and a cartouche is `assets/images/cartouches/<slug>.svg`. Each surface serves the file at the address it serves every other [image](#images-and-protected-content) at. Draw the SVG with `stroke="currentColor"` and `fill="none"` to take the colour of the surface it appears on.
1823
2070
 
1824
2071
  ### `glyph`
1825
2072
 
1826
- Renders an icon-scale SVG, such as a deity-name cluster, a category icon or an ornamental mark, in the line of text or as a block. The image is `icons/<slug>.svg`, resolved against the consuming site's `params.cdnBaseURL`. This is an image drawn from the site's icon directory; it is a separate construct from the `:icon name:` font glyphs of the [icon guide](../guides/icons.md), which every surface renders from the package's declared `icons` registry.
2073
+ The label between the brackets is the picture's text alternative, read aloud by a screen reader and printed in the book when the package ships no file for the sign. An empty label takes the humanized slug.
1827
2074
 
1828
- | Parameter | Value | Default | What it does |
1829
- | --------- | ------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------- |
1830
- | `slug` | the SVG's basename | required | Names the file. May be given as the first positional argument when no other parameter is used. |
1831
- | `frame` | `tablet` | none | Sets the glyph inside a limestone rectangular frame, so rows of different intrinsic width line up in a table. |
1832
- | `size` | `inline` or `block` | 1.8em tall | `inline` is small and flows with the text. `block` centers the glyph as a standalone line. |
1833
- | `alt` | text | the humanized slug | The image's text alternative. |
2075
+ | Attribute | Value | Default | What it does |
2076
+ | --------- | ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------- |
2077
+ | `slug` | the SVG's basename | required | Names the file `icons/<slug>.svg`. |
2078
+ | `frame` | `tablet` | none | Sets the glyph inside a limestone rectangular frame, so rows of different intrinsic width line up in a table. |
2079
+ | `size` | `inline` or `block` | 1.8em tall | `inline` is small and flows with the text. `block` centers the glyph as a standalone line. |
1834
2080
 
1835
- ```markdown
1836
- | God | Glyph |
1837
- | ------ | ------------------------------------------ |
1838
- | Anubis | {{< glyph slug="anubis" frame="tablet" >}} |
1839
-
1840
- The name {{< glyph "ra" >}} is written with one sign.
2081
+ A glyph takes no `#id`, no other class and no other attribute. Write the alternative text as the label; an `alt=` attribute is refused, as is any attribute the glyph does not take. A glyph with a finding is drawn as an ordinary span, and the finding names the line and column of the span.
1841
2082
 
1842
- {{< glyph slug="ra" size="block" alt="Sun god Ra" >}}
1843
- ```
2083
+ This is a separate construct from the `:icon name:` font glyphs of the [icon guide](../guides/icons.md), which every surface renders from the package's declared `icons` registry.
1844
2084
 
1845
- ### `pagelist`
1846
-
1847
- Renders the pages carrying a tag as a list of links, ordered as asked. The group is expressed by intent: a page joins by being tagged and leaves by being untagged. The shortcode lists the site's own pages by the tags the site's taxonomy holds.
2085
+ ### `cartouche`
1848
2086
 
1849
- The ` ```pagelist ` fence described under [Page lists](#page-lists) is a different construct with its own attributes. The fence is answered by the content pipeline and reaches Foundry and the book; the shortcode is the site's alone.
2087
+ The label between the brackets is the picture's text alternative, read aloud by a screen reader and printed in the book when the package ships no file for the seal. An empty label takes the humanized slug. The seal is drawn as a figure at the place the span stands in the sentence.
1850
2088
 
1851
- | Parameter | Value | Default | What it does |
1852
- | --------- | --------------------------- | ------------- | ----------------------------------------------------------------------------------------------------- |
1853
- | `tag` | a tag | required | The taxonomy term to list, matched case-insensitively. May be given as the first positional argument. |
1854
- | `section` | a top-level section name | every section | Restricts the list to one section of the site, such as `rules`. |
1855
- | `sort` | `title`, `weight` or `date` | `title` | The order of the list. `date` puts the newest first. |
1856
- | `desc` | `true` | off | Shows each page's description beneath its link. Any other value leaves descriptions out. |
2089
+ | Attribute | Value | Default | What it does |
2090
+ | --------- | ------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
2091
+ | `slug` | the SVG's basename | required | Names the file `images/cartouches/<slug>.svg`. |
2092
+ | `align` | `left` or `right` | centered | Floats the figure on the website and in Foundry so prose wraps around it. The book sets it at that side of its own line. |
2093
+ | `height` | a length | set by the theme | Sets the image's height, a number followed by `rem`, `em`, `px`, `pt`, `cm`, `mm` or `in`. The width follows the image's aspect ratio. |
2094
+ | `caption` | text | none | A caption under the image. |
1857
2095
 
1858
2096
  ```markdown
1859
- {{< pagelist tag="key-concept" >}}
2097
+ The seal [Ta Kheperu]{.cartouche slug=takheperu} is cut above the gate.
1860
2098
 
1861
- {{< pagelist tag="key-concept" section="rules" sort="weight" desc="true" >}}
2099
+ The seal of [Anpu]{.cartouche slug=anubis caption="Anpu"} is cut below it.
1862
2100
  ```
1863
2101
 
1864
- The first call lists every page tagged `key-concept` alphabetically by title. The second lists only those in the `rules` section, in weight order, each followed by its description. A tag that no page carries produces a build warning naming the tag and the note's position, and the list is left out.
2102
+ The first span centers the `takheperu` seal and takes its alternative text from the label. The second adds a caption under the `anubis` seal. A cartouche takes no `#id`, no other class and no other attribute; write the alternative text as the label, because an `alt=` attribute is refused, as is `size=`. A cartouche with a finding is drawn as an ordinary span, and the finding names the line and column of the span. A `:::cartouche` fence is not a cartouche: it is reported like any other unknown block and draws nothing.
1865
2103
 
1866
2104
  ## Expressions
1867
2105
 
@@ -1878,7 +2116,7 @@ There are {{words (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'")}} bein
1878
2116
  - **An expression is one line.** It may not contain a line break or a brace.
1879
2117
  - **A failed expression leaves its own text in the page** and reports the line and column it is written on, so the page is readable and the fault is locatable.
1880
2118
  - **`\{{` writes an expression without evaluating it**, for an example in prose.
1881
- - **`{{{…}}}`, `{{<…}}` and `{{%…}}` are left alone.** A Hugo shortcode is not an expression.
2119
+ - **`{{{…}}}` is left alone.**
1882
2120
  - **An expression inside a code fence is left alone.**
1883
2121
  - Helpers nest, with the inner call in parentheses: `{{words (sql "…")}}`.
1884
2122